diff --git a/.ci/travis/run.sh b/.ci/travis/run.sh index b3a6a4a09..1501387a5 100755 --- a/.ci/travis/run.sh +++ b/.ci/travis/run.sh @@ -14,19 +14,20 @@ if [[ "$(uname -s)" == 'Darwin' ]]; then fi # install psutil +make clean python setup.py build python setup.py develop # run tests (with coverage) -if [[ "$(uname -s)" != 'Darwin' ]]; then - coverage run psutil/tests/runner.py --include="psutil/*" --omit="test/*,*setup*" +if [[ $PYVER == '2.7' ]] && [[ "$(uname -s)" != 'Darwin' ]]; then + PSUTIL_TESTING=1 python -Wa -m coverage run psutil/tests/__main__.py else - python psutil/tests/runner.py + PSUTIL_TESTING=1 python -Wa psutil/tests/__main__.py fi -if [ "$PYVER" == "2.7" ] || [ "$PYVER" == "3.5" ]; then +if [ "$PYVER" == "2.7" ] || [ "$PYVER" == "3.6" ]; then # run mem leaks test - python psutil/tests/test_memory_leaks.py + PSUTIL_TESTING=1 python -Wa psutil/tests/test_memory_leaks.py # run linter (on Linux only) if [[ "$(uname -s)" != 'Darwin' ]]; then python -m flake8 diff --git a/.coveragerc b/.coveragerc index 6b6309b9f..7d3f185f5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,32 +2,31 @@ include = *psutil* - omit = + psutil/_compat.py psutil/tests/* setup.py - psutil/_compat.py - exclude_lines = - pragma: no cover - if PY3: + enum.IntEnum + except ImportError: + globals().update if __name__ == .__main__.: - if sys.platform.startswith if _WINDOWS: - import enum - if enum is not None: + if BSD if enum is None: + if enum is not None: + if FREEBSD if has_enums: + if LINUX if LITTLE_ENDIAN: - enum.IntEnum - except ImportError: - raise NotImplementedError - if WINDOWS - if OSX - if BSD - if FREEBSD - if OPENBSD if NETBSD - if SUNOS - if LINUX + if OPENBSD + if OSX if ppid_map is None: + if PY3: + if SUNOS + if sys.platform.startswith + if WINDOWS + import enum + pragma: no cover + raise NotImplementedError diff --git a/.git-pre-commit b/.git-pre-commit index e15884d16..c3c605e05 100755 --- a/.git-pre-commit +++ b/.git-pre-commit @@ -6,8 +6,15 @@ """ This gets executed on 'git commit' and rejects the commit in case the -submitted code does not pass validation. -Install it with "make install-git-hooks". +submitted code does not pass validation. Validation is run only against +the *.py files which were modified in the commit. Checks: + +- assert no space at EOLs +- assert not pdb.set_trace in code +- assert no bare except clause ("except:") in code +- assert "flake8" returns no warnings + +Install this with "make install-git-hooks". """ from __future__ import print_function @@ -30,6 +37,8 @@ def term_supports_colors(): def hilite(s, ok=True, bold=False): """Return an highlighted version of 'string'.""" + if not term_supports_colors(): + return s attr = [] if ok is None: # no color pass @@ -43,34 +52,49 @@ def hilite(s, ok=True, bold=False): def exit(msg): - if term_supports_colors(): - msg = hilite(msg, ok=False) + msg = hilite(msg, ok=False) print(msg, file=sys.stderr) sys.exit(1) +def sh(cmd): + """run cmd in a subprocess and return its output. + raises RuntimeError on error. + """ + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, universal_newlines=True) + stdout, stderr = p.communicate() + if p.returncode != 0: + raise RuntimeError(stderr) + if stderr: + print(stderr, file=sys.stderr) + if stdout.endswith('\n'): + stdout = stdout[:-1] + return stdout + + def main(): - out = subprocess.check_output("git diff --cached --name-only", shell=True) - py_files = [x for x in out.split(b'\n') if x.endswith(b'.py') and + out = sh("git diff --cached --name-only") + py_files = [x for x in out.split('\n') if x.endswith('.py') and os.path.exists(x)] + lineno = 0 for path in py_files: with open(path) as f: - data = f.read() - - # pdb - if "pdb.set_trace" in data: - for lineno, line in enumerate(data.split('\n'), 1): + for line in f: + lineno += 1 + # space at end of line + if line.endswith(' '): + print("%s:%s %r" % (path, lineno, line)) + return exit( + "commit aborted: space at end of line") line = line.rstrip() + # pdb if "pdb.set_trace" in line: print("%s:%s %s" % (path, lineno, line)) return exit( "commit aborted: you forgot a pdb in your python code") - - # bare except clause - if "except:" in data: - for lineno, line in enumerate(data.split('\n'), 1): - line = line.rstrip() + # bare except clause if "except:" in line and not line.endswith("# NOQA"): print("%s:%s %s" % (path, lineno, line)) return exit("commit aborted: bare except clause") diff --git a/.travis.yml b/.travis.yml index 17206c58a..9289eb6b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,37 +3,26 @@ language: python cache: pip matrix: include: + # Linux - python: 2.6 - python: 2.7 - - python: 3.3 - python: 3.4 - python: 3.5 - - "pypy" - # XXX - commented because OSX builds are deadly slow - # - language: generic - # os: osx - # env: PYVER=py26 + - python: 3.6 + # OSX - 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 - | - if [ "$(uname -s)" != 'Darwin' ]; then + if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]] && [[ "$(uname -s)" != 'Darwin' ]]; then + echo "sending test coverage results to coveralls.io" coveralls fi diff --git a/CREDITS b/CREDITS index ccc4515f2..cf9ce493c 100644 --- a/CREDITS +++ b/CREDITS @@ -20,6 +20,32 @@ C: Italy E: g.rodola@gmail.com W: http://grodola.blogspot.com/ +Experts +======= + +Github usernames of people to CC on github when in need of help. + +- NetBSD: + - 0-wiz-0, Thomas Klausner + - ryoqun, Ryo Onodera +- OpenBSD: + - landryb, Landry Breuil +- FreeBSD: + - glebius, Gleb Smirnoff (#1013) + - sunpoet, Po-Chuan Hsieh (pkg maintainer, #1105) + - kostikbel, Konstantin Belousov (#1105) +- OSX: + - whitlockjc, Jeremy Whitlock +- Windows: + - mrjefftang, Jeff Tang + - wj32, Wen Jia Liu + - fbenkstein, Frank Benkstein +- SunOS: + - wiggin15, Arnon Yaari + - alxchk, Oleksii Shevchuk +- AIX: + - wiggin15, Arnon Yaari (maintainer) + Contributors ============ @@ -29,6 +55,10 @@ E: jloden@gmail.com D: original co-author, initial design/bootstrap and occasional bug fixes W: http://www.jayloden.com +N: Arnon Yaari (wiggin15) +W: https://github.com/wiggin15 +I: 517, 607, 610, 1131, 1123, 1130, 1154, 1164, 1174, 1177 + N: Jeff Tang W: https://github.com/mrjefftang I: 340, 529, 616, 653, 654, 648, 641 @@ -339,10 +369,6 @@ N: maozguttman W: https://github.com/maozguttman I: 659 -N: wiggin15 -W: https://github.com/wiggin15 -I: 517, 607, 610 - N: dasumin W: https://github.com/dasumin I: 541 @@ -388,8 +414,8 @@ E: khanzf@gmail.com I: 823 N: Jake Omann -E: https://github.com/jhomann -I: 816 +E: https://github.com/jomann09 +I: 816, 775 N: Jeremy Humble W: https://github.com/jhumble @@ -419,4 +445,73 @@ I: 919 N: Max Bélanger W: https://github.com/maxbelanger -I: 936 +I: 936, 1133 + +N: Pierre Fersing +C: France +E: pierre.fersing@bleemeo.com +I: 950 + +N: Thiago Borges Abdnur +W: https://github.com/bolaum +I: 959 + +N: Nicolas Hennion +W: https://github.com/nicolargo +I: 974 + +N: Baruch Siach +W: https://github.com/baruchsiach +I: 872 + +N: Danek Duvall +W: https://github.com/dhduvall +I: 1002 + +N: Alexander Hasselhuhn +C: Germany +W: https://github.com/alexanha + +N: Himanshu Shekhar +W: https://github.com/himanshub16 +I: 1036 + +N: Yannick Gingras +W: https://github.com/ygingras +I: 1057 + +N: Gleb Smirnoff +W: https://github.com/glebius +I: 1042, 1079 + +N: Oleksii Shevchuk +W: https://github.com/alxchk +I: 1077, 1093, 1091 + +N: Prodesire +W: https://github.com/Prodesire +I: 1138 + +N: Sebastian Saip +W: https://github.com/ssaip +I: 1141 + +N: Jakub Bacic +W: https://github.com/jakub-bacic +I: 1127 + +N: Akos Kiss +W: https://github.com/akosthekiss +I: 1150 + +N: Adrian Page +W: https://github.com/adpag +I: 1159, 1160, 1161 + +N: Matthew Long +W: https://github.com/matray +I: 1167 + +N: janderbrain +W: https://github.com/janderbrain +I: 1169 diff --git a/DEVGUIDE.rst b/DEVGUIDE.rst index 95bea79a3..2d48ced27 100644 --- a/DEVGUIDE.rst +++ b/DEVGUIDE.rst @@ -1,30 +1,35 @@ -===== -Setup -===== +======================= +Setup and running tests +======================= If you plan on hacking on psutil this is what you're supposed to do first: -- clone the GIT repository:: +- clone the GIT repository: + +.. code-block:: bash $ git clone git@github.com:giampaolo/psutil.git -- install system deps (see `install instructions `__). +- install test deps and GIT hooks: + +.. code-block:: bash + + make setup-dev-env + +- run tests: -- install development deps; these are useful for running tests (e.g. mock, - unittest2), building doc (e.g. sphinx), running linters (flake8), etc. :: +.. code-block:: bash - $ make setup-dev-env + make test -- bear in mind that ``make`` (see `Makefile `_) +- bear in mind that ``make`` + (see `Makefile `_) is the designated tool to run tests, build, install etc. and that it is also available on Windows (see `make.bat `_). -- bear in mind that both psutil (``make install``) and any other lib - (``make setup-dev-env``) is installed as a limited user - (``pip install --user ...``), so develop as such (don't use root). -- (UNIX only) run ``make install-git-hooks``: this will reject your commits - if python code is not PEP8 compliant. -- run ``make test`` to run tests. +- do not use ``sudo``; ``make install`` installs psutil as a limited user in + "edit" mode; also ``make setup-dev-env`` installs deps as a limited user. +- use `make help` to see the list of available commands. ============ Coding style @@ -39,14 +44,48 @@ Coding style Makefile ======== -Some useful make commands:: +Some useful make commands: + +.. code-block:: bash + + make install # install + make setup-dev-env # install useful dev libs (pyflakes, unittest2, etc.) + make test # run unit tests + make test-memleaks # run memory leak tests + make test-coverage # run test coverage + make flake8 # run PEP8 linter + +There are some differences between ``make`` on UNIX and Windows. +For instance, to run a specific Python version. On UNIX: + +.. code-block:: bash + + make test PYTHON=python3.5 - $ make install # install - $ make setup-dev-env # install useful dev libs (pyflakes, unittest2, etc.) - $ make test # run all tests - $ make test-memleaks # run memory leak tests - $ make coverage # run test coverage - $ make flake8 # run PEP8 linter +On Windows: + +.. code-block:: bat + + set PYTHON=C:\python35\python.exe && make test + +...or: + +.. code-block:: bat + + make -p 35 test + +If you want to modify psutil and run a script on the fly which uses it do +(on UNIX): + +.. code-block:: bash + + make test TSCRIPT=foo.py + +On Windows: + +.. code-block:: bat + + set TSCRIPT=foo.py && make test ==================== Adding a new feature @@ -54,7 +93,7 @@ Adding a new feature Usually the files involved when adding a new functionality are: -.. code-block:: plain +.. code-block:: bash psutil/__init__.py # main psutil namespace psutil/_ps{platform}.py # python platform wrapper @@ -81,6 +120,16 @@ Typical process occurring when adding a new functionality (API): - update ``README.rst`` (if necessary). - make a pull request. +=================== +Make a pull request +=================== + +- fork psutil +- create your feature branch (``git checkout -b my-new-feature``) +- commit your changes (``git commit -am 'add some feature'``) +- push to the branch (``git push origin my-new-feature``) +- create a new pull request + ====================== Continuous integration ====================== @@ -101,18 +150,18 @@ Test files controlling these are and `appveyor.yml `_. Both services run psutil test suite against all supported python version -(2.6 - 3.5). +(2.6 - 3.6). Two icons in the home page (README) always show the build status: -.. image:: https://api.travis-ci.org/giampaolo/psutil.png?branch=master +.. image:: https://img.shields.io/travis/giampaolo/psutil/master.svg?maxAge=3600&label=Linux%20/%20OSX :target: https://travis-ci.org/giampaolo/psutil :alt: Linux tests (Travis) -.. image:: https://ci.appveyor.com/api/projects/status/qdwvw7v1t915ywr5/branch/master?svg=true +.. image:: https://img.shields.io/appveyor/ci/giampaolo/psutil/master.svg?maxAge=3600&label=Windows :target: https://ci.appveyor.com/project/giampaolo/psutil :alt: Windows tests (Appveyor) -OSX, FreeBSD and Solaris are currently tested manually (sigh!). +OSX, BSD, AIX and Solaris are currently tested manually (sigh!). Test coverage ------------- @@ -134,8 +183,7 @@ Documentation - it uses `RsT syntax `_ and it's built with `sphinx `_. - doc can be built with ``make setup-dev-env; cd docs; make html``. -- public doc is hosted on http://pythonhosted.org/psutil/. -- it is uploaded on every new release with ``make upload-doc``. +- public doc is hosted on http://psutil.readthedocs.io/ ======================= Releasing a new version @@ -155,7 +203,7 @@ FreeBSD notes .. code-block:: bash - $ pkg install python python3 gcc git vim screen bash - $ chsh -s /usr/local/bin/bash user # set bash as default shell + pkg install python python3 gcc git vim screen bash + chsh -s /usr/local/bin/bash user # set bash as default shell - ``/usr/src`` contains the source codes for all installed CLI tools (grep in it). diff --git a/HISTORY.rst b/HISTORY.rst index defd94d93..dd813c5b5 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,9 +1,334 @@ *Bug tracker at https://github.com/giampaolo/psutil/issues* +5.4.3 +===== + +*2018-01-01* + +**Enhancements** + +- 775_: disk_partitions() on Windows return mount points. + +**Bug fixes** + +- 1193_: pids() may return False on OSX. + +5.4.2 +===== + +*2017-12-07* + +**Enhancements** + +- 1173_: introduced PSUTIL_DEBUG environment variable which can be set in order + to print useful debug messages on stderr (useful in case of nasty errors). +- 1177_: added support for sensors_battery() on OSX. (patch by Arnon Yaari) +- 1183_: Process.children() is 2x faster on UNIX and 2.4x faster on Linux. +- 1188_: deprecated method Process.memory_info_ex() now warns by using + FutureWarning instead of DeprecationWarning. + +**Bug fixes** + +- 1152_: [Windows] disk_io_counters() may return an empty dict. +- 1169_: [Linux] users() "hostname" returns username instead. (patch by + janderbrain) +- 1172_: [Windows] `make test` does not work. +- 1179_: [Linux] Process.cmdline() is now able to splits cmdline args for + misbehaving processes which overwrite /proc/pid/cmdline and use spaces + instead of null bytes as args separator. +- 1181_: [OSX] Process.memory_maps() may raise ENOENT. +- 1187_: [OSX] pids() does not return PID 0 on recent OSX versions. + +5.4.1 +===== + +*2017-11-08* + +**Enhancements** + +- 1164_: [AIX] add support for Process.num_ctx_switches(). (patch by Arnon + Yaari) +- 1053_: abandon Python 3.3 support (psutil still works but it's no longer + tested). + +**Bug fixes** + +- 1150_: [Windows] when a process is terminate()d now the exit code is set to + SIGTERM instead of 0. (patch by Akos Kiss) +- 1151_: python -m psutil.tests fail +- 1154_: [AIX] psutil won't compile on AIX 6.1.0. (patch by Arnon Yaari) +- 1167_: [Windows] net_io_counter() packets count now include also non-unicast + packets. (patch by Matthew Long) + +5.4.0 +===== + +*2017-10-12* + +**Enhancements** + +- 1123_: [AIX] added support for AIX platform. (patch by Arnon Yaari) + +**Bug fixes** + +- 1009_: [Linux] sensors_temperatures() may crash with IOError. +- 1012_: [Windows] disk_io_counters()'s read_time and write_time were expressed + in tens of micro seconds instead of milliseconds. +- 1127_: [OSX] invalid reference counting in Process.open_files() may lead to + segfault. (patch by Jakub Bacic) +- 1129_: [Linux] sensors_fans() may crash with IOError. (patch by Sebastian + Saip) +- 1131_: [SunOS] fix compilation warnings. (patch by Arnon Yaari) +- 1133_: [Windows] can't compile on newer versions of Visual Studio 2017 15.4. + (patch by Max Bélanger) +- 1138_: [Linux] can't compile on CentOS 5.0 and RedHat 5.0. + (patch by Prodesire) + +5.3.1 +===== + +*2017-09-10* + +**Enhancements** + +- 1124_: documentation moved to http://psutil.readthedocs.io + +**Bug fixes** + +- 1105_: [FreeBSD] psutil does not compile on FreeBSD 12. +- 1125_: [BSD] net_connections() raises TypeError. + +**Compatibility notes** + +- 1120_: .exe files for Windows are no longer uploaded on PYPI as per PEP-527; + only wheels are provided. + +5.3.0 +===== + +*2017-09-01* + +**Enhancements** + +- 802_: disk_io_counters() and net_io_counters() numbers no longer wrap + (restart from 0). Introduced a new "nowrap" argument. +- 928_: psutil.net_connections() and psutil.Process.connections() "laddr" and + "raddr" are now named tuples. +- 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. +- 1022_: psutil.users() provides a new "pid" field. +- 1025_: process_iter() accepts two new parameters in order to invoke + Process.as_dict(): "attrs" and "ad_value". With this you can iterate over all + processes in one shot without needing to catch NoSuchProcess and do list/dict + comprehensions. +- 1040_: implemented full unicode support. +- 1051_: disk_usage() on Python 3 is now able to accept bytes. +- 1058_: test suite now enables all warnings by default. +- 1060_: source distribution is dynamically generated so that it only includes + relevant files. +- 1079_: [FreeBSD] net_connections()'s fd number is now being set for real + (instead of -1). (patch by Gleb Smirnoff) +- 1091_: [SunOS] implemented Process.environ(). (patch by Oleksii Shevchuk) + +**Bug fixes** + +- 989_: [Windows] boot_time() may return a negative value. +- 1007_: [Windows] boot_time() can have a 1 sec fluctuation between calls; the + value of the first call is now cached so that boot_time() always returns the + same value if fluctuation is <= 1 second. +- 1013_: [FreeBSD] psutil.net_connections() may return incorrect PID. (patch + by Gleb Smirnoff) +- 1014_: [Linux] Process class can mask legitimate ENOENT exceptions as + NoSuchProcess. +- 1016_: disk_io_counters() raises RuntimeError on a system with no disks. +- 1017_: net_io_counters() raises RuntimeError on a system with no network + cards installed. +- 1021_: [Linux] open_files() may erroneously raise NoSuchProcess instead of + skipping a file which gets deleted while open files are retrieved. +- 1029_: [OSX, FreeBSD] Process.connections('unix') on Python 3 doesn't + properly handle unicode paths and may raise UnicodeDecodeError. +- 1033_: [OSX, FreeBSD] memory leak for net_connections() and + Process.connections() when retrieving UNIX sockets (kind='unix'). +- 1040_: fixed many unicode related issues such as UnicodeDecodeError on + Python 3 + UNIX and invalid encoded data on Windows. +- 1042_: [FreeBSD] psutil won't compile on FreeBSD 12. +- 1044_: [OSX] different Process methods incorrectly raise AccessDenied for + zombie processes. +- 1046_: [Windows] disk_partitions() on Windows overrides user's SetErrorMode. +- 1047_: [Windows] Process username(): memory leak in case exception is thrown. +- 1048_: [Windows] users()'s host field report an invalid IP address. +- 1050_: [Windows] Process.memory_maps memory() leaks memory. +- 1055_: cpu_count() is no longer cached; this is useful on systems such as + Linux where CPUs can be disabled at runtime. This also reflects on + Process.cpu_percent() which no longer uses the cache. +- 1058_: fixed Python warnings. +- 1062_: disk_io_counters() and net_io_counters() raise TypeError if no disks + or NICs are installed on the system. +- 1063_: [NetBSD] net_connections() may list incorrect sockets. +- 1064_: [NetBSD] swap_memory() may segfault in case of error. +- 1065_: [OpenBSD] Process.cmdline() may raise SystemError. +- 1067_: [NetBSD] Process.cmdline() leaks memory if process has terminated. +- 1069_: [FreeBSD] Process.cpu_num() may return 255 for certain kernel + processes. +- 1071_: [Linux] cpu_freq() may raise IOError on old RedHat distros. +- 1074_: [FreeBSD] sensors_battery() raises OSError in case of no battery. +- 1075_: [Windows] net_if_addrs(): inet_ntop() return value is not checked. +- 1077_: [SunOS] net_if_addrs() shows garbage addresses on SunOS 5.10. + (patch by Oleksii Shevchuk) +- 1077_: [SunOS] net_connections() does not work on SunOS 5.10. (patch by + Oleksii Shevchuk) +- 1079_: [FreeBSD] net_connections() didn't list locally connected sockets. + (patch by Gleb Smirnoff) +- 1085_: cpu_count() return value is now checked and forced to None if <= 1. +- 1087_: Process.cpu_percent() guard against cpu_count() returning None and + assumes 1 instead. +- 1093_: [SunOS] memory_maps() shows wrong 64 bit addresses. +- 1094_: [Windows] psutil.pid_exists() may lie. Also, all process APIs relying + on OpenProcess Windows API now check whether the PID is actually running. +- 1098_: [Windows] Process.wait() may erroneously return sooner, when the PID + is still alive. +- 1099_: [Windows] Process.terminate() may raise AccessDenied even if the + process already died. +- 1101_: [Linux] sensors_temperatures() may raise ENODEV. + +**Porting notes** + +- 1039_: returned types consolidation: + - Windows / Process.cpu_times(): fields #3 and #4 were int instead of float + - Linux / FreeBSD: connections('unix'): raddr is now set to "" instead of + None + - OpenBSD: connections('unix'): laddr and raddr are now set to "" instead of + None +- 1040_: all strings are encoded by using OS fs encoding. +- 1040_: the following Windows APIs on Python 2 now return a string instead of + unicode: + - Process.memory_maps().path + - WindowsService.bin_path() + - WindowsService.description() + - WindowsService.display_name() + - WindowsService.username() + +5.2.2 +===== + +*2017-04-10* + +**Bug fixes** + +- 1000_: fixed some setup.py warnings. +- 1002_: [SunOS] remove C macro which will not be available on new Solaris + versions. (patch by Danek Duvall) +- 1004_: [Linux] Process.io_counters() may raise ValueError. +- 1006_: [Linux] cpu_freq() may return None on some Linux versions does not + support the function; now the function is not declared instead. +- 1009_: [Linux] sensors_temperatures() may raise OSError. +- 1010_: [Linux] virtual_memory() may raise ValueError on Ubuntu 14.04. + +5.2.1 +===== + +*2017-03-24* + +**Bug fixes** + +- 981_: [Linux] cpu_freq() may return an empty list. +- 993_: [Windows] Process.memory_maps() on Python 3 may raise + UnicodeDecodeError. +- 996_: [Linux] sensors_temperatures() may not show all temperatures. +- 997_: [FreeBSD] virtual_memory() may fail due to missing sysctl parameter on + FreeBSD 12. + +5.2.0 +===== + +*2017-03-05* + +**Enhancements** + +- 971_: [Linux] Add psutil.sensors_fans() function. (patch by Nicolas Hennion) +- 976_: [Windows] Process.io_counters() has 2 new fields: *other_count* and + *other_bytes*. +- 976_: [Linux] Process.io_counters() has 2 new fields: *read_chars* and + *write_chars*. + +**Bug fixes** + +- 872_: [Linux] can now compile on Linux by using MUSL C library. +- 985_: [Windows] Fix a crash in `Process.open_files` when the worker thread for `NtQueryObject` times out. +- 986_: [Linux] Process.cwd() may raise NoSuchProcess instead of ZombieProcess. + +5.1.3 +===== + +**Bug fixes** + +- 971_: [Linux] sensors_temperatures() didn't work on CentOS 7. +- 973_: cpu_percent() may raise ZeroDivisionError. + +5.1.2 +===== + +*2017-02-03* + +**Bug fixes** + +- 966_: [Linux] sensors_battery().power_plugged may erroneously return None on + Python 3. +- 968_: [Linux] disk_io_counters() raises TypeError on python 3. +- 970_: [Linux] sensors_battery()'s name and label fields on Python 3 are bytes + instead of str. + +5.1.1 +===== + +*2017-02-03* + +**Enhancements** + +- 966_: [Linux] sensors_battery().percent is a float and is more precise. + +**Bug fixes** + +- 964_: [Windows] Process.username() and psutil.users() may return badly + decoding character on Python 3. +- 965_: [Linux] disk_io_counters() may miscalculate sector size and report the + wrong number of bytes read and written. +- 966_: [Linux] sensors_battery() may fail with "no such file error". +- 966_: [Linux] sensors_battery().power_plugged may lie. + +5.1.0 +===== + +*2017-02-01* + +**Enhancements** + +- 357_: added psutil.Process.cpu_num() (what CPU a process is on). +- 371_: added psutil.sensors_temperatures() (Linux only). +- 941_: added psutil.cpu_freq() (CPU frequency). +- 955_: added psutil.sensors_battery() (Linux, Windows, only). +- 956_: cpu_affinity([]) can now be used as an alias to set affinity against + all eligible CPUs. + +**Bug fixes** + +- 687_: [Linux] pid_exists() no longer returns True if passed a process thread + ID. +- 948_: cannot install psutil with PYTHONOPTIMIZE=2. +- 950_: [Windows] Process.cpu_percent() was calculated incorrectly and showed + higher number than real usage. +- 951_: [Windows] the uploaded wheels for Python 3.6 64 bit didn't work. +- 959_: psutil exception objects could not be pickled. +- 960_: Popen.wait() did not return the correct negative exit status if process + is ``kill()``ed by a signal. +- 961_: [Windows] WindowsService.description() may fail with + ERROR_MUI_FILE_NOT_FOUND. + 5.0.1 ===== -*XXXX-XX-XX* +*2016-12-21* **Enhancements** @@ -13,8 +338,12 @@ **Bug fixes** +- 609_: [SunOS] psutil does not compile on Solaris 10. - 936_: [Windows] fix compilation error on VS 2013 (patch by Max Bélanger). - +- 940_: [Linux] cpu_percent() and cpu_times_percent() was calculated + incorrectly as "iowait", "guest" and "guest_nice" times were not properly + taken into account. +- 944_: [OpenBSD] psutil.pids() was omitting PID 0. 5.0.0 ===== @@ -33,7 +362,6 @@ raising an exception. - 933_: [Windows] memory leak in cpu_stats() and WindowsService.description(). - 4.4.2 ===== @@ -43,7 +371,6 @@ - 931_: psutil no longer compiles on Solaris. - 4.4.1 ===== @@ -53,7 +380,6 @@ - 927_: ``Popen.__del__`` may cause maximum recursion depth error. - 4.4.0 ===== @@ -90,7 +416,6 @@ OSError with no exception set if process is gone. - 916_: [OSX] fix many compilation warnings. - 4.3.1 ===== @@ -115,7 +440,6 @@ unit (ms instead of sec). - 870_: [Windows] Handle leak inside psutil_get_process_data. - 4.3.0 ===== @@ -139,7 +463,6 @@ - 816_: [Windows] fixed net_io_counter() values wrapping after 4.3GB in Windows Vista (NT 6.0) and above using 64bit values from newer win APIs. - 4.2.0 ===== @@ -162,7 +485,6 @@ - 813_: Process.as_dict() should ignore extraneous attribute names which gets attached to the Process instance. - 4.1.0 ===== @@ -190,7 +512,6 @@ - 788_: [NetBSD] virtual_memory()'s buffers and shared values were set to 0. - 790_: [OSX] psutil won't compile on OSX 10.4. - 4.0.0 ===== @@ -234,7 +555,6 @@ broken on 2.4 kernels. - 770_: [NetBSD] disk_io_counters() metrics didn't update. - 3.4.2 ===== @@ -250,7 +570,6 @@ - 724_: [FreeBSD] psutil.virtual_memory().total is incorrect. - 730_: [FreeBSD] psutil.virtual_memory() crashes. - 3.4.1 ===== @@ -274,7 +593,6 @@ due to missing /proc/vmstat. - 724_: [FreeBSD] virtual_memory().total is slightly incorrect. - 3.3.0 ===== @@ -290,7 +608,6 @@ - 692_: [UNIX] Process.name() is no longer cached as it may change. - 3.2.2 ===== @@ -309,7 +626,6 @@ - 688_: [Windows] compilation fails with MSVC 2015, Python 3.5. (patch by Mike Sarahan) - 3.2.1 ===== @@ -319,7 +635,6 @@ - 677_: [Linux] can't install psutil due to bug in setup.py. - 3.2.0 ===== @@ -359,7 +674,6 @@ - 675_: [Linux] net_connections(); UnicodeDecodeError may occur when listing UNIX sockets. - 3.1.1 ===== @@ -371,7 +685,6 @@ - 645_: [Linux] psutil.cpu_times_percent() may produce negative results. - 656_: 'from psutil import *' does not work. - 3.1.0 ===== @@ -384,8 +697,7 @@ https://ci.appveyor.com/project/giampaolo/psutil. - 647_: new dev guide: https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst -- 651_: continuous code quality test integration with - https://scrutinizer-ci.com/g/giampaolo/psutil/ +- 651_: continuous code quality test integration with scrutinizer-ci.com **Bug fixes** @@ -404,7 +716,6 @@ - 653_: [Windows] Add inet_ntop function for Windows XP to support IPv6. - 641_: [Windows] Replace deprecated string functions with safe equivalents. - 3.0.1 ===== @@ -417,7 +728,6 @@ - 635_: [UNIX] crash on module import if 'enum' package is installed on python < 3.4. - 3.0.0 ===== @@ -465,7 +775,6 @@ - 628_: [Linux] Process.name() truncates process name in case it contains spaces or parentheses. - 2.2.1 ===== @@ -476,7 +785,6 @@ - 496_: [Linux] fix "ValueError: ambiguos inode with multiple PIDs references" (patch by Bruno Binet) - 2.2.0 ===== @@ -507,7 +815,6 @@ - 571_: [Linux] Process.open_files() might swallow AccessDenied exceptions and return an incomplete list of open files. - 2.1.3 ===== @@ -515,7 +822,6 @@ - 536_: [Linux]: fix "undefined symbol: CPU_ALLOC" compilation error. - 2.1.2 ===== @@ -546,7 +852,6 @@ (< 2.6.5) (patch by Yaolong Huang) - 533_: [Linux] Process.memory_maps() may raise TypeError on old Linux distros. - 2.1.1 ===== @@ -559,7 +864,6 @@ - 460_: [Windows] net_io_counters() wraps after 4G. - 491_: [Linux] psutil.net_connections() exceptions. (patch by Alexander Grothe) - 2.1.0 ===== @@ -575,7 +879,6 @@ Roudsari) - 489_: [Linux] psutil.disk_partitions() return an empty list. - 2.0.0 ===== @@ -744,7 +1047,6 @@ DeprecationWarning. - Process instances' "retcode" attribute returned by psutil.wait_procs() has been renamed to "returncode" for consistency with subprocess.Popen. - 1.2.1 ===== @@ -757,7 +1059,6 @@ DeprecationWarning. - 425_: [Solaris] crash on import due to failure at determining BOOT_TIME. - 443_: [Linux] can't set CPU affinity on systems with more than 64 cores. - 1.2.0 ===== @@ -775,7 +1076,6 @@ DeprecationWarning. - 348_: [Windows XP/Vista] fix "ImportError: DLL load failed" occurring on module import. - 1.1.3 ===== @@ -786,7 +1086,6 @@ DeprecationWarning. - 442_: [Linux] psutil won't compile on certain version of Linux because of missing prlimit(2) syscall. - 1.1.2 ===== @@ -797,7 +1096,6 @@ DeprecationWarning. - 442_: [Linux] psutil won't compile on Debian 6.0 because of missing prlimit(2) syscall. - 1.1.1 ===== @@ -808,7 +1106,6 @@ DeprecationWarning. - 442_: [Linux] psutil won't compile on kernels < 2.6.36 due to missing prlimit(2) syscall. - 1.1.0 ===== @@ -839,7 +1136,6 @@ DeprecationWarning. - 408_: turn STATUS_* and CONN_* constants into plain Python strings. - 1.0.1 ===== @@ -849,7 +1145,6 @@ DeprecationWarning. - 405_: network_io_counters(pernic=True) no longer works as intended in 1.0.0. - 1.0.0 ===== @@ -879,7 +1174,6 @@ DeprecationWarning. renamed to 'laddr' and 'raddr'. - psutil.network_io_counters() renamed to psutil.net_io_counters(). - 0.7.1 ===== @@ -893,7 +1187,6 @@ DeprecationWarning. - 372_: [BSD] different process methods raise NoSuchProcess instead of AccessDenied. - 0.7.0 ===== @@ -957,7 +1250,6 @@ DeprecationWarning. will raise NotImplementedError instead of RuntimeError. - psutil.error module is deprecated and scheduled for removal. - 0.6.1 ===== @@ -978,7 +1270,6 @@ DeprecationWarning. - process exe can now return an empty string instead of raising AccessDenied. - process exe is no longer resolved in case it's a symlink. - 0.6.0 ===== @@ -1068,7 +1359,6 @@ DeprecationWarning. - [Windows and BSD] psutil.virtmem_usage() now returns information about swap memory instead of virtual memory. - 0.5.1 ===== @@ -1084,7 +1374,6 @@ DeprecationWarning. - 292_: [Linux] race condition in process files/threads/connections. - 294_: [Windows] Process CPU affinity is only able to set CPU #0. - 0.5.0 ===== @@ -1155,7 +1444,6 @@ DeprecationWarning. - psutil.STATUS_* constants can now be compared by using their string representation. - 0.4.1 ===== @@ -1170,7 +1458,6 @@ DeprecationWarning. - 236_: [Windows] memory/handle leak in Process's get_memory_info(), suspend() and resume() methods. - 0.4.0 ===== @@ -1211,7 +1498,6 @@ DeprecationWarning. line in /proc/meminfo. - 226_: [FreeBSD] crash at import time on FreeBSD 7 and minor. - 0.3.0 ===== @@ -1241,7 +1527,6 @@ DeprecationWarning. - 180_: [Windows] Process's get_num_threads() and get_threads() methods can raise NoSuchProcess exception while process still exists. - 0.2.1 ===== @@ -1284,7 +1569,6 @@ DeprecationWarning. - Process "uid" and "gid" properties are deprecated in favor of "uids" and "gids" properties. - 0.2.0 ===== @@ -1346,7 +1630,6 @@ DeprecationWarning. - psutil.Process.get_cpu_percent() and psutil.cpu_percent() no longer returns immediately by default (see issue 123). - 0.1.3 ===== @@ -1377,7 +1660,6 @@ DeprecationWarning. - 77_: NoSuchProcess wasn't raised on Process.create_time if kill() was used first. - 0.1.2 ===== @@ -1401,7 +1683,6 @@ DeprecationWarning. - 40_: test_get_cpu_times() failing on FreeBSD and OS X. - 42_: [Windows] get_memory_percent() raises AccessDenied. - 0.1.1 ===== @@ -2436,3 +2717,504 @@ DeprecationWarning. .. _997: https://github.com/giampaolo/psutil/issues/997 .. _998: https://github.com/giampaolo/psutil/issues/998 .. _999: https://github.com/giampaolo/psutil/issues/999 +.. _1000: https://github.com/giampaolo/psutil/issues/1000 +.. _1001: https://github.com/giampaolo/psutil/issues/1001 +.. _1002: https://github.com/giampaolo/psutil/issues/1002 +.. _1003: https://github.com/giampaolo/psutil/issues/1003 +.. _1004: https://github.com/giampaolo/psutil/issues/1004 +.. _1005: https://github.com/giampaolo/psutil/issues/1005 +.. _1006: https://github.com/giampaolo/psutil/issues/1006 +.. _1007: https://github.com/giampaolo/psutil/issues/1007 +.. _1008: https://github.com/giampaolo/psutil/issues/1008 +.. _1009: https://github.com/giampaolo/psutil/issues/1009 +.. _1010: https://github.com/giampaolo/psutil/issues/1010 +.. _1011: https://github.com/giampaolo/psutil/issues/1011 +.. _1012: https://github.com/giampaolo/psutil/issues/1012 +.. _1013: https://github.com/giampaolo/psutil/issues/1013 +.. _1014: https://github.com/giampaolo/psutil/issues/1014 +.. _1015: https://github.com/giampaolo/psutil/issues/1015 +.. _1016: https://github.com/giampaolo/psutil/issues/1016 +.. _1017: https://github.com/giampaolo/psutil/issues/1017 +.. _1018: https://github.com/giampaolo/psutil/issues/1018 +.. _1019: https://github.com/giampaolo/psutil/issues/1019 +.. _1020: https://github.com/giampaolo/psutil/issues/1020 +.. _1021: https://github.com/giampaolo/psutil/issues/1021 +.. _1022: https://github.com/giampaolo/psutil/issues/1022 +.. _1023: https://github.com/giampaolo/psutil/issues/1023 +.. _1024: https://github.com/giampaolo/psutil/issues/1024 +.. _1025: https://github.com/giampaolo/psutil/issues/1025 +.. _1026: https://github.com/giampaolo/psutil/issues/1026 +.. _1027: https://github.com/giampaolo/psutil/issues/1027 +.. _1028: https://github.com/giampaolo/psutil/issues/1028 +.. _1029: https://github.com/giampaolo/psutil/issues/1029 +.. _1030: https://github.com/giampaolo/psutil/issues/1030 +.. _1031: https://github.com/giampaolo/psutil/issues/1031 +.. _1032: https://github.com/giampaolo/psutil/issues/1032 +.. _1033: https://github.com/giampaolo/psutil/issues/1033 +.. _1034: https://github.com/giampaolo/psutil/issues/1034 +.. _1035: https://github.com/giampaolo/psutil/issues/1035 +.. _1036: https://github.com/giampaolo/psutil/issues/1036 +.. _1037: https://github.com/giampaolo/psutil/issues/1037 +.. _1038: https://github.com/giampaolo/psutil/issues/1038 +.. _1039: https://github.com/giampaolo/psutil/issues/1039 +.. _1040: https://github.com/giampaolo/psutil/issues/1040 +.. _1041: https://github.com/giampaolo/psutil/issues/1041 +.. _1042: https://github.com/giampaolo/psutil/issues/1042 +.. _1043: https://github.com/giampaolo/psutil/issues/1043 +.. _1044: https://github.com/giampaolo/psutil/issues/1044 +.. _1045: https://github.com/giampaolo/psutil/issues/1045 +.. _1046: https://github.com/giampaolo/psutil/issues/1046 +.. _1047: https://github.com/giampaolo/psutil/issues/1047 +.. _1048: https://github.com/giampaolo/psutil/issues/1048 +.. _1049: https://github.com/giampaolo/psutil/issues/1049 +.. _1050: https://github.com/giampaolo/psutil/issues/1050 +.. _1051: https://github.com/giampaolo/psutil/issues/1051 +.. _1052: https://github.com/giampaolo/psutil/issues/1052 +.. _1053: https://github.com/giampaolo/psutil/issues/1053 +.. _1054: https://github.com/giampaolo/psutil/issues/1054 +.. _1055: https://github.com/giampaolo/psutil/issues/1055 +.. _1056: https://github.com/giampaolo/psutil/issues/1056 +.. _1057: https://github.com/giampaolo/psutil/issues/1057 +.. _1058: https://github.com/giampaolo/psutil/issues/1058 +.. _1059: https://github.com/giampaolo/psutil/issues/1059 +.. _1060: https://github.com/giampaolo/psutil/issues/1060 +.. _1061: https://github.com/giampaolo/psutil/issues/1061 +.. _1062: https://github.com/giampaolo/psutil/issues/1062 +.. _1063: https://github.com/giampaolo/psutil/issues/1063 +.. _1064: https://github.com/giampaolo/psutil/issues/1064 +.. _1065: https://github.com/giampaolo/psutil/issues/1065 +.. _1066: https://github.com/giampaolo/psutil/issues/1066 +.. _1067: https://github.com/giampaolo/psutil/issues/1067 +.. _1068: https://github.com/giampaolo/psutil/issues/1068 +.. _1069: https://github.com/giampaolo/psutil/issues/1069 +.. _1070: https://github.com/giampaolo/psutil/issues/1070 +.. _1071: https://github.com/giampaolo/psutil/issues/1071 +.. _1072: https://github.com/giampaolo/psutil/issues/1072 +.. _1073: https://github.com/giampaolo/psutil/issues/1073 +.. _1074: https://github.com/giampaolo/psutil/issues/1074 +.. _1075: https://github.com/giampaolo/psutil/issues/1075 +.. _1076: https://github.com/giampaolo/psutil/issues/1076 +.. _1077: https://github.com/giampaolo/psutil/issues/1077 +.. _1078: https://github.com/giampaolo/psutil/issues/1078 +.. _1079: https://github.com/giampaolo/psutil/issues/1079 +.. _1080: https://github.com/giampaolo/psutil/issues/1080 +.. _1081: https://github.com/giampaolo/psutil/issues/1081 +.. _1082: https://github.com/giampaolo/psutil/issues/1082 +.. _1083: https://github.com/giampaolo/psutil/issues/1083 +.. _1084: https://github.com/giampaolo/psutil/issues/1084 +.. _1085: https://github.com/giampaolo/psutil/issues/1085 +.. _1086: https://github.com/giampaolo/psutil/issues/1086 +.. _1087: https://github.com/giampaolo/psutil/issues/1087 +.. _1088: https://github.com/giampaolo/psutil/issues/1088 +.. _1089: https://github.com/giampaolo/psutil/issues/1089 +.. _1090: https://github.com/giampaolo/psutil/issues/1090 +.. _1091: https://github.com/giampaolo/psutil/issues/1091 +.. _1092: https://github.com/giampaolo/psutil/issues/1092 +.. _1093: https://github.com/giampaolo/psutil/issues/1093 +.. _1094: https://github.com/giampaolo/psutil/issues/1094 +.. _1095: https://github.com/giampaolo/psutil/issues/1095 +.. _1096: https://github.com/giampaolo/psutil/issues/1096 +.. _1097: https://github.com/giampaolo/psutil/issues/1097 +.. _1098: https://github.com/giampaolo/psutil/issues/1098 +.. _1099: https://github.com/giampaolo/psutil/issues/1099 +.. _1100: https://github.com/giampaolo/psutil/issues/1100 +.. _1101: https://github.com/giampaolo/psutil/issues/1101 +.. _1102: https://github.com/giampaolo/psutil/issues/1102 +.. _1103: https://github.com/giampaolo/psutil/issues/1103 +.. _1104: https://github.com/giampaolo/psutil/issues/1104 +.. _1105: https://github.com/giampaolo/psutil/issues/1105 +.. _1106: https://github.com/giampaolo/psutil/issues/1106 +.. _1107: https://github.com/giampaolo/psutil/issues/1107 +.. _1108: https://github.com/giampaolo/psutil/issues/1108 +.. _1109: https://github.com/giampaolo/psutil/issues/1109 +.. _1110: https://github.com/giampaolo/psutil/issues/1110 +.. _1111: https://github.com/giampaolo/psutil/issues/1111 +.. _1112: https://github.com/giampaolo/psutil/issues/1112 +.. _1113: https://github.com/giampaolo/psutil/issues/1113 +.. _1114: https://github.com/giampaolo/psutil/issues/1114 +.. _1115: https://github.com/giampaolo/psutil/issues/1115 +.. _1116: https://github.com/giampaolo/psutil/issues/1116 +.. _1117: https://github.com/giampaolo/psutil/issues/1117 +.. _1118: https://github.com/giampaolo/psutil/issues/1118 +.. _1119: https://github.com/giampaolo/psutil/issues/1119 +.. _1120: https://github.com/giampaolo/psutil/issues/1120 +.. _1121: https://github.com/giampaolo/psutil/issues/1121 +.. _1122: https://github.com/giampaolo/psutil/issues/1122 +.. _1123: https://github.com/giampaolo/psutil/issues/1123 +.. _1124: https://github.com/giampaolo/psutil/issues/1124 +.. _1125: https://github.com/giampaolo/psutil/issues/1125 +.. _1126: https://github.com/giampaolo/psutil/issues/1126 +.. _1127: https://github.com/giampaolo/psutil/issues/1127 +.. _1128: https://github.com/giampaolo/psutil/issues/1128 +.. _1129: https://github.com/giampaolo/psutil/issues/1129 +.. _1130: https://github.com/giampaolo/psutil/issues/1130 +.. _1131: https://github.com/giampaolo/psutil/issues/1131 +.. _1132: https://github.com/giampaolo/psutil/issues/1132 +.. _1133: https://github.com/giampaolo/psutil/issues/1133 +.. _1134: https://github.com/giampaolo/psutil/issues/1134 +.. _1135: https://github.com/giampaolo/psutil/issues/1135 +.. _1136: https://github.com/giampaolo/psutil/issues/1136 +.. _1137: https://github.com/giampaolo/psutil/issues/1137 +.. _1138: https://github.com/giampaolo/psutil/issues/1138 +.. _1139: https://github.com/giampaolo/psutil/issues/1139 +.. _1140: https://github.com/giampaolo/psutil/issues/1140 +.. _1141: https://github.com/giampaolo/psutil/issues/1141 +.. _1142: https://github.com/giampaolo/psutil/issues/1142 +.. _1143: https://github.com/giampaolo/psutil/issues/1143 +.. _1144: https://github.com/giampaolo/psutil/issues/1144 +.. _1145: https://github.com/giampaolo/psutil/issues/1145 +.. _1146: https://github.com/giampaolo/psutil/issues/1146 +.. _1147: https://github.com/giampaolo/psutil/issues/1147 +.. _1148: https://github.com/giampaolo/psutil/issues/1148 +.. _1149: https://github.com/giampaolo/psutil/issues/1149 +.. _1150: https://github.com/giampaolo/psutil/issues/1150 +.. _1151: https://github.com/giampaolo/psutil/issues/1151 +.. _1152: https://github.com/giampaolo/psutil/issues/1152 +.. _1153: https://github.com/giampaolo/psutil/issues/1153 +.. _1154: https://github.com/giampaolo/psutil/issues/1154 +.. _1155: https://github.com/giampaolo/psutil/issues/1155 +.. _1156: https://github.com/giampaolo/psutil/issues/1156 +.. _1157: https://github.com/giampaolo/psutil/issues/1157 +.. _1158: https://github.com/giampaolo/psutil/issues/1158 +.. _1159: https://github.com/giampaolo/psutil/issues/1159 +.. _1160: https://github.com/giampaolo/psutil/issues/1160 +.. _1161: https://github.com/giampaolo/psutil/issues/1161 +.. _1162: https://github.com/giampaolo/psutil/issues/1162 +.. _1163: https://github.com/giampaolo/psutil/issues/1163 +.. _1164: https://github.com/giampaolo/psutil/issues/1164 +.. _1165: https://github.com/giampaolo/psutil/issues/1165 +.. _1166: https://github.com/giampaolo/psutil/issues/1166 +.. _1167: https://github.com/giampaolo/psutil/issues/1167 +.. _1168: https://github.com/giampaolo/psutil/issues/1168 +.. _1169: https://github.com/giampaolo/psutil/issues/1169 +.. _1170: https://github.com/giampaolo/psutil/issues/1170 +.. _1171: https://github.com/giampaolo/psutil/issues/1171 +.. _1172: https://github.com/giampaolo/psutil/issues/1172 +.. _1173: https://github.com/giampaolo/psutil/issues/1173 +.. _1174: https://github.com/giampaolo/psutil/issues/1174 +.. _1175: https://github.com/giampaolo/psutil/issues/1175 +.. _1176: https://github.com/giampaolo/psutil/issues/1176 +.. _1177: https://github.com/giampaolo/psutil/issues/1177 +.. _1178: https://github.com/giampaolo/psutil/issues/1178 +.. _1179: https://github.com/giampaolo/psutil/issues/1179 +.. _1180: https://github.com/giampaolo/psutil/issues/1180 +.. _1181: https://github.com/giampaolo/psutil/issues/1181 +.. _1182: https://github.com/giampaolo/psutil/issues/1182 +.. _1183: https://github.com/giampaolo/psutil/issues/1183 +.. _1184: https://github.com/giampaolo/psutil/issues/1184 +.. _1185: https://github.com/giampaolo/psutil/issues/1185 +.. _1186: https://github.com/giampaolo/psutil/issues/1186 +.. _1187: https://github.com/giampaolo/psutil/issues/1187 +.. _1188: https://github.com/giampaolo/psutil/issues/1188 +.. _1189: https://github.com/giampaolo/psutil/issues/1189 +.. _1190: https://github.com/giampaolo/psutil/issues/1190 +.. _1191: https://github.com/giampaolo/psutil/issues/1191 +.. _1192: https://github.com/giampaolo/psutil/issues/1192 +.. _1193: https://github.com/giampaolo/psutil/issues/1193 +.. _1194: https://github.com/giampaolo/psutil/issues/1194 +.. _1195: https://github.com/giampaolo/psutil/issues/1195 +.. _1196: https://github.com/giampaolo/psutil/issues/1196 +.. _1197: https://github.com/giampaolo/psutil/issues/1197 +.. _1198: https://github.com/giampaolo/psutil/issues/1198 +.. _1199: https://github.com/giampaolo/psutil/issues/1199 +.. _1200: https://github.com/giampaolo/psutil/issues/1200 +.. _1201: https://github.com/giampaolo/psutil/issues/1201 +.. _1202: https://github.com/giampaolo/psutil/issues/1202 +.. _1203: https://github.com/giampaolo/psutil/issues/1203 +.. _1204: https://github.com/giampaolo/psutil/issues/1204 +.. _1205: https://github.com/giampaolo/psutil/issues/1205 +.. _1206: https://github.com/giampaolo/psutil/issues/1206 +.. _1207: https://github.com/giampaolo/psutil/issues/1207 +.. _1208: https://github.com/giampaolo/psutil/issues/1208 +.. _1209: https://github.com/giampaolo/psutil/issues/1209 +.. _1210: https://github.com/giampaolo/psutil/issues/1210 +.. _1211: https://github.com/giampaolo/psutil/issues/1211 +.. _1212: https://github.com/giampaolo/psutil/issues/1212 +.. _1213: https://github.com/giampaolo/psutil/issues/1213 +.. _1214: https://github.com/giampaolo/psutil/issues/1214 +.. _1215: https://github.com/giampaolo/psutil/issues/1215 +.. _1216: https://github.com/giampaolo/psutil/issues/1216 +.. _1217: https://github.com/giampaolo/psutil/issues/1217 +.. _1218: https://github.com/giampaolo/psutil/issues/1218 +.. _1219: https://github.com/giampaolo/psutil/issues/1219 +.. _1220: https://github.com/giampaolo/psutil/issues/1220 +.. _1221: https://github.com/giampaolo/psutil/issues/1221 +.. _1222: https://github.com/giampaolo/psutil/issues/1222 +.. _1223: https://github.com/giampaolo/psutil/issues/1223 +.. _1224: https://github.com/giampaolo/psutil/issues/1224 +.. _1225: https://github.com/giampaolo/psutil/issues/1225 +.. _1226: https://github.com/giampaolo/psutil/issues/1226 +.. _1227: https://github.com/giampaolo/psutil/issues/1227 +.. _1228: https://github.com/giampaolo/psutil/issues/1228 +.. _1229: https://github.com/giampaolo/psutil/issues/1229 +.. _1230: https://github.com/giampaolo/psutil/issues/1230 +.. _1231: https://github.com/giampaolo/psutil/issues/1231 +.. _1232: https://github.com/giampaolo/psutil/issues/1232 +.. _1233: https://github.com/giampaolo/psutil/issues/1233 +.. _1234: https://github.com/giampaolo/psutil/issues/1234 +.. _1235: https://github.com/giampaolo/psutil/issues/1235 +.. _1236: https://github.com/giampaolo/psutil/issues/1236 +.. _1237: https://github.com/giampaolo/psutil/issues/1237 +.. _1238: https://github.com/giampaolo/psutil/issues/1238 +.. _1239: https://github.com/giampaolo/psutil/issues/1239 +.. _1240: https://github.com/giampaolo/psutil/issues/1240 +.. _1241: https://github.com/giampaolo/psutil/issues/1241 +.. _1242: https://github.com/giampaolo/psutil/issues/1242 +.. _1243: https://github.com/giampaolo/psutil/issues/1243 +.. _1244: https://github.com/giampaolo/psutil/issues/1244 +.. _1245: https://github.com/giampaolo/psutil/issues/1245 +.. _1246: https://github.com/giampaolo/psutil/issues/1246 +.. _1247: https://github.com/giampaolo/psutil/issues/1247 +.. _1248: https://github.com/giampaolo/psutil/issues/1248 +.. _1249: https://github.com/giampaolo/psutil/issues/1249 +.. _1250: https://github.com/giampaolo/psutil/issues/1250 +.. _1251: https://github.com/giampaolo/psutil/issues/1251 +.. _1252: https://github.com/giampaolo/psutil/issues/1252 +.. _1253: https://github.com/giampaolo/psutil/issues/1253 +.. _1254: https://github.com/giampaolo/psutil/issues/1254 +.. _1255: https://github.com/giampaolo/psutil/issues/1255 +.. _1256: https://github.com/giampaolo/psutil/issues/1256 +.. _1257: https://github.com/giampaolo/psutil/issues/1257 +.. _1258: https://github.com/giampaolo/psutil/issues/1258 +.. _1259: https://github.com/giampaolo/psutil/issues/1259 +.. _1260: https://github.com/giampaolo/psutil/issues/1260 +.. _1261: https://github.com/giampaolo/psutil/issues/1261 +.. _1262: https://github.com/giampaolo/psutil/issues/1262 +.. _1263: https://github.com/giampaolo/psutil/issues/1263 +.. _1264: https://github.com/giampaolo/psutil/issues/1264 +.. _1265: https://github.com/giampaolo/psutil/issues/1265 +.. _1266: https://github.com/giampaolo/psutil/issues/1266 +.. _1267: https://github.com/giampaolo/psutil/issues/1267 +.. _1268: https://github.com/giampaolo/psutil/issues/1268 +.. _1269: https://github.com/giampaolo/psutil/issues/1269 +.. _1270: https://github.com/giampaolo/psutil/issues/1270 +.. _1271: https://github.com/giampaolo/psutil/issues/1271 +.. _1272: https://github.com/giampaolo/psutil/issues/1272 +.. _1273: https://github.com/giampaolo/psutil/issues/1273 +.. _1274: https://github.com/giampaolo/psutil/issues/1274 +.. _1275: https://github.com/giampaolo/psutil/issues/1275 +.. _1276: https://github.com/giampaolo/psutil/issues/1276 +.. _1277: https://github.com/giampaolo/psutil/issues/1277 +.. _1278: https://github.com/giampaolo/psutil/issues/1278 +.. _1279: https://github.com/giampaolo/psutil/issues/1279 +.. _1280: https://github.com/giampaolo/psutil/issues/1280 +.. _1281: https://github.com/giampaolo/psutil/issues/1281 +.. _1282: https://github.com/giampaolo/psutil/issues/1282 +.. _1283: https://github.com/giampaolo/psutil/issues/1283 +.. _1284: https://github.com/giampaolo/psutil/issues/1284 +.. _1285: https://github.com/giampaolo/psutil/issues/1285 +.. _1286: https://github.com/giampaolo/psutil/issues/1286 +.. _1287: https://github.com/giampaolo/psutil/issues/1287 +.. _1288: https://github.com/giampaolo/psutil/issues/1288 +.. _1289: https://github.com/giampaolo/psutil/issues/1289 +.. _1290: https://github.com/giampaolo/psutil/issues/1290 +.. _1291: https://github.com/giampaolo/psutil/issues/1291 +.. _1292: https://github.com/giampaolo/psutil/issues/1292 +.. _1293: https://github.com/giampaolo/psutil/issues/1293 +.. _1294: https://github.com/giampaolo/psutil/issues/1294 +.. _1295: https://github.com/giampaolo/psutil/issues/1295 +.. _1296: https://github.com/giampaolo/psutil/issues/1296 +.. _1297: https://github.com/giampaolo/psutil/issues/1297 +.. _1298: https://github.com/giampaolo/psutil/issues/1298 +.. _1299: https://github.com/giampaolo/psutil/issues/1299 +.. _1300: https://github.com/giampaolo/psutil/issues/1300 +.. _1301: https://github.com/giampaolo/psutil/issues/1301 +.. _1302: https://github.com/giampaolo/psutil/issues/1302 +.. _1303: https://github.com/giampaolo/psutil/issues/1303 +.. _1304: https://github.com/giampaolo/psutil/issues/1304 +.. _1305: https://github.com/giampaolo/psutil/issues/1305 +.. _1306: https://github.com/giampaolo/psutil/issues/1306 +.. _1307: https://github.com/giampaolo/psutil/issues/1307 +.. _1308: https://github.com/giampaolo/psutil/issues/1308 +.. _1309: https://github.com/giampaolo/psutil/issues/1309 +.. _1310: https://github.com/giampaolo/psutil/issues/1310 +.. _1311: https://github.com/giampaolo/psutil/issues/1311 +.. _1312: https://github.com/giampaolo/psutil/issues/1312 +.. _1313: https://github.com/giampaolo/psutil/issues/1313 +.. _1314: https://github.com/giampaolo/psutil/issues/1314 +.. _1315: https://github.com/giampaolo/psutil/issues/1315 +.. _1316: https://github.com/giampaolo/psutil/issues/1316 +.. _1317: https://github.com/giampaolo/psutil/issues/1317 +.. _1318: https://github.com/giampaolo/psutil/issues/1318 +.. _1319: https://github.com/giampaolo/psutil/issues/1319 +.. _1320: https://github.com/giampaolo/psutil/issues/1320 +.. _1321: https://github.com/giampaolo/psutil/issues/1321 +.. _1322: https://github.com/giampaolo/psutil/issues/1322 +.. _1323: https://github.com/giampaolo/psutil/issues/1323 +.. _1324: https://github.com/giampaolo/psutil/issues/1324 +.. _1325: https://github.com/giampaolo/psutil/issues/1325 +.. _1326: https://github.com/giampaolo/psutil/issues/1326 +.. _1327: https://github.com/giampaolo/psutil/issues/1327 +.. _1328: https://github.com/giampaolo/psutil/issues/1328 +.. _1329: https://github.com/giampaolo/psutil/issues/1329 +.. _1330: https://github.com/giampaolo/psutil/issues/1330 +.. _1331: https://github.com/giampaolo/psutil/issues/1331 +.. _1332: https://github.com/giampaolo/psutil/issues/1332 +.. _1333: https://github.com/giampaolo/psutil/issues/1333 +.. _1334: https://github.com/giampaolo/psutil/issues/1334 +.. _1335: https://github.com/giampaolo/psutil/issues/1335 +.. _1336: https://github.com/giampaolo/psutil/issues/1336 +.. _1337: https://github.com/giampaolo/psutil/issues/1337 +.. _1338: https://github.com/giampaolo/psutil/issues/1338 +.. _1339: https://github.com/giampaolo/psutil/issues/1339 +.. _1340: https://github.com/giampaolo/psutil/issues/1340 +.. _1341: https://github.com/giampaolo/psutil/issues/1341 +.. _1342: https://github.com/giampaolo/psutil/issues/1342 +.. _1343: https://github.com/giampaolo/psutil/issues/1343 +.. _1344: https://github.com/giampaolo/psutil/issues/1344 +.. _1345: https://github.com/giampaolo/psutil/issues/1345 +.. _1346: https://github.com/giampaolo/psutil/issues/1346 +.. _1347: https://github.com/giampaolo/psutil/issues/1347 +.. _1348: https://github.com/giampaolo/psutil/issues/1348 +.. _1349: https://github.com/giampaolo/psutil/issues/1349 +.. _1350: https://github.com/giampaolo/psutil/issues/1350 +.. _1351: https://github.com/giampaolo/psutil/issues/1351 +.. _1352: https://github.com/giampaolo/psutil/issues/1352 +.. _1353: https://github.com/giampaolo/psutil/issues/1353 +.. _1354: https://github.com/giampaolo/psutil/issues/1354 +.. _1355: https://github.com/giampaolo/psutil/issues/1355 +.. _1356: https://github.com/giampaolo/psutil/issues/1356 +.. _1357: https://github.com/giampaolo/psutil/issues/1357 +.. _1358: https://github.com/giampaolo/psutil/issues/1358 +.. _1359: https://github.com/giampaolo/psutil/issues/1359 +.. _1360: https://github.com/giampaolo/psutil/issues/1360 +.. _1361: https://github.com/giampaolo/psutil/issues/1361 +.. _1362: https://github.com/giampaolo/psutil/issues/1362 +.. _1363: https://github.com/giampaolo/psutil/issues/1363 +.. _1364: https://github.com/giampaolo/psutil/issues/1364 +.. _1365: https://github.com/giampaolo/psutil/issues/1365 +.. _1366: https://github.com/giampaolo/psutil/issues/1366 +.. _1367: https://github.com/giampaolo/psutil/issues/1367 +.. _1368: https://github.com/giampaolo/psutil/issues/1368 +.. _1369: https://github.com/giampaolo/psutil/issues/1369 +.. _1370: https://github.com/giampaolo/psutil/issues/1370 +.. _1371: https://github.com/giampaolo/psutil/issues/1371 +.. _1372: https://github.com/giampaolo/psutil/issues/1372 +.. _1373: https://github.com/giampaolo/psutil/issues/1373 +.. _1374: https://github.com/giampaolo/psutil/issues/1374 +.. _1375: https://github.com/giampaolo/psutil/issues/1375 +.. _1376: https://github.com/giampaolo/psutil/issues/1376 +.. _1377: https://github.com/giampaolo/psutil/issues/1377 +.. _1378: https://github.com/giampaolo/psutil/issues/1378 +.. _1379: https://github.com/giampaolo/psutil/issues/1379 +.. _1380: https://github.com/giampaolo/psutil/issues/1380 +.. _1381: https://github.com/giampaolo/psutil/issues/1381 +.. _1382: https://github.com/giampaolo/psutil/issues/1382 +.. _1383: https://github.com/giampaolo/psutil/issues/1383 +.. _1384: https://github.com/giampaolo/psutil/issues/1384 +.. _1385: https://github.com/giampaolo/psutil/issues/1385 +.. _1386: https://github.com/giampaolo/psutil/issues/1386 +.. _1387: https://github.com/giampaolo/psutil/issues/1387 +.. _1388: https://github.com/giampaolo/psutil/issues/1388 +.. _1389: https://github.com/giampaolo/psutil/issues/1389 +.. _1390: https://github.com/giampaolo/psutil/issues/1390 +.. _1391: https://github.com/giampaolo/psutil/issues/1391 +.. _1392: https://github.com/giampaolo/psutil/issues/1392 +.. _1393: https://github.com/giampaolo/psutil/issues/1393 +.. _1394: https://github.com/giampaolo/psutil/issues/1394 +.. _1395: https://github.com/giampaolo/psutil/issues/1395 +.. _1396: https://github.com/giampaolo/psutil/issues/1396 +.. _1397: https://github.com/giampaolo/psutil/issues/1397 +.. _1398: https://github.com/giampaolo/psutil/issues/1398 +.. _1399: https://github.com/giampaolo/psutil/issues/1399 +.. _1400: https://github.com/giampaolo/psutil/issues/1400 +.. _1401: https://github.com/giampaolo/psutil/issues/1401 +.. _1402: https://github.com/giampaolo/psutil/issues/1402 +.. _1403: https://github.com/giampaolo/psutil/issues/1403 +.. _1404: https://github.com/giampaolo/psutil/issues/1404 +.. _1405: https://github.com/giampaolo/psutil/issues/1405 +.. _1406: https://github.com/giampaolo/psutil/issues/1406 +.. _1407: https://github.com/giampaolo/psutil/issues/1407 +.. _1408: https://github.com/giampaolo/psutil/issues/1408 +.. _1409: https://github.com/giampaolo/psutil/issues/1409 +.. _1410: https://github.com/giampaolo/psutil/issues/1410 +.. _1411: https://github.com/giampaolo/psutil/issues/1411 +.. _1412: https://github.com/giampaolo/psutil/issues/1412 +.. _1413: https://github.com/giampaolo/psutil/issues/1413 +.. _1414: https://github.com/giampaolo/psutil/issues/1414 +.. _1415: https://github.com/giampaolo/psutil/issues/1415 +.. _1416: https://github.com/giampaolo/psutil/issues/1416 +.. _1417: https://github.com/giampaolo/psutil/issues/1417 +.. _1418: https://github.com/giampaolo/psutil/issues/1418 +.. _1419: https://github.com/giampaolo/psutil/issues/1419 +.. _1420: https://github.com/giampaolo/psutil/issues/1420 +.. _1421: https://github.com/giampaolo/psutil/issues/1421 +.. _1422: https://github.com/giampaolo/psutil/issues/1422 +.. _1423: https://github.com/giampaolo/psutil/issues/1423 +.. _1424: https://github.com/giampaolo/psutil/issues/1424 +.. _1425: https://github.com/giampaolo/psutil/issues/1425 +.. _1426: https://github.com/giampaolo/psutil/issues/1426 +.. _1427: https://github.com/giampaolo/psutil/issues/1427 +.. _1428: https://github.com/giampaolo/psutil/issues/1428 +.. _1429: https://github.com/giampaolo/psutil/issues/1429 +.. _1430: https://github.com/giampaolo/psutil/issues/1430 +.. _1431: https://github.com/giampaolo/psutil/issues/1431 +.. _1432: https://github.com/giampaolo/psutil/issues/1432 +.. _1433: https://github.com/giampaolo/psutil/issues/1433 +.. _1434: https://github.com/giampaolo/psutil/issues/1434 +.. _1435: https://github.com/giampaolo/psutil/issues/1435 +.. _1436: https://github.com/giampaolo/psutil/issues/1436 +.. _1437: https://github.com/giampaolo/psutil/issues/1437 +.. _1438: https://github.com/giampaolo/psutil/issues/1438 +.. _1439: https://github.com/giampaolo/psutil/issues/1439 +.. _1440: https://github.com/giampaolo/psutil/issues/1440 +.. _1441: https://github.com/giampaolo/psutil/issues/1441 +.. _1442: https://github.com/giampaolo/psutil/issues/1442 +.. _1443: https://github.com/giampaolo/psutil/issues/1443 +.. _1444: https://github.com/giampaolo/psutil/issues/1444 +.. _1445: https://github.com/giampaolo/psutil/issues/1445 +.. _1446: https://github.com/giampaolo/psutil/issues/1446 +.. _1447: https://github.com/giampaolo/psutil/issues/1447 +.. _1448: https://github.com/giampaolo/psutil/issues/1448 +.. _1449: https://github.com/giampaolo/psutil/issues/1449 +.. _1450: https://github.com/giampaolo/psutil/issues/1450 +.. _1451: https://github.com/giampaolo/psutil/issues/1451 +.. _1452: https://github.com/giampaolo/psutil/issues/1452 +.. _1453: https://github.com/giampaolo/psutil/issues/1453 +.. _1454: https://github.com/giampaolo/psutil/issues/1454 +.. _1455: https://github.com/giampaolo/psutil/issues/1455 +.. _1456: https://github.com/giampaolo/psutil/issues/1456 +.. _1457: https://github.com/giampaolo/psutil/issues/1457 +.. _1458: https://github.com/giampaolo/psutil/issues/1458 +.. _1459: https://github.com/giampaolo/psutil/issues/1459 +.. _1460: https://github.com/giampaolo/psutil/issues/1460 +.. _1461: https://github.com/giampaolo/psutil/issues/1461 +.. _1462: https://github.com/giampaolo/psutil/issues/1462 +.. _1463: https://github.com/giampaolo/psutil/issues/1463 +.. _1464: https://github.com/giampaolo/psutil/issues/1464 +.. _1465: https://github.com/giampaolo/psutil/issues/1465 +.. _1466: https://github.com/giampaolo/psutil/issues/1466 +.. _1467: https://github.com/giampaolo/psutil/issues/1467 +.. _1468: https://github.com/giampaolo/psutil/issues/1468 +.. _1469: https://github.com/giampaolo/psutil/issues/1469 +.. _1470: https://github.com/giampaolo/psutil/issues/1470 +.. _1471: https://github.com/giampaolo/psutil/issues/1471 +.. _1472: https://github.com/giampaolo/psutil/issues/1472 +.. _1473: https://github.com/giampaolo/psutil/issues/1473 +.. _1474: https://github.com/giampaolo/psutil/issues/1474 +.. _1475: https://github.com/giampaolo/psutil/issues/1475 +.. _1476: https://github.com/giampaolo/psutil/issues/1476 +.. _1477: https://github.com/giampaolo/psutil/issues/1477 +.. _1478: https://github.com/giampaolo/psutil/issues/1478 +.. _1479: https://github.com/giampaolo/psutil/issues/1479 +.. _1480: https://github.com/giampaolo/psutil/issues/1480 +.. _1481: https://github.com/giampaolo/psutil/issues/1481 +.. _1482: https://github.com/giampaolo/psutil/issues/1482 +.. _1483: https://github.com/giampaolo/psutil/issues/1483 +.. _1484: https://github.com/giampaolo/psutil/issues/1484 +.. _1485: https://github.com/giampaolo/psutil/issues/1485 +.. _1486: https://github.com/giampaolo/psutil/issues/1486 +.. _1487: https://github.com/giampaolo/psutil/issues/1487 +.. _1488: https://github.com/giampaolo/psutil/issues/1488 +.. _1489: https://github.com/giampaolo/psutil/issues/1489 +.. _1490: https://github.com/giampaolo/psutil/issues/1490 +.. _1491: https://github.com/giampaolo/psutil/issues/1491 +.. _1492: https://github.com/giampaolo/psutil/issues/1492 +.. _1493: https://github.com/giampaolo/psutil/issues/1493 +.. _1494: https://github.com/giampaolo/psutil/issues/1494 +.. _1495: https://github.com/giampaolo/psutil/issues/1495 +.. _1496: https://github.com/giampaolo/psutil/issues/1496 +.. _1497: https://github.com/giampaolo/psutil/issues/1497 +.. _1498: https://github.com/giampaolo/psutil/issues/1498 +.. _1499: https://github.com/giampaolo/psutil/issues/1499 +.. _1500: https://github.com/giampaolo/psutil/issues/1500 diff --git a/IDEAS b/IDEAS index 015b5fffe..4932ad728 100644 --- a/IDEAS +++ b/IDEAS @@ -8,22 +8,18 @@ https://github.com/giampaolo/psutil/issues PLATFORMS ========= -- #355 (patch): Android -- #605 (branch): AIX +- #355: Android (with patch) +- #82: Cygwin (PR at #998) - #276: GNU/Hurd +- #693: Windows Nano - DragonFlyBSD - HP-UX - -APIS -==== - -- cpu_info() (#550) - - FEATURES ======== +- #371: sensors_temperatures() at least for OSX. + - #669: Windows / net_if_addrs(): return broadcast addr. - #550: CPU info (frequency, architecture, threads per core, cores per socket, @@ -50,9 +46,6 @@ FEATURES - (Linux) locked files via /proc/locks: https://www.centos.org/docs/5/html/5.2/Deployment_Guide/s2-proc-locks.html -- #371: CPU temperature (apparently OSX and Linux only; on Linux it requires - lm-sensors lib). - - #269: NIC rx/tx queue. This should probably go into net_if_stats(). Figure out on what platforms this is supported: Linux: yes @@ -79,8 +72,6 @@ FEATURES - Number of system threads. - Windows: http://msdn.microsoft.com/en-us/library/windows/desktop/ms684824(v=vs.85).aspx -- #357: what CPU a process is on. - - Doc / wiki which compares similarities between UNIX cli tools and psutil. Example: ``` @@ -155,21 +146,16 @@ FEATURES - #550: number of threads per core. -- Have psutil.Process().cpu_affinity([]) be an alias for "all CPUs"? - - BUGFIXES ======== - #600: windows / open_files(): support network file handles. - REJECTED ======== - #550: threads per core - RESOURCES ========= diff --git a/INSTALL.rst b/INSTALL.rst index b1b40d683..8d54d6ae1 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -1,18 +1,27 @@ -PIP -=== +Install pip +=========== pip is the easiest way to install psutil. -It is shipped by default with Python 2.7.9+ and 3.4+. If you're using an -older Python version `install pip `__ -first. -If you GIT cloned psutil source code you can also install pip with:: +It is shipped by default with Python 2.7.9+ and 3.4+. For other Python versions +you can install it manually. +On Linux or via wget: + +.. code-block:: bash + + wget https://bootstrap.pypa.io/get-pip.py -O - | python + +On OSX or via curl: + +.. code-block:: bash + + python < <(curl -s https://bootstrap.pypa.io/get-pip.py) - make install-pip +On Windows, `download pip `__, open +cmd.exe and install it: -Unless you're on Windows, in order to install psutil with pip you'll also need -a C compiler installed. -pip will retrieve psutil source code or binaries from -`PYPI `__ repository. +.. code-block:: bat + + C:\Python27\python.exe get-pip.py Permission issues (UNIX) ======================== @@ -22,50 +31,43 @@ If you're not or you bump into permission errors you can either: * prepend ``sudo``, e.g.: -:: +.. code-block:: bash sudo pip install psutil * install psutil for your user only (not at system level): -:: +.. code-block:: bash pip install --user psutil Linux ===== -Ubuntu / Debian:: +Ubuntu / Debian: + +.. code-block:: bash sudo apt-get install gcc python-dev python-pip pip install psutil -RedHat / CentOS:: +RedHat / CentOS: + + +.. code-block:: bash sudo yum install gcc python-devel python-pip pip install psutil If you're on Python 3 use ``python3-dev`` and ``python3-pip`` instead. -Major Linux distros also provide binary distributions of psutil so, for -instance, on Ubuntu and Debian you can also do:: - - sudo apt-get install python-psutil - -On RedHat and CentOS:: - - sudo yum install python-psutil - -This is not recommended though as Linux distros usually ship older psutil -versions. - OSX === -Install `XcodeTools `__ +Install `Xcode `__ first, then: -:: +.. code-block:: bash pip install psutil @@ -74,12 +76,17 @@ Windows The easiest way to install psutil on Windows is to just use the pre-compiled exe/wheel installers hosted on -`PYPI `__ via pip:: +`PYPI `__ via pip: + +.. code-block:: bat C:\Python27\python.exe -m pip install psutil If you want to compile psutil from sources you'll need **Visual Studio** -(Mingw32 is no longer supported): +(Mingw32 is no longer supported), which really is a mess. +The VS versions are the onle listed below. +This `blog post `__ +provides numerous info on how to properly set up VS (good luck with that). * Python 2.6, 2.7: `VS-2008 `__ * Python 3.3, 3.4: `VS-2010 `__ @@ -89,7 +96,9 @@ Compiling 64 bit versions of Python 2.6 and 2.7 with VS 2008 requires `Windows SDK and .NET Framework 3.5 SP1 `__. Once installed run vcvars64.bat, then you can finally compile (see `here `__). -To compile / install psutil from sources on Windows run:: +To compile / install psutil from sources on Windows run: + +.. code-block:: bat make.bat build make.bat install @@ -97,7 +106,7 @@ To compile / install psutil from sources on Windows run:: FreeBSD ======= -:: +.. code-block:: bash pkg install python gcc python -m pip install psutil @@ -105,7 +114,7 @@ FreeBSD OpenBSD ======= -:: +.. code-block:: bash export PKG_PATH="http://ftp.openbsd.org/pub/OpenBSD/`uname -r`/packages/`arch -s`/" pkg_add -v python gcc @@ -114,7 +123,7 @@ OpenBSD NetBSD ====== -:: +.. code-block:: bash export PKG_PATH="ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/`uname -m`/`uname -r`/All" pkg_add -v pkgin @@ -126,17 +135,27 @@ Solaris If ``cc`` compiler is not installed create a symlink to ``gcc``: -:: +.. code-block:: bash sudo ln -s /usr/bin/gcc /usr/local/bin/cc Install: -:: +.. code-block:: bash pkg install gcc python -m pip install psutil +Install from sources +==================== + +.. code-block:: bash + + git clone https://github.com/giampaolo/psutil.git + cd psutil + python setup.py install + + Dev Guide ========= diff --git a/MANIFEST.in b/MANIFEST.in index 945322928..7a92a4e5a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,131 @@ -include *.rst -include CREDITS* -include INSTALL* -include LICENSE* -include make.bar +include .coveragerc +include .git-pre-commit +include .gitignore +include CREDITS +include DEVGUIDE.rst +include HISTORY.rst +include IDEAS +include INSTALL.rst +include LICENSE +include MANIFEST.in include Makefile -recursive-include psutil *.py *.c *.h +include README.rst +include docs/Makefile +include docs/README +include docs/_static/copybutton.js +include docs/_static/css/custom.css +include docs/_static/favicon.ico +include docs/_static/sidebar.js +include docs/conf.py +include docs/index.rst +include docs/make.bat +include make.bat +include psutil/DEVNOTES +include psutil/__init__.py +include psutil/_common.py +include psutil/_compat.py +include psutil/_exceptions.py +include psutil/_psaix.py +include psutil/_psbsd.py +include psutil/_pslinux.py +include psutil/_psosx.py +include psutil/_psposix.py +include psutil/_pssunos.py +include psutil/_psutil_aix.c +include psutil/_psutil_bsd.c +include psutil/_psutil_common.c +include psutil/_psutil_common.h +include psutil/_psutil_linux.c +include psutil/_psutil_osx.c +include psutil/_psutil_posix.c +include psutil/_psutil_posix.h +include psutil/_psutil_sunos.c +include psutil/_psutil_windows.c +include psutil/_pswindows.py +include psutil/arch/aix/common.c +include psutil/arch/aix/common.h +include psutil/arch/aix/ifaddrs.c +include psutil/arch/aix/ifaddrs.h +include psutil/arch/aix/net_connections.c +include psutil/arch/aix/net_connections.h +include psutil/arch/aix/net_kernel_structs.h +include psutil/arch/freebsd/proc_socks.c +include psutil/arch/freebsd/proc_socks.h +include psutil/arch/freebsd/specific.c +include psutil/arch/freebsd/specific.h +include psutil/arch/freebsd/sys_socks.c +include psutil/arch/freebsd/sys_socks.h +include psutil/arch/netbsd/socks.c +include psutil/arch/netbsd/socks.h +include psutil/arch/netbsd/specific.c +include psutil/arch/netbsd/specific.h +include psutil/arch/openbsd/specific.c +include psutil/arch/openbsd/specific.h +include psutil/arch/osx/process_info.c +include psutil/arch/osx/process_info.h +include psutil/arch/solaris/environ.c +include psutil/arch/solaris/environ.h +include psutil/arch/solaris/v10/ifaddrs.c +include psutil/arch/solaris/v10/ifaddrs.h +include psutil/arch/windows/glpi.h +include psutil/arch/windows/inet_ntop.c +include psutil/arch/windows/inet_ntop.h +include psutil/arch/windows/ntextapi.h +include psutil/arch/windows/process_handles.c +include psutil/arch/windows/process_handles.h +include psutil/arch/windows/process_info.c +include psutil/arch/windows/process_info.h +include psutil/arch/windows/security.c +include psutil/arch/windows/security.h +include psutil/arch/windows/services.c +include psutil/arch/windows/services.h +include psutil/tests/README.rst +include psutil/tests/__init__.py +include psutil/tests/__main__.py +include psutil/tests/test_aix.py +include psutil/tests/test_bsd.py +include psutil/tests/test_connections.py +include psutil/tests/test_contracts.py +include psutil/tests/test_linux.py +include psutil/tests/test_memory_leaks.py +include psutil/tests/test_misc.py +include psutil/tests/test_osx.py +include psutil/tests/test_posix.py +include psutil/tests/test_process.py +include psutil/tests/test_sunos.py +include psutil/tests/test_system.py +include psutil/tests/test_unicode.py +include psutil/tests/test_windows.py +include scripts/battery.py +include scripts/cpu_distribution.py +include scripts/disk_usage.py +include scripts/fans.py +include scripts/free.py +include scripts/ifconfig.py +include scripts/internal/README +include scripts/internal/bench_oneshot.py +include scripts/internal/bench_oneshot_2.py +include scripts/internal/check_broken_links.py +include scripts/internal/download_exes.py +include scripts/internal/generate_manifest.py +include scripts/internal/print_announce.py +include scripts/internal/print_timeline.py +include scripts/internal/winmake.py +include scripts/iotop.py +include scripts/killall.py +include scripts/meminfo.py +include scripts/netstat.py +include scripts/nettop.py +include scripts/pidof.py +include scripts/pmap.py +include scripts/procinfo.py +include scripts/procsmem.py +include scripts/ps.py +include scripts/pstree.py +include scripts/sensors.py +include scripts/temperatures.py +include scripts/top.py +include scripts/who.py +include scripts/winservices.py +include setup.py +include tox.ini diff --git a/Makefile b/Makefile index c91d3b961..5081a4edd 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,12 @@ # You can set the variables below from the command line. PYTHON = python -TSCRIPT = psutil/tests/runner.py +TSCRIPT = psutil/tests/__main__.py ARGS = - # List of nice-to-have dev libs. -DEPS = argparse \ +DEPS = \ + argparse \ + check-manifest \ coverage \ flake8 \ futures \ @@ -19,13 +20,13 @@ DEPS = argparse \ requests \ setuptools \ sphinx \ - sphinx-pypi-upload \ twine \ - unittest2 + unittest2 \ + wheel # In not in a virtualenv, add --user options for install commands. INSTALL_OPTS = `$(PYTHON) -c "import sys; print('' if hasattr(sys, 'real_prefix') else '--user')"` - +TEST_PREFIX = PYTHONWARNINGS=all PSUTIL_TESTING=1 PSUTIL_DEBUG=1 all: test @@ -33,8 +34,7 @@ all: test # Install # =================================================================== -# Remove all build files. -clean: +clean: ## Remove all build files. rm -rf `find . -type d -name __pycache__ \ -o -type f -name \*.bak \ -o -type f -name \*.orig \ @@ -48,7 +48,7 @@ clean: rm -rf \ *.core \ *.egg-info \ - *\$testfile* \ + *\$testfn* \ .coverage \ .tox \ build/ \ @@ -59,31 +59,26 @@ clean: _: - -# Compile without installing. -build: _ - $(PYTHON) setup.py build +build: _ ## Compile without installing. + # make sure setuptools is installed (needed for 'develop' / edit mode) + $(PYTHON) -c "import setuptools" + PYTHONWARNINGS=all $(PYTHON) setup.py build @# copies compiled *.so files in ./psutil directory in order to allow @# "import psutil" when using the interactive interpreter from within @# this directory. - $(PYTHON) setup.py build_ext -i + PYTHONWARNINGS=all $(PYTHON) setup.py build_ext -i rm -rf tmp + $(PYTHON) -c "import psutil" # make sure it actually worked -# Install this package + GIT hooks. Install is done: -# - as the current user, in order to avoid permission issues -# - in development / edit mode, so that source can be modified on the fly -install: build - # make sure setuptools is installed (needed for 'develop' / edit mode) - $(PYTHON) -c "import setuptools" - $(PYTHON) setup.py develop $(INSTALL_OPTS) +install: ## Install this package as current user in "edit" mode. + ${MAKE} build + PYTHONWARNINGS=all $(PYTHON) setup.py develop $(INSTALL_OPTS) rm -rf tmp -# Uninstall this package via pip. -uninstall: +uninstall: ## Uninstall this package via pip. cd ..; $(PYTHON) -m pip uninstall -y -v psutil -# Install PIP (only if necessary). -install-pip: +install-pip: ## Install pip (no-op if already installed). $(PYTHON) -c \ "import sys, ssl, os, pkgutil, tempfile, atexit; \ sys.exit(0) if pkgutil.find_loader('pip') else None; \ @@ -102,12 +97,9 @@ install-pip: f.close(); \ sys.exit(code);" -# Install: -# - GIT hooks -# - pip (if necessary) -# - useful deps which are nice to have while developing / testing; -# deps these are also upgraded -setup-dev-env: install-git-hooks install-pip +setup-dev-env: ## Install GIT hooks, pip, test deps (also upgrades them). + ${MAKE} install-git-hooks + ${MAKE} install-pip $(PYTHON) -m pip install $(INSTALL_OPTS) --upgrade pip $(PYTHON) -m pip install $(INSTALL_OPTS) --upgrade $(DEPS) @@ -115,43 +107,55 @@ setup-dev-env: install-git-hooks install-pip # Tests # =================================================================== -# Run all tests. -test: install - $(PYTHON) $(TSCRIPT) +test: ## Run all tests. + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) + +test-process: ## Run process-related API tests. + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) -m unittest -v psutil.tests.test_process + +test-system: ## Run system-related API tests. + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) -m unittest -v psutil.tests.test_system -# Test psutil process-related APIs. -test-process: install - $(PYTHON) -m unittest -v psutil.tests.test_process +test-misc: ## Run miscellaneous tests. + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_misc.py -# Test psutil system-related APIs. -test-system: install - $(PYTHON) -m unittest -v psutil.tests.test_system +test-unicode: ## Test APIs dealing with strings. + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_unicode.py -# Test misc. -test-misc: install - $(PYTHON) psutil/tests/test_misc.py +test-contracts: ## APIs sanity tests. + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_contracts.py -# Test POSIX. -test-posix: install - $(PYTHON) psutil/tests/test_posix.py +test-connections: ## Test net_connections() and Process.connections(). + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_connections.py -# Test memory leaks. -test-memleaks: install - $(PYTHON) psutil/tests/test_memory_leaks.py +test-posix: ## POSIX specific tests. + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_posix.py -# Run specific platform tests only. -test-platform: install - $(PYTHON) psutil/tests/test_`$(PYTHON) -c 'import psutil; print([x.lower() for x in ("LINUX", "BSD", "OSX", "SUNOS", "WINDOWS") if getattr(psutil, x)][0])'`.py +test-platform: ## Run specific platform tests only. + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_`$(PYTHON) -c 'import psutil; print([x.lower() for x in ("LINUX", "BSD", "OSX", "SUNOS", "WINDOWS", "AIX") if getattr(psutil, x)][0])'`.py -# Run a specific test by name, e.g. -# make test-by-name psutil.tests.test_system.TestSystemAPIs.test_cpu_times -test-by-name: install - @$(PYTHON) -m unittest -v $(ARGS) +test-memleaks: ## Memory leak tests. + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_memory_leaks.py -coverage: install +test-by-name: ## e.g. make test-by-name ARGS=psutil.tests.test_system.TestSystemAPIs + ${MAKE} install + @$(TEST_PREFIX) $(PYTHON) -m unittest -v $(ARGS) + +test-coverage: ## Run test coverage. + ${MAKE} install # Note: coverage options are controlled by .coveragerc file rm -rf .coverage htmlcov - $(PYTHON) -m coverage run $(TSCRIPT) + $(TEST_PREFIX) $(PYTHON) -m coverage run $(TSCRIPT) $(PYTHON) -m coverage report @echo "writing results to htmlcov/index.html" $(PYTHON) -m coverage html @@ -161,27 +165,25 @@ coverage: install # Linters # =================================================================== -pep8: +pep8: ## PEP8 linter. @git ls-files | grep \\.py$ | xargs $(PYTHON) -m pep8 -pyflakes: +pyflakes: ## Pyflakes linter. @export PYFLAKES_NODOCTEST=1 && \ git ls-files | grep \\.py$ | xargs $(PYTHON) -m pyflakes -flake8: +flake8: ## flake8 linter. @git ls-files | grep \\.py$ | xargs $(PYTHON) -m flake8 # =================================================================== # GIT # =================================================================== -# git-tag a new release -git-tag-release: +git-tag-release: ## Git-tag a new release. git tag -a release-`python -c "import setup; print(setup.get_version())"` -m `git rev-list HEAD --count`:`git rev-parse --short HEAD` git push --follow-tags -# Install GIT pre-commit hook. -install-git-hooks: +install-git-hooks: ## Install GIT pre-commit hook. ln -sf ../../.git-pre-commit .git/hooks/pre-commit chmod +x .git/hooks/pre-commit @@ -189,63 +191,79 @@ install-git-hooks: # Distribution # =================================================================== -# Upload source tarball on https://pypi.python.org/pypi/psutil. -upload-src: clean +# --- create + +sdist: ## Create tar.gz source distribution. + ${MAKE} generate-manifest + $(PYTHON) setup.py sdist + +wheel: ## Generate wheel. + $(PYTHON) setup.py bdist_wheel + +win-download-wheels: ## Download wheels hosted on appveyor. + $(TEST_PREFIX) $(PYTHON) scripts/internal/download_exes.py --user giampaolo --project psutil + +# --- upload + +upload-src: ## Upload source tarball on https://pypi.python.org/pypi/psutil. + ${MAKE} sdist $(PYTHON) setup.py sdist upload -# Build and upload doc on https://pythonhosted.org/psutil/. -# Requires "pip install sphinx-pypi-upload". -upload-doc: - cd docs && make html - $(PYTHON) setup.py upload_sphinx --upload-dir=docs/_build/html - -# Download exes/wheels hosted on appveyor. -win-download-exes: - $(PYTHON) scripts/internal/download_exes.py --user giampaolo --project psutil - -# Upload exes/wheels in dist/* directory to PYPI. -win-upload-exes: - $(PYTHON) -m twine upload dist/*.exe - $(PYTHON) -m twine upload dist/*.wheel - -# All the necessary steps before making a release. -pre-release: - ${MAKE} clean - ${MAKE} install # to import psutil from download_exes.py +upload-win-wheels: ## Upload wheels in dist/* directory on PYPI. + $(PYTHON) -m twine upload dist/*.whl + +# --- others + +pre-release: ## Check if we're ready to produce a new release. + rm -rf dist + ${MAKE} install + ${MAKE} generate-manifest + git diff MANIFEST.in > /dev/null # ...otherwise 'git diff-index HEAD' will complain + ${MAKE} win-download-wheels + ${MAKE} sdist $(PYTHON) -c \ "from psutil import __version__ as ver; \ doc = open('docs/index.rst').read(); \ history = open('HISTORY.rst').read(); \ assert ver in doc, '%r not in docs/index.rst' % ver; \ assert ver in history, '%r not in HISTORY.rst' % ver; \ - assert 'XXXX' not in history; \ - " - ${MAKE} setup-dev-env # mainly to update sphinx and install twine - ${MAKE} win-download-exes - $(PYTHON) setup.py sdist + assert 'XXXX' not in history, 'XXXX in HISTORY.rst';" + $(PYTHON) -c "import subprocess, sys; out = subprocess.check_output('git diff --quiet && git diff --cached --quiet', shell=True).strip(); sys.exit('there are uncommitted changes:\n%s' % out) if out else 0 ;" -# Create a release: creates tar.gz and exes/wheels, uploads them, -# upload doc, git tag release. -release: +release: ## Create a release (down/uploads tar.gz, wheels, git tag release). ${MAKE} pre-release - $(PYTHON) -m twine upload dist/* # upload tar.gz, exes, wheels on PYPI + $(PYTHON) -m twine upload dist/* # upload tar.gz and Windows wheels on PYPI ${MAKE} git-tag-release -# Print announce of new release. -print-announce: - @$(PYTHON) scripts/internal/print_announce.py +print-announce: ## Print announce of new release. + @$(TEST_PREFIX) $(PYTHON) scripts/internal/print_announce.py + +print-timeline: ## Print releases' timeline. + @$(TEST_PREFIX) $(PYTHON) scripts/internal/print_timeline.py + +check-manifest: ## Inspect MANIFEST.in file. + $(PYTHON) -m check_manifest -v $(ARGS) + +generate-manifest: ## Generates MANIFEST.in file. + $(PYTHON) scripts/internal/generate_manifest.py > MANIFEST.in # =================================================================== # Misc # =================================================================== -grep-todos: +grep-todos: ## Look for TODOs in the source files. git grep -EIn "TODO|FIXME|XXX" -# run script which benchmarks oneshot() ctx manager (see #799) -bench-oneshot: install - $(PYTHON) scripts/internal/bench_oneshot.py +bench-oneshot: ## Benchmarks for oneshot() ctx manager (see #799). + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) scripts/internal/bench_oneshot.py + +bench-oneshot-2: ## Same as above but using perf module (supposed to be more precise) + ${MAKE} install + $(TEST_PREFIX) $(PYTHON) scripts/internal/bench_oneshot_2.py + +check-broken-links: ## Look for broken links in source files. + git ls-files | xargs $(PYTHON) -Wa scripts/internal/check_broken_links.py -# same as above but using perf module (supposed to be more precise) -bench-oneshot-2: install - $(PYTHON) scripts/internal/bench_oneshot_2.py +help: ## Display callable targets. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/README.rst b/README.rst index ecd642ac3..4ac707aba 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,11 @@ :target: https://coveralls.io/github/giampaolo/psutil?branch=master :alt: Test coverage (coverall.io) -.. image:: https://img.shields.io/pypi/v/psutil.svg?label=version +.. image:: https://readthedocs.org/projects/psutil/badge/?version=latest + :target: http://psutil.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +.. image:: https://img.shields.io/pypi/v/psutil.svg?label=pypi :target: https://pypi.python.org/pypi/psutil/ :alt: Latest version @@ -28,7 +32,7 @@ Quick links - `Home page `_ - `Install `_ -- `Documentation `_ +- `Documentation `_ - `Download `_ - `Forum `_ - `Blog `_ @@ -41,15 +45,23 @@ Summary psutil (process and system utilities) is a cross-platform library for retrieving information on **running processes** and **system utilization** -(CPU, memory, disks, network) in Python. It is useful mainly for **system -monitoring**, **profiling and limiting process resources** and **management of -running processes**. It implements many functionalities offered by command line -tools such as: ps, top, lsof, netstat, ifconfig, who, df, kill, free, nice, -ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap. It currently supports -**Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD** and **NetBSD**, -both **32-bit** and **64-bit** architectures, with Python versions from **2.6 -to 3.5** (users of Python 2.4 and 2.5 may use -`2.1.3 `__ version). +(CPU, memory, disks, network, sensors) in Python. +It is useful mainly for **system monitoring**, **profiling and limiting process +resources** and **management of running processes**. +It implements many functionalities offered by UNIX command line tools such as: +ps, top, lsof, netstat, ifconfig, who, df, kill, free, nice, ionice, iostat, +iotop, uptime, pidof, tty, taskset, pmap. +psutil currently supports the following platforms: + +- **Linux** +- **Windows** +- **OSX**, +- **FreeBSD, OpenBSD**, **NetBSD** +- **Sun Solaris** +- **AIX** + +...both **32-bit** and **64-bit** architectures, with Python +versions from **2.6 to 3.6**. `PyPy `__ is also known to work. ==================== @@ -64,14 +76,17 @@ Example applications | :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/procsmem.png | :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/pmap.png | +------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+ -Also see https://github.com/giampaolo/psutil/tree/master/scripts. +Also see `scripts directory `__ +and `doc recipes `__. ===================== Projects using psutil ===================== -At the time of writing there are currently almost -`4000 projects `__ +At the time of writing psutil has roughly +`2.9 milion downloads `__ +per month and there are over +`6000 open source projects `__ on github which depend from psutil. Here's some I find particularly interesting: @@ -80,6 +95,18 @@ Here's some I find particularly interesting: - https://github.com/google/grr - https://github.com/Jahaja/psdash - https://github.com/ajenti/ajenti +- https://github.com/home-assistant/home-assistant/ + +======== +Portings +======== + +- Go: https://github.com/shirou/gopsutil +- C: https://github.com/hamon-in/cpslib +- Node: https://github.com/christkv/node-psutil +- Rust: https://github.com/borntyping/rust-psutil +- Ruby: https://github.com/spacewander/posixpsutil +- Nim: https://github.com/johnscillieri/psutil-nim ============== Example usages @@ -122,12 +149,17 @@ CPU >>> >>> psutil.cpu_stats() scpustats(ctx_switches=20455687, interrupts=6598984, soft_interrupts=2134212, syscalls=0) + >>> + >>> psutil.cpu_freq() + scpufreq(current=931.42925, min=800.0, max=3500.0) + >>> Memory ====== .. code-block:: python + >>> import psutil >>> psutil.virtual_memory() svmem(total=10367352832, available=6472179712, percent=37.6, used=8186245120, free=2181107712, active=4748992512, inactive=2758115328, buffers=790724608, cached=3500347392, shared=787554304) >>> psutil.swap_memory() @@ -139,6 +171,7 @@ Disks .. code-block:: python + >>> import psutil >>> psutil.disk_partitions() [sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid'), sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw')] @@ -155,15 +188,16 @@ Network .. code-block:: python + >>> import psutil >>> psutil.net_io_counters(pernic=True) {'eth0': netio(bytes_sent=485291293, bytes_recv=6004858642, packets_sent=3251564, packets_recv=4787798, errin=0, errout=0, dropin=0, dropout=0), 'lo': netio(bytes_sent=2838627, bytes_recv=2838627, packets_sent=30567, packets_recv=30567, errin=0, errout=0, dropin=0, dropout=0)} >>> >>> psutil.net_connections() - [pconn(fd=115, family=, type=, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED', pid=1254), - pconn(fd=117, family=, type=, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING', pid=2987), - pconn(fd=-1, family=, type=, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED', pid=None), - pconn(fd=-1, family=, type=, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT', pid=None) + [sconn(fd=115, family=, type=, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED', pid=1254), + sconn(fd=117, family=, type=, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING', pid=2987), + sconn(fd=-1, family=, type=, laddr=addr(ip='10.0.0.1', port=60759), raddr=addr(ip='72.14.234.104', port=80), status='ESTABLISHED', pid=None), + sconn(fd=-1, family=, type=, laddr=addr(ip='10.0.0.1', port=51314), raddr=addr(ip='72.14.234.83', port=443), status='SYN_SENT', pid=None) ...] >>> >>> psutil.net_if_addrs() @@ -177,15 +211,39 @@ Network >>> psutil.net_if_stats() {'eth0': snicstats(isup=True, duplex=, speed=100, mtu=1500), 'lo': snicstats(isup=True, duplex=, speed=0, mtu=65536)} + >>> + +Sensors +======= + +.. code-block:: python + + >>> import psutil + >>> psutil.sensors_temperatures() + {'acpitz': [shwtemp(label='', current=47.0, high=103.0, critical=103.0)], + 'asus': [shwtemp(label='', current=47.0, high=None, critical=None)], + 'coretemp': [shwtemp(label='Physical id 0', current=52.0, high=100.0, critical=100.0), + shwtemp(label='Core 0', current=45.0, high=100.0, critical=100.0), + shwtemp(label='Core 1', current=52.0, high=100.0, critical=100.0), + shwtemp(label='Core 2', current=45.0, high=100.0, critical=100.0), + shwtemp(label='Core 3', current=47.0, high=100.0, critical=100.0)]} + >>> + >>> psutil.sensors_fans() + {'asus': [sfan(label='cpu_fan', current=3200)]} + >>> + >>> psutil.sensors_battery() + sbattery(percent=93, secsleft=16628, power_plugged=False) + >>> Other system info ================= .. code-block:: python + >>> import psutil >>> psutil.users() - [user(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0), - user(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0)] + [suser(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0, pid=1352), + suser(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0, pid=1788)] >>> >>> psutil.boot_time() 1365519115.0 @@ -198,12 +256,10 @@ Process management >>> import psutil >>> psutil.pids() - [1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, - 268, 1215, 1216, 1220, 1221, 1243, 1244, 1301, 1601, 2237, 2355, - 2637, 2774, 3932, 4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, - 4263, 4282, 4306, 4311, 4312, 4313, 4314, 4337, 4339, 4357, 4358, - 4363, 4383, 4395, 4408, 4433, 4443, 4445, 4446, 5167, 5234, 5235, - 5252, 5318, 5424, 5644, 6987, 7054, 7055, 7071] + [1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, 268, 1215, 1216, 1220, 1221, 1243, 1244, + 1301, 1601, 2237, 2355, 2637, 2774, 3932, 4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, 4263, 4282, + 4306, 4311, 4312, 4313, 4314, 4337, 4339, 4357, 4358, 4363, 4383, 4395, 4408, 4433, 4443, 4445, 4446, + 5167, 5234, 5235, 5252, 5318, 5424, 5644, 6987, 7054, 7055, 7071] >>> >>> p = psutil.Process(7055) >>> p.name() @@ -215,6 +271,16 @@ Process management >>> p.cmdline() ['/usr/bin/python', 'main.py'] >>> + >>> p.pid + 7055 + >>> p.ppid() + 7054 + >>> p.parent() + + >>> p.children() + [, + ] + >>> >>> p.status() 'running' >>> p.username() @@ -235,17 +301,16 @@ Process management 12.1 >>> p.cpu_affinity() [0, 1, 2, 3] - >>> p.cpu_affinity([0]) # set - >>> - >>> p.memory_percent() - 0.63423 + >>> p.cpu_affinity([0, 1]) # set + >>> p.cpu_num() + 1 >>> >>> p.memory_info() pmem(rss=10915840, vms=67608576, shared=3313664, text=2310144, lib=0, data=7262208, dirty=0) - >>> >>> p.memory_full_info() # "real" USS memory usage (Linux, OSX, Win only) pfullmem(rss=10199040, vms=52133888, shared=3887104, text=2867200, lib=0, data=5967872, dirty=0, uss=6545408, pss=6872064, swap=0) - >>> + >>> p.memory_percent() + 0.7823 >>> p.memory_maps() [pmmap_grouped(path='/lib/x8664-linux-gnu/libutil-2.15.so', rss=32768, size=2125824, pss=32768, shared_clean=0, shared_dirty=0, private_clean=20480, private_dirty=12288, referenced=32768, anonymous=12288, swap=0), pmmap_grouped(path='/lib/x8664-linux-gnu/libc-2.15.so', rss=3821568, size=3842048, pss=3821568, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=3821568, referenced=3575808, anonymous=3821568, swap=0), @@ -255,17 +320,17 @@ Process management ...] >>> >>> p.io_counters() - pio(read_count=478001, write_count=59371, read_bytes=700416, write_bytes=69632) + pio(read_count=478001, write_count=59371, read_bytes=700416, write_bytes=69632, read_chars=456232, write_chars=517543) >>> >>> p.open_files() [popenfile(path='/home/giampaolo/svn/psutil/setup.py', fd=3, position=0, mode='r', flags=32768), popenfile(path='/var/log/monitd', fd=4, position=235542, mode='a', flags=33793)] >>> >>> p.connections() - [pconn(fd=115, family=, type=, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED'), - pconn(fd=117, family=, type=, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING'), - pconn(fd=119, family=, type=, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED'), - pconn(fd=123, family=, type=, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT')] + [pconn(fd=115, family=, type=, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED'), + pconn(fd=117, family=, type=, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING'), + pconn(fd=119, family=, type=, laddr=addr(ip='10.0.0.1', port=60759), raddr=addr(ip='72.14.234.104', port=80), status='ESTABLISHED'), + pconn(fd=123, family=, type=, laddr=addr(ip='10.0.0.1', port=51314), raddr=addr(ip='72.14.234.83', port=443), status='SYN_SENT')] >>> >>> p.num_threads() 4 @@ -297,6 +362,10 @@ Process management 'XDG_CONFIG_DIRS': '/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg', 'COLORTERM': 'gnome-terminal', ...} >>> + >>> p.as_dict() + {'status': 'running', 'num_ctx_switches': pctxsw(voluntary=63, involuntary=1), 'pid': 5457, ...} + >>> p.is_running() + True >>> p.suspend() >>> p.resume() >>> @@ -320,14 +389,18 @@ Further process APIs .. code-block:: python - >>> for p in psutil.process_iter(): - ... print(p) + >>> import psutil + >>> for proc in psutil.process_iter(attrs=['pid', 'name']): + ... print(proc.info) ... - psutil.Process(pid=1, name='init') - psutil.Process(pid=2, name='kthreadd') - psutil.Process(pid=3, name='ksoftirqd/0') + {'pid': 1, 'name': 'systemd'} + {'pid': 2, 'name': 'kthreadd'} + {'pid': 3, 'name': 'ksoftirqd/0'} ... >>> + >>> psutil.pid_exists(3) + True + >>> >>> def on_terminate(proc): ... print("process {} terminated".format(proc)) ... @@ -335,6 +408,23 @@ Further process APIs >>> gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate) >>> +Popen wrapper: + +.. code-block:: python + + >>> import psutil + >>> from subprocess import PIPE + >>> p = psutil.Popen(["/usr/bin/python", "-c", "print('hello')"], stdout=PIPE) + >>> p.name() + 'python' + >>> p.username() + 'giampaolo' + >>> p.communicate() + ('hello\n', None) + >>> p.wait(timeout=2) + 0 + >>> + Windows services ================ @@ -357,22 +447,24 @@ Windows services 'status': 'stopped', 'username': 'NT AUTHORITY\\LocalService'} +Other samples +============= + +See `doc recipes `__. + ====== -Donate +Author ====== +psutil was created and is maintained by +`Giampaolo Rodola' `__. A lot of time and effort went into making psutil as it is right now. -If you feel psutil is useful to you or your business and want to support its future development please consider donating me (`Giampaolo Rodola' `_) some money. -I only ask for a small donation, but of course I appreciate any amount. +If you feel psutil is useful to you or your business and want to support its +future development please consider donating me +(`Giampaolo `__) some money. .. image:: http://www.paypal.com/en_US/i/btn/x-click-but04.gif :target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8 :alt: Donate via PayPal Don't want to donate money? Then maybe you could `write me a recommendation on Linkedin `_. - -============ -Mailing list -============ - -http://groups.google.com/group/psutil/ diff --git a/appveyor.yml b/appveyor.yml index 927d9cb3d..092dc23a8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,5 @@ +# Build: 1 (bump this up by 1 to force an appveyor run) + os: Visual Studio 2015 environment: @@ -18,10 +20,6 @@ environment: PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "32" - - PYTHON: "C:\\Python33" - PYTHON_VERSION: "3.3.x" - PYTHON_ARCH: "32" - - PYTHON: "C:\\Python34" PYTHON_VERSION: "3.4.x" PYTHON_ARCH: "32" @@ -30,16 +28,16 @@ environment: PYTHON_VERSION: "3.5.x" PYTHON_ARCH: "32" + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.x" + PYTHON_ARCH: "32" + # 64 bits - PYTHON: "C:\\Python27-x64" PYTHON_VERSION: "2.7.x" PYTHON_ARCH: "64" - - PYTHON: "C:\\Python33-x64" - PYTHON_VERSION: "3.3.x" - PYTHON_ARCH: "64" - - PYTHON: "C:\\Python34-x64" PYTHON_VERSION: "3.4.x" PYTHON_ARCH: "64" @@ -51,6 +49,13 @@ environment: VS_VER: "2015" INSTANCENAME: "SQL2012SP1" + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" + PYTHON_ARCH: "64" + ARCH: x86_64 + VS_VER: "2015" + INSTANCENAME: "SQL2012SP1" + # Also build on a Python version not pre-installed by Appveyor. # See: https://github.com/ogrisel/python-appveyor-demo/issues/10 @@ -67,6 +72,7 @@ install: - "%WITH_COMPILER% %PYTHON%/python.exe -m pip --version" - "%WITH_COMPILER% %PYTHON%/python.exe -m pip install --upgrade --user unittest2 ipaddress pypiwin32 wmi wheel" - "%WITH_COMPILER% %PYTHON%/python.exe -m pip freeze" + - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/winmake.py clean" - "%WITH_COMPILER% %PYTHON%/python.exe setup.py build" - "%WITH_COMPILER% %PYTHON%/python.exe setup.py build build_ext -i" - "%WITH_COMPILER% %PYTHON%/python.exe setup.py develop" @@ -77,11 +83,10 @@ build: off test_script: - "%WITH_COMPILER% %PYTHON%/python -V" - - "%WITH_COMPILER% %PYTHON%/python psutil/tests/runner.py" + - "set PYTHONWARNINGS=all && set PSUTIL_TESTING=1 && set PSUTIL_DEBUG=1 && %WITH_COMPILER% %PYTHON%/python psutil/tests/__main__.py" after_test: - "%WITH_COMPILER% %PYTHON%/python setup.py bdist_wheel" - - "%WITH_COMPILER% %PYTHON%/python setup.py bdist_wininst" artifacts: - path: dist\* @@ -105,7 +110,7 @@ only_commits: psutil/_pswindows.py psutil/arch/windows/* psutil/tests/__init__.py - psutil/tests/runner.py + psutil/tests/__main__.py psutil/tests/test_memory_leaks.py psutil/tests/test_misc.py psutil/tests/test_process.py @@ -113,4 +118,3 @@ only_commits: psutil/tests/test_windows.py scripts/* setup.py - diff --git a/docs/Makefile b/docs/Makefile index a69fc329e..0c4bdf48a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -15,8 +15,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - +.PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @@ -26,8 +25,10 @@ help: @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" + @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @@ -41,41 +42,51 @@ help: @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" +.PHONY: clean clean: rm -rf $(BUILDDIR) +.PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." +.PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." +.PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." +.PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." +.PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." +.PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." +.PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @@ -85,6 +96,16 @@ qthelp: @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/psutil.qhc" +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @@ -94,11 +115,19 @@ devhelp: @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/psutil" @echo "# devhelp" +.PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @@ -106,28 +135,33 @@ latex: @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." +.PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." +.PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." +.PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." +.PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." +.PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @@ -135,39 +169,58 @@ texinfo: @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." +.PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." +.PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." +.PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." +.PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." +.PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." +.PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/docs/README b/docs/README index 3aaea8a5b..8ceb5f21f 100644 --- a/docs/README +++ b/docs/README @@ -3,7 +3,7 @@ About This directory contains the reStructuredText (reST) sources to the psutil documentation. You don't need to build them yourself, prebuilt versions are -available at https://pythonhosted.org/psutil/. +available at http://psutil.readthedocs.io. In case you want, you need to install sphinx first: $ pip install sphinx @@ -12,4 +12,4 @@ Then run: $ make html -You'll then have an HTML version of the doc at _build/html/index.html. \ No newline at end of file +You'll then have an HTML version of the doc at _build/html/index.html. diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 000000000..b76f442af --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,518 @@ +.wy-nav-content { + max-width: 100% !important; + padding: 15px !important; +} + +.rst-content dl:not(.docutils) { + margin: 0px 0px 0px 0px; +} + +.data dd { + margin-bottom: 0px !important; +} + +.data .descname { + border-right:10px !important; +} + +.local-toc li ul li{ + padding-left: 20px !important; +} + +.function .descclassname { + font-weight: normal !important; +} + +.class .descclassname { + font-weight: normal !important; +} + +.admonition.warning { + padding-top: 2px !important; + padding-bottom: 2px !important; +} + +.admonition.note { + padding-top: 2px !important; + padding-bottom: 2px !important; +} + +.rst-content dl:not(.docutils) dt { + color: #555; +} + +.sig-paren { + padding-left: 2px; + padding-right: 2px; +} + +h1, h2, h3 { + background: #eee; + padding: 5px; + border-bottom: 1px solid #ccc; +} + +h1 { + font-size: 35px; +} + +.admonition.warning { + padding-top: 5px !important; + padding-bottom: 5px !important; +} + +.admonition.warning p { + margin-bottom: 5px !important; +} + +.admonition.note { + padding-top: 5px !important; + padding-bottom: 5px !important; +} + +.admonition.note p { + margin-bottom: 5px !important; + backround-color: rgb(238, 255, 204) !important; +} + +.codeblock div[class^='highlight'], pre.literal-block div[class^='highlight'], .rst-content .literal-block div[class^='highlight'], div[class^='highlight'] div[class^='highlight'] { + background-color: #eeffcc !important; +} + +.highlight .hll { + background-color: #ffffcc +} + +.highlight { + background: #eeffcc; +} + +.highlight-default, .highlight-python { + border-radius: 3px !important; + border: 1px solid #ac9 !important; +} + +.highlight .c { + color: #408090; + font-style: italic +} + +.wy-side-nav-search { + background-color: grey !important +} + +.highlight { + border-radius: 3px !important; + +} + +div.highlight-default { + margin-bottom: 10px !important; +} + +pre { + padding: 5px !important; +} + +/* ================================================================== */ +/* Warnings and info boxes like python doc */ +/* ================================================================== */ + +div.admonition { + margin-top: 10px !important; + margin-bottom: 10px !important; +} + +div.warning { + background-color: #ffe4e4 !important; + border: 1px solid #f66 !important; + border-radius: 3px !important; +} + +div.note { + background-color: #eee !important; + border: 1px solid #ccc !important; + border-radius: 3px !important; +} + +div.admonition p.admonition-title + p { + display: inline !important; +} + +p.admonition-title { + display: inline !important; + background: none !important; + color: black !important; +} + +p.admonition-title:after { + content: ":" !important; +} + +div.body div.admonition, div.body div.impl-detail { +} + +.fa-exclamation-circle:before, .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .wy-input-context:before, .rst-content .admonition-title:before { + display: none !important; +} + +.note code { + background: #d6d6d6 !important; +} + +/* ================================================================== */ +/* Syntax highlight like Python doc. +/* ================================================================== */ + +/* Comment */ +.highlight .err { + border: 1px solid #FF0000 +} + +/* Error */ +.highlight .k { + color: #007020; + font-weight: bold +} + +/* Keyword */ +.highlight .o { + color: #666666 +} + +/* Operator */ +.highlight .ch { + color: #408090; + font-style: italic +} + +/* Comment.Hashbang */ +.highlight .cm { + color: #408090; + font-style: italic +} + +/* Comment.Multiline */ +.highlight .cp { + color: #007020 +} + +/* Comment.Preproc */ +.highlight .cpf { + color: #408090; + font-style: italic +} + +/* Comment.PreprocFile */ +.highlight .c1 { + color: #408090; + font-style: italic +} + +/* Comment.Single */ +.highlight .cs { + color: #408090; + background-color: #fff0f0 +} + +/* Comment.Special */ +.highlight .gd { + color: #A00000 +} + +/* Generic.Deleted */ +.highlight .ge { + font-style: italic +} + +/* Generic.Emph */ +.highlight .gr { + color: #FF0000 +} + +/* Generic.Error */ +.highlight .gh { + color: #000080; + font-weight: bold +} + +/* Generic.Heading */ +.highlight .gi { + color: #00A000 +} + +/* Generic.Inserted */ +.highlight .go { + color: #333333 +} + +/* Generic.Output */ +.highlight .gp { + color: #c65d09; + font-weight: bold +} + +/* Generic.Prompt */ +.highlight .gs { + font-weight: bold +} + +/* Generic.Strong */ +.highlight .gu { + color: #800080; + font-weight: bold +} + +/* Generic.Subheading */ +.highlight .gt { + color: #0044DD +} + +/* Generic.Traceback */ +.highlight .kc { + color: #007020; + font-weight: bold +} + +/* Keyword.Constant */ +.highlight .kd { + color: #007020; + font-weight: bold +} + +/* Keyword.Declaration */ +.highlight .kn { + color: #007020; + font-weight: bold +} + +/* Keyword.Namespace */ +.highlight .kp { + color: #007020 +} + +/* Keyword.Pseudo */ +.highlight .kr { + color: #007020; + font-weight: bold +} + +/* Keyword.Reserved */ +.highlight .kt { + color: #902000 +} + +/* Keyword.Type */ +.highlight .m { + color: #208050 +} + +/* Literal.Number */ +.highlight .s { + color: #4070a0 +} + +/* Literal.String */ +.highlight .na { + color: #4070a0 +} + +/* Name.Attribute */ +.highlight .nb { + color: #007020 +} + +/* Name.Builtin */ +.highlight .nc { + color: #0e84b5; + font-weight: bold +} + +/* Name.Class */ +.highlight .no { + color: #60add5 +} + +/* Name.Constant */ +.highlight .nd { + color: #555555; + font-weight: bold +} + +/* Name.Decorator */ +.highlight .ni { + color: #d55537; + font-weight: bold +} + +/* Name.Entity */ +.highlight .ne { + color: #007020 +} + +/* Name.Exception */ +.highlight .nf { + color: #06287e +} + +/* Name.Function */ +.highlight .nl { + color: #002070; + font-weight: bold +} + +/* Name.Label */ +.highlight .nn { + color: #0e84b5; + font-weight: bold +} + +/* Name.Namespace */ +.highlight .nt { + color: #062873; + font-weight: bold +} + +/* Name.Tag */ +.highlight .nv { + color: #bb60d5 +} + +/* Name.Variable */ +.highlight .ow { + color: #007020; + font-weight: bold +} + +/* Operator.Word */ +.highlight .w { + color: #bbbbbb +} + +/* Text.Whitespace */ +.highlight .mb { + color: #208050 +} + +/* Literal.Number.Bin */ +.highlight .mf { + color: #208050 +} + +/* Literal.Number.Float */ +.highlight .mh { + color: #208050 +} + +/* Literal.Number.Hex */ +.highlight .mi { + color: #208050 +} + +/* Literal.Number.Integer */ +.highlight .mo { + color: #208050 +} + +/* Literal.Number.Oct */ +.highlight .sa { + color: #4070a0 +} + +/* Literal.String.Affix */ +.highlight .sb { + color: #4070a0 +} + +/* Literal.String.Backtick */ +.highlight .sc { + color: #4070a0 +} + +/* Literal.String.Char */ +.highlight .dl { + color: #4070a0 +} + +/* Literal.String.Delimiter */ +.highlight .sd { + color: #4070a0; + font-style: italic +} + +/* Literal.String.Doc */ +.highlight .s2 { + color: #4070a0 +} + +/* Literal.String.Double */ +.highlight .se { + color: #4070a0; + font-weight: bold +} + +/* Literal.String.Escape */ +.highlight .sh { + color: #4070a0 +} + +/* Literal.String.Heredoc */ +.highlight .si { + color: #70a0d0; + font-style: italic +} + +/* Literal.String.Interpol */ +.highlight .sx { + color: #c65d09 +} + +/* Literal.String.Other */ +.highlight .sr { + color: #235388 +} + +/* Literal.String.Regex */ +.highlight .s1 { + color: #4070a0 +} + +/* Literal.String.Single */ +.highlight .ss { + color: #517918 +} + +/* Literal.String.Symbol */ +.highlight .bp { + color: #007020 +} + +/* Name.Builtin.Pseudo */ +.highlight .fm { + color: #06287e +} + +/* Name.Function.Magic */ +.highlight .vc { + color: #bb60d5 +} + +/* Name.Variable.Class */ +.highlight .vg { + color: #bb60d5 +} + +/* Name.Variable.Global */ +.highlight .vi { + color: #bb60d5 +} + +/* Name.Variable.Instance */ +.highlight .vm { + color: #bb60d5 +} + +/* Name.Variable.Magic */ +.highlight .il { + color: #208050 +} diff --git a/docs/_template/globaltoc.html b/docs/_template/globaltoc.html deleted file mode 100644 index f5fbb406c..000000000 --- a/docs/_template/globaltoc.html +++ /dev/null @@ -1,12 +0,0 @@ -{# - basic/globaltoc.html - ~~~~~~~~~~~~~~~~~~~~ - - Sphinx sidebar template: global table of contents. - - :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -#} -

{{ _('Manual') }}

-{{ toctree() }} -Back to Welcome diff --git a/docs/_template/indexcontent.html b/docs/_template/indexcontent.html deleted file mode 100644 index dd5e7249a..000000000 --- a/docs/_template/indexcontent.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "defindex.html" %} -{% block tables %} - -{% endblock %} diff --git a/docs/_template/indexsidebar.html b/docs/_template/indexsidebar.html deleted file mode 100644 index 903675d10..000000000 --- a/docs/_template/indexsidebar.html +++ /dev/null @@ -1,8 +0,0 @@ -

Useful links

- diff --git a/docs/_template/page.html b/docs/_template/page.html deleted file mode 100644 index 04b47b415..000000000 --- a/docs/_template/page.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends "!page.html" %} -{% block extrahead %} -{{ super() }} -{% if not embedded %}{% endif %} - - -{% endblock %} - -{% block rootrellink %} -
  • Project Homepage{{ reldelim1 }}
  • -
  • {{ shorttitle }}{{ reldelim1 }}
  • -{% endblock %} - - -{% block footer %} - -{% endblock %} \ No newline at end of file diff --git a/docs/_themes/pydoctheme/static/pydoctheme.css b/docs/_themes/pydoctheme/static/pydoctheme.css deleted file mode 100644 index 4196e5582..000000000 --- a/docs/_themes/pydoctheme/static/pydoctheme.css +++ /dev/null @@ -1,187 +0,0 @@ -@import url("default.css"); - -body { - background-color: white; - margin-left: 1em; - margin-right: 1em; -} - -div.related { - margin-bottom: 1.2em; - padding: 0.5em 0; - border-top: 1px solid #ccc; - margin-top: 0.5em; -} - -div.related a:hover { - color: #0095C4; -} - -div.related:first-child { - border-top: 0; - padding-top: 0; - border-bottom: 1px solid #ccc; -} - -div.sphinxsidebar { - background-color: #eeeeee; - border-radius: 5px; - line-height: 130%; - font-size: smaller; -} - -div.sphinxsidebar h3, div.sphinxsidebar h4 { - margin-top: 1.5em; -} - -div.sphinxsidebarwrapper > h3:first-child { - margin-top: 0.2em; -} - -div.sphinxsidebarwrapper > ul > li > ul > li { - margin-bottom: 0.4em; -} - -div.sphinxsidebar a:hover { - color: #0095C4; -} - -div.sphinxsidebar input { - font-family: 'Lucida Grande','Lucida Sans','DejaVu Sans',Arial,sans-serif; - border: 1px solid #999999; - font-size: smaller; - border-radius: 3px; -} - -div.sphinxsidebar input[type=text] { - max-width: 150px; -} - -div.body { - padding: 0 0 0 1.2em; -} - -div.body p { - line-height: 140%; -} - -div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { - margin: 0; - border: 0; - padding: 0.3em 0; -} - -div.body hr { - border: 0; - background-color: #ccc; - height: 1px; -} - -div.body pre { - border-radius: 3px; - border: 1px solid #ac9; -} - -div.body div.admonition, div.body div.impl-detail { - border-radius: 3px; -} - -div.body div.impl-detail > p { - margin: 0; -} - -div.body div.seealso { - border: 1px solid #dddd66; -} - -div.body a { - color: #00608f; -} - -div.body a:visited { - color: #30306f; -} - -div.body a:hover { - color: #00B0E4; -} - -tt, pre { - font-family: monospace, sans-serif; - font-size: 96.5%; -} - -div.body tt { - border-radius: 3px; -} - -div.body tt.descname { - font-size: 120%; -} - -div.body tt.xref, div.body a tt { - font-weight: normal; -} - -p.deprecated { - border-radius: 3px; -} - -table.docutils { - border: 1px solid #ddd; - min-width: 20%; - border-radius: 3px; - margin-top: 10px; - margin-bottom: 10px; -} - -table.docutils td, table.docutils th { - border: 1px solid #ddd !important; - border-radius: 3px; -} - -table p, table li { - text-align: left !important; -} - -table.docutils th { - background-color: #eee; - padding: 0.3em 0.5em; -} - -table.docutils td { - background-color: white; - padding: 0.3em 0.5em; -} - -table.footnote, table.footnote td { - border: 0 !important; -} - -div.footer { - line-height: 150%; - margin-top: -2em; - text-align: right; - width: auto; - margin-right: 10px; -} - -div.footer a:hover { - color: #0095C4; -} - -div.body h1, -div.body h2, -div.body h3 { - background-color: #EAEAEA; - border-bottom: 1px solid #CCC; - padding-top: 2px; - padding-bottom: 2px; - padding-left: 5px; - margin-top: 5px; - margin-bottom: 5px; -} - -div.body h2 { - padding-left:10px; -} diff --git a/docs/_themes/pydoctheme/theme.conf b/docs/_themes/pydoctheme/theme.conf deleted file mode 100644 index 95b97e536..000000000 --- a/docs/_themes/pydoctheme/theme.conf +++ /dev/null @@ -1,23 +0,0 @@ -[theme] -inherit = default -stylesheet = pydoctheme.css -pygments_style = sphinx - -[options] -bodyfont = 'Lucida Grande', 'Lucida Sans', 'DejaVu Sans', Arial, sans-serif -headfont = 'Lucida Grande', 'Lucida Sans', 'DejaVu Sans', Arial, sans-serif -footerbgcolor = white -footertextcolor = #555555 -relbarbgcolor = white -relbartextcolor = #666666 -relbarlinkcolor = #444444 -sidebarbgcolor = white -sidebartextcolor = #444444 -sidebarlinkcolor = #444444 -bgcolor = white -textcolor = #222222 -linkcolor = #0090c0 -visitedlinkcolor = #00608f -headtextcolor = #1a1a1a -headbgcolor = white -headlinkcolor = #aaaaaa diff --git a/docs/conf.py b/docs/conf.py index 9fa163b65..df825cbd5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # psutil documentation build configuration file, created by -# sphinx-quickstart. +# sphinx-quickstart on Wed Oct 19 21:54:30 2016. # # This file is execfile()d with the current directory set to its # containing dir. @@ -12,12 +12,22 @@ # All configuration values have a default; values that are commented out # serve to show the default. +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + import datetime import os PROJECT_NAME = "psutil" -AUTHOR = "Giampaolo Rodola'" +AUTHOR = u"Giampaolo Rodola" THIS_YEAR = str(datetime.datetime.now().year) HERE = os.path.abspath(os.path.dirname(__file__)) @@ -35,27 +45,33 @@ def get_version(): else: raise ValueError("couldn't find version string") + VERSION = get_version() # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.0' +# +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', - 'sphinx.ext.pngmath', + 'sphinx.ext.imgmath', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_template'] +templates_path = ['_templates'] -# The suffix of source filenames. +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. +# # source_encoding = 'utf-8-sig' # The master toctree document. @@ -64,6 +80,7 @@ def get_version(): # General information about the project. project = PROJECT_NAME copyright = '2009-%s, %s' % (THIS_YEAR, AUTHOR) +author = AUTHOR # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -71,35 +88,47 @@ def get_version(): # # The short X.Y version. version = VERSION +# The full version, including alpha/beta/rc tags. +release = VERSION # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -# language = None +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: +# # today = '' +# # Else, today_fmt is used as the format for a strftime call. +# # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The reST default role (used for this markup: `text`) to use for all # documents. +# # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -add_function_parentheses = True +# +# add_function_parentheses = True + # If true, the current module name will be prepended to all description # unit titles (such as .. function::). +# # add_module_names = True -autodoc_docstring_signature = True - # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. +# # show_authors = False # The name of the Pygments (syntax highlighting) style to use. @@ -108,141 +137,240 @@ def get_version(): # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + -# -- Options for HTML output ------------------------------------------------- +# -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -html_theme = 'pydoctheme' -html_theme_options = {'collapsiblesidebar': True} +# +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ["_themes"] +# html_theme_path = [] -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = "{project} {version} documentation".format(**locals()) +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = u'psutil v1.0' # A shorter title for the navigation bar. Default is the same as html_title. +# # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -# html_logo = 'logo.png' +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or +# 32x32 pixels large. -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -html_favicon = '_static/favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -html_last_updated_fmt = '%b %d, %Y' +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -html_use_smartypants = True +# +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -html_sidebars = { - 'index': 'indexsidebar.html', - '**': ['globaltoc.html', - 'relations.html', - 'sourcelink.html', - 'searchbox.html'] -} +# +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -# html_additional_pages = { -# 'index': 'indexcontent.html', -# } +# +# html_additional_pages = {} # If false, no module index is generated. -html_domain_indices = False +# +# html_domain_indices = True # If false, no index is generated. -html_use_index = True +# +# html_use_index = True # If true, the index is split into individual pages for each letter. +# # html_split_index = False # If true, links to the reST sources are added to the pages. +# # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. +# # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + # Output file base name for HTML help builder. htmlhelp_basename = '%s-doc' % PROJECT_NAME -# -- Options for LaTeX output ------------------------------------------------ +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', -# The paper size ('letter' or 'a4'). -# latex_paper_size = 'letter' + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', -# The font size ('10pt', '11pt' or '12pt'). -# latex_font_size = '10pt' + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} # Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto/manual]). +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', '%s.tex' % PROJECT_NAME, - '%s documentation' % PROJECT_NAME, AUTHOR), + (master_doc, 'psutil.tex', u'psutil Documentation', + AUTHOR, 'manual'), ] -# The name of an image file (relative to this directory) to place at -# the top of the title page. +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. +# # latex_use_parts = False # If true, show page references after internal links. +# # latex_show_pagerefs = False # If true, show URL addresses after external links. +# # latex_show_urls = False -# Additional stuff for the LaTeX preamble. -# latex_preamble = '' - # Documents to append as an appendix to all manuals. +# # latex_appendices = [] +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + # If false, no module index is generated. +# # latex_domain_indices = True -# -- Options for manual page output ------------------------------------------ +# -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', PROJECT_NAME, '%s documentation' % PROJECT_NAME, [AUTHOR], 1) + (master_doc, 'psutil', u'psutil Documentation', + [author], 1) ] # If true, show URL addresses after external links. +# # man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'psutil', u'psutil Documentation', + author, 'psutil', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False + + +html_context = { + 'css_files': [ + 'https://media.readthedocs.org/css/sphinx_rtd_theme.css', + 'https://media.readthedocs.org/css/readthedocs-doc-embed.css', + '_static/css/custom.css', + ], +} diff --git a/docs/index.rst b/docs/index.rst index 7fd79a1e3..d58e1c19d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,29 +8,37 @@ psutil documentation Quick links ----------- -* `Home page `__ -* `Install `_ -* `Blog `__ -* `Forum `__ -* `Download `__ -* `Development guide `_ -* `What's new `__ +- `Home page `__ +- `Install `_ +- `Blog `__ +- `Forum `__ +- `Download `__ +- `Development guide `_ +- `What's new `__ About ----- psutil (python system and process utilities) is a cross-platform library for retrieving information on running -**processes** and **system utilization** (CPU, memory, disks, network) in -**Python**. -It is useful mainly for **system monitoring**, **profiling** and **limiting -process resources** and **management of running processes**. -It implements many functionalities offered by command line tools +**processes** and **system utilization** (CPU, memory, disks, network, sensors) +in **Python**. +It is useful mainly for **system monitoring**, **profiling**, **limiting +process resources** and the **management of running processes**. +It implements many functionalities offered by UNIX command line tools such as: *ps, top, lsof, netstat, ifconfig, who, df, kill, free, nice, ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap*. -It currently supports **Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD** -and **NetBSD**, both **32-bit** and **64-bit** architectures, with Python -versions from **2.6 to 3.5** (users of Python 2.4 and 2.5 may use +psutil currently supports the following platforms: + +- **Linux** +- **Windows** +- **OSX**, +- **FreeBSD, OpenBSD**, **NetBSD** +- **Sun Solaris** +- **AIX** + +...both **32-bit** and **64-bit** architectures, with Python +versions from **2.6 to 3.6** (users of Python 2.4 and 2.5 may use `2.1.3 `__ version). `PyPy `__ is also known to work. @@ -39,11 +47,13 @@ The psutil documentation you're reading is distributed as a single HTML page. Install ------- -On Windows, or on UNIX if you have a C compiler installed, the easiest way to -install psutil is via ``pip``:: +The easiest way to install psutil is via ``pip``:: pip install psutil +On UNIX this requires a C compiler (e.g. gcc) installed. On Windows pip will +automatically retrieve a pre-compiled wheel version from +`PYPI repository `__. Alternatively, see more detailed `install `_ instructions. @@ -57,27 +67,35 @@ CPU .. function:: cpu_times(percpu=False) - Return system CPU times as a namedtuple. + Return system CPU times as a named tuple. Every attribute represents the seconds the CPU has spent in the given mode. The attributes availability varies depending on the platform: - - **user** - - **system** - - **idle** + - **user**: time spent by normal processes executing in user mode; on Linux + this also includes **guest** time + - **system**: time spent by processes executing in kernel mode + - **idle**: time spent doing nothing Platform-specific fields: - - **nice** *(UNIX)* - - **iowait** *(Linux)* - - **irq** *(Linux, BSD)* - - **softirq** *(Linux)* - - **steal** *(Linux 2.6.11+)* - - **guest** *(Linux 2.6.24+)* - - **guest_nice** *(Linux 3.2.0+)* - - **interrupt** *(Windows)* - - **dpc** *(Windows)* - - When *percpu* is ``True`` return a list of namedtuples for each logical CPU + - **nice** *(UNIX)*: time spent by niced (prioritized) processes executing in + user mode; on Linux this also includes **guest_nice** time + - **iowait** *(Linux)*: time spent waiting for I/O to complete + - **irq** *(Linux, BSD)*: time spent for servicing hardware interrupts + - **softirq** *(Linux)*: time spent for servicing software interrupts + - **steal** *(Linux 2.6.11+)*: time spent by other operating systems running + in a virtualized environment + - **guest** *(Linux 2.6.24+)*: time spent running a virtual CPU for guest + operating systems under the control of the Linux kernel + - **guest_nice** *(Linux 3.2.0+)*: time spent running a niced guest + (virtual CPU for guest operating systems under the control of the Linux + kernel) + - **interrupt** *(Windows)*: time spent for servicing hardware interrupts ( + similar to "irq" on UNIX) + - **dpc** *(Windows)*: time spent servicing deferred procedure calls (DPCs); + DPCs are interrupts that run at a lower priority than standard interrupts. + + When *percpu* is ``True`` return a list of named tuples for each logical CPU on the system. First element of the list refers to first CPU, second element to second CPU and so on. @@ -99,8 +117,8 @@ CPU since last call or module import, returning immediately. That means the first time this is called it will return a meaningless ``0.0`` value which you are supposed to ignore. - In this case is recommended for accuracy that this function be called with at - least ``0.1`` seconds between calls. + In this case it is recommended for accuracy that this function be called with + at least ``0.1`` seconds between calls. When *percpu* is ``True`` returns a list of floats representing the utilization as a percentage for each CPU. First element of the list refers to first CPU, second element to second CPU @@ -130,6 +148,8 @@ CPU :func:`psutil.cpu_times(percpu=True)`. *interval* and *percpu* arguments have the same meaning as in :func:`cpu_percent()`. + On Linux "guest" and "guest_nice" percentages are not accounted in "user" + and "user_nice" percentages. .. warning:: the first time this function is called with *interval* = ``0.0`` or @@ -143,20 +163,31 @@ CPU Return the number of logical CPUs in the system (same as `os.cpu_count() `__ - in Python 3.4). + in Python 3.4) or ``None`` if undetermined. + This number may not be equivalent to the number of CPUs the current process + can actually use in case process CPU affinity has been changed or Linux + cgroups are being used. + The number of usable CPUs can be obtained with + ``len(psutil.Process().cpu_affinity())``. If *logical* is ``False`` return the number of physical cores only (hyper - thread CPUs are excluded). Return ``None`` if undetermined. + thread CPUs are excluded). + On OpenBSD and NetBSD ``psutil.cpu_count(logical=False)`` always return + ``None``. Example on a system having 2 physical hyper-thread CPU cores: >>> import psutil >>> psutil.cpu_count() 4 >>> psutil.cpu_count(logical=False) 2 - >>> + + Example returning the number of CPUs usable by the current process: + + >>> len(psutil.Process().cpu_affinity()) + 1 .. function:: cpu_stats() - Return various CPU statistics as a namedtuple: + Return various CPU statistics as a named tuple: - **ctx_switches**: number of context switches (voluntary + involuntary) since boot. @@ -179,12 +210,41 @@ CPU .. versionadded:: 4.1.0 +.. function:: cpu_freq(percpu=False) + + Return CPU frequency as a nameduple including *current*, *min* and *max* + frequencies expressed in Mhz. + On Linux *current* frequency reports the real-time value, on all other + platforms it represents the nominal "fixed" value. + If *percpu* is ``True`` and the system supports per-cpu frequency + retrieval (Linux only) a list of frequencies is returned for each CPU, + if not, a list with a single element is returned. + If *min* and *max* cannot be determined they are set to ``0``. + + Example (Linux): + + .. code-block:: python + + >>> import psutil + >>> psutil.cpu_freq() + scpufreq(current=931.42925, min=800.0, max=3500.0) + >>> psutil.cpu_freq(percpu=True) + [scpufreq(current=2394.945, min=800.0, max=3500.0), + scpufreq(current=2236.812, min=800.0, max=3500.0), + scpufreq(current=1703.609, min=800.0, max=3500.0), + scpufreq(current=1754.289, min=800.0, max=3500.0)] + + Availability: Linux, OSX, Windows + + .. versionadded:: 5.1.0 + + Memory ------ .. function:: virtual_memory() - Return statistics about system memory usage as a namedtuple including the + Return statistics about system memory usage as a named tuple including the following fields, expressed in bytes. Main metrics: - **total**: total physical memory. @@ -240,7 +300,7 @@ Memory .. function:: swap_memory() - Return system swap memory statistics as a namedtuple including the following + Return system swap memory statistics as a named tuple including the following fields: * **total**: total swap memory in bytes @@ -260,12 +320,17 @@ 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 ----- .. function:: disk_partitions(all=False) - Return all mounted disk partitions as a list of namedtuples including device, + Return all mounted disk partitions as a list of named tuples including device, mount point and filesystem type, similarly to "df" command on UNIX. If *all* parameter is ``False`` it tries to distinguish and return physical devices only (e.g. hard disks, cd-rom drives, USB keys) and ignore all others @@ -273,7 +338,7 @@ Disks `/dev/shm `__). Note that this may not be fully reliable on all systems (e.g. on BSD this parameter is ignored). - Namedtuple's **fstype** field is a string which varies depending on the + Named tuple's **fstype** field is a string which varies depending on the platform. On Linux it can be one of the values found in /proc/filesystems (e.g. ``'ext3'`` for an ext3 hard drive o ``'iso9660'`` for the CD-ROM drive). @@ -292,9 +357,9 @@ Disks .. function:: disk_usage(path) - Return disk usage statistics about the given *path* as a namedtuple including - **total**, **used** and **free** space expressed in bytes, plus the - **percentage** usage. + Return disk usage statistics about the partition which contains the given + *path* as a named tuple including **total**, **used** and **free** space + expressed in bytes, plus the **percentage** usage. `OSError `__ is raised if *path* does not exist. Starting from `Python 3.3 `__ this is @@ -319,9 +384,9 @@ Disks .. versionchanged:: 4.3.0 *percent* value takes root reserved space into account. -.. function:: disk_io_counters(perdisk=False) +.. function:: disk_io_counters(perdisk=False, nowrap=True) - Return system-wide disk I/O statistics as a namedtuple including the + Return system-wide disk I/O statistics as a named tuple including the following fields: - **read_count**: number of reads @@ -344,9 +409,18 @@ Disks If *perdisk* is ``True`` return the same information for every physical disk installed on the system as a dictionary with partition names as the keys and - the namedtuple described above as the values. + the named tuple described above as the values. See `iotop.py `__ for an example application. + On some systems such as Linux, on a very busy or long-lived system, the + numbers returned by the kernel may overflow and wrap (restart from zero). + If *nowrap* is ``True`` psutil will detect and adjust those numbers across + function calls and add "old value" to "new value" so that the returned + numbers will always be increasing or remain the same, but never decrease. + ``disk_io_counters.cache_clear()`` can be used to invalidate the *nowrap* + cache. + On Windows it may be ncessary to issue ``diskperf -y`` command from cmd.exe + first in order to enable IO counters. >>> import psutil >>> psutil.disk_io_counters() @@ -357,11 +431,13 @@ Disks 'sda2': sdiskio(read_count=18707, write_count=8830, read_bytes=6060, write_bytes=3443, read_time=24585, write_time=1572), 'sdb1': sdiskio(read_count=161, write_count=0, read_bytes=786432, write_bytes=0, read_time=44, write_time=0)} - .. warning:: - on some systems such as Linux, on a very busy or long-lived system these - numbers may wrap (restart from zero), see - `issues #802 `__. - Applications should be prepared to deal with that. + .. note:: + on Windows ``"diskperf -y"`` command may need to be executed first + otherwise this function won't find any disk. + + .. versionchanged:: + 5.3.0 numbers no longer wrap (restart from zero) across calls thanks to new + *nowrap* argument. .. versionchanged:: 4.0.0 added *busy_time* (Linux, FreeBSD), *read_merged_count* and @@ -375,7 +451,7 @@ Network .. function:: net_io_counters(pernic=False) - Return system-wide network I/O statistics as a namedtuple including the + Return system-wide network I/O statistics as a named tuple including the following attributes: - **bytes_sent**: number of bytes sent @@ -390,7 +466,14 @@ Network If *pernic* is ``True`` return the same information for every network interface installed on the system as a dictionary with network interface - names as the keys and the namedtuple described above as the values. + names as the keys and the named tuple described above as the values. + On some systems such as Linux, on a very busy or long-lived system, the + numbers returned by the kernel may overflow and wrap (restart from zero). + If *nowrap* is ``True`` psutil will detect and adjust those numbers across + function calls and add "old value" to "new value" so that the returned + numbers will always be increasing or remain the same, but never decrease. + ``net_io_counters.cache_clear()`` can be used to invalidate the *nowrap* + cache. >>> import psutil >>> psutil.net_io_counters() @@ -404,21 +487,20 @@ Network and `ifconfig.py `__ for an example application. - .. warning:: - on some systems such as Linux, on a very busy or long-lived system these - numbers may wrap (restart from zero), see - `issues #802 `__. - Applications should be prepared to deal with that. + .. versionchanged:: + 5.3.0 numbers no longer wrap (restart from zero) across calls thanks to new + *nowrap* argument. .. function:: net_connections(kind='inet') - Return system-wide socket connections as a list of namedtuples. - Every namedtuple provides 7 attributes: + Return system-wide socket connections as a list of named tuples. + Every named tuple provides 7 attributes: - - **fd**: the socket file descriptor, if retrievable, else ``-1``. - If the connection refers to the current process this may be passed to + - **fd**: the socket file descriptor. If the connection refers to the current + process this may be passed to `socket.fromfd() `__ to obtain a usable socket object. + On Windows and SunOS this is always set to ``-1``. - **family**: the address family, either `AF_INET `__, `AF_INET6 `__ @@ -427,13 +509,12 @@ Network `__ or `SOCK_DGRAM `__. - - **laddr**: the local address as a ``(ip, port)`` tuple or a ``path`` - in case of AF_UNIX sockets. - - **raddr**: the remote address as a ``(ip, port)`` tuple or an absolute - ``path`` in case of UNIX sockets. + - **laddr**: the local address as a ``(ip, port)`` named tuple or a ``path`` + in case of AF_UNIX sockets. For UNIX sockets see notes below. + - **raddr**: the remote address as a ``(ip, port)`` named tuple or an + absolute ``path`` in case of UNIX sockets. When the remote endpoint is not connected you'll get an empty tuple - (AF_INET*) or ``None`` (AF_UNIX). - On Linux AF_UNIX sockets will always have this set to ``None``. + (AF_INET*) or ``""`` (AF_UNIX). For UNIX sockets see notes below. - **status**: represents the status of a TCP connection. The return value is one of the :data:`psutil.CONN_* ` constants (a string). @@ -443,7 +524,7 @@ Network else ``None``. On some platforms (e.g. Linux) the availability of this field changes depending on process privileges (root is needed). - The *kind* parameter is a string which filters for connections that fit the + The *kind* parameter is a string which filters for connections matching the following criteria: .. table:: @@ -451,30 +532,30 @@ Network +----------------+-----------------------------------------------------+ | **Kind value** | **Connections using** | +================+=====================================================+ - | "inet" | IPv4 and IPv6 | + | ``"inet"`` | IPv4 and IPv6 | +----------------+-----------------------------------------------------+ - | "inet4" | IPv4 | + | ``"inet4"`` | IPv4 | +----------------+-----------------------------------------------------+ - | "inet6" | IPv6 | + | ``"inet6"`` | IPv6 | +----------------+-----------------------------------------------------+ - | "tcp" | TCP | + | ``"tcp"`` | TCP | +----------------+-----------------------------------------------------+ - | "tcp4" | TCP over IPv4 | + | ``"tcp4"`` | TCP over IPv4 | +----------------+-----------------------------------------------------+ - | "tcp6" | TCP over IPv6 | + | ``"tcp6"`` | TCP over IPv6 | +----------------+-----------------------------------------------------+ - | "udp" | UDP | + | ``"udp"`` | UDP | +----------------+-----------------------------------------------------+ - | "udp4" | UDP over IPv4 | + | ``"udp4"`` | UDP over IPv4 | +----------------+-----------------------------------------------------+ - | "udp6" | UDP over IPv6 | + | ``"udp6"`` | UDP over IPv6 | +----------------+-----------------------------------------------------+ - | "unix" | UNIX socket (both UDP and TCP protocols) | + | ``"unix"`` | UNIX socket (both UDP and TCP protocols) | +----------------+-----------------------------------------------------+ - | "all" | the sum of all the possible families and protocols | + | ``"all"`` | the sum of all the possible families and protocols | +----------------+-----------------------------------------------------+ - On OSX this function requires root privileges. + On OSX and AIX this function requires root privileges. To get per-process connections use :meth:`Process.connections`. Also, see `netstat.py sample script `__. @@ -482,27 +563,40 @@ Network >>> import psutil >>> psutil.net_connections() - [pconn(fd=115, family=, type=, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED', pid=1254), - pconn(fd=117, family=, type=, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING', pid=2987), - pconn(fd=-1, family=, type=, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED', pid=None), - pconn(fd=-1, family=, type=, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT', pid=None) + [pconn(fd=115, family=, type=, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED', pid=1254), + pconn(fd=117, family=, type=, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING', pid=2987), + pconn(fd=-1, family=, type=, laddr=addr(ip='10.0.0.1', port=60759), raddr=addr(ip='72.14.234.104', port=80), status='ESTABLISHED', pid=None), + pconn(fd=-1, family=, type=, laddr=addr(ip='10.0.0.1', port=51314), raddr=addr(ip='72.14.234.83', port=443), status='SYN_SENT', pid=None) ...] .. note:: - (OSX) :class:`psutil.AccessDenied` is always raised unless running as root - (lsof does the same). + (OSX and AIX) :class:`psutil.AccessDenied` is always raised unless running + as root. This is a limitation of the OS and ``lsof`` does the same. .. note:: (Solaris) UNIX sockets are not supported. + .. note:: + (Linux, FreeBSD) "raddr" field for UNIX sockets is always set to "". + This is a limitation of the OS. + + .. note:: + (OpenBSD) "laddr" and "raddr" fields for UNIX sockets are always set to + "". This is a limitation of the OS. + .. versionadded:: 2.1.0 + .. versionchanged:: 5.3.0 : socket "fd" is now set for real instead of being + ``-1``. + + .. versionchanged:: 5.3.0 : "laddr" and "raddr" are named tuples. + .. function:: net_if_addrs() Return the addresses associated to each NIC (network interface card) installed on the system as a dictionary whose keys are the NIC names and - value is a list of namedtuples for each address assigned to the NIC. - Each namedtuple includes 5 fields: + value is a list of named tuples for each address assigned to the NIC. + Each named tuple includes 5 fields: - **family**: the address family, either `AF_INET `__, @@ -553,7 +647,7 @@ Network .. function:: net_if_stats() Return information about each NIC (network interface card) installed on the - system as a dictionary whose keys are the NIC names and value is a namedtuple + system as a dictionary whose keys are the NIC names and value is a named tuple with the following fields: - **isup**: a bool indicating whether the NIC is up and running. @@ -578,6 +672,109 @@ Network .. versionadded:: 3.0.0 +Sensors +------- + +.. function:: sensors_temperatures(fahrenheit=False) + + Return hardware temperatures. Each entry is a named tuple representing a + certain hardware temperature sensor (it may be a CPU, an hard disk or + something else, depending on the OS and its configuration). + All temperatures are expressed in celsius unless *fahrenheit* is set to + ``True``. + If sensors are not supported by the OS an empty dict is returned. + Example:: + + >>> import psutil + >>> psutil.sensors_temperatures() + {'acpitz': [shwtemp(label='', current=47.0, high=103.0, critical=103.0)], + 'asus': [shwtemp(label='', current=47.0, high=None, critical=None)], + 'coretemp': [shwtemp(label='Physical id 0', current=52.0, high=100.0, critical=100.0), + shwtemp(label='Core 0', current=45.0, high=100.0, critical=100.0), + shwtemp(label='Core 1', current=52.0, high=100.0, critical=100.0), + shwtemp(label='Core 2', current=45.0, high=100.0, critical=100.0), + shwtemp(label='Core 3', current=47.0, high=100.0, critical=100.0)]} + + See also `temperatures.py `__ and `sensors.py `__ + for an example application. + + Availability: Linux + + .. versionadded:: 5.1.0 + + .. warning:: + + this API is experimental. Backward incompatible changes may occur if + deemed necessary. + +.. function:: sensors_fans() + + Return hardware fans speed. Each entry is a named tuple representing a + certain hardware sensor fan. + Fan speed is expressed in RPM (rounds per minute). + If sensors are not supported by the OS an empty dict is returned. + Example:: + + >>> import psutil + >>> psutil.sensors_fans() + {'asus': [sfan(label='cpu_fan', current=3200)]} + + See also `fans.py `__ and `sensors.py `__ + for an example application. + + Availability: Linux + + .. versionadded:: 5.2.0 + + .. warning:: + + this API is experimental. Backward incompatible changes may occur if + deemed necessary. + +.. function:: sensors_battery() + + Return battery status information as a named tuple including the following + values. If no battery is installed or metrics can't be determined ``None`` + is returned. + + - **percent**: battery power left as a percentage. + - **secsleft**: a rough approximation of how many seconds are left before the + battery runs out of power. + If the AC power cable is connected this is set to + :data:`psutil.POWER_TIME_UNLIMITED `. + If it can't be determined it is set to + :data:`psutil.POWER_TIME_UNKNOWN `. + - **power_plugged**: ``True`` if the AC power cable is connected, ``False`` + if not or ``None`` if it can't be determined. + + Example:: + + >>> import psutil + >>> + >>> def secs2hours(secs): + ... mm, ss = divmod(secs, 60) + ... hh, mm = divmod(mm, 60) + ... return "%d:%02d:%02d" % (hh, mm, ss) + ... + >>> battery = psutil.sensors_battery() + >>> battery + sbattery(percent=93, secsleft=16628, power_plugged=False) + >>> print("charge = %s%%, time left = %s" % (batt.percent, secs2hours(batt.secsleft))) + charge = 93%, time left = 4:37:08 + + See also `battery.py `__ and `sensors.py `__ for an example application. + + Availability: Linux, Windows, FreeBSD + + .. versionadded:: 5.1.0 + + .. versionchanged:: 5.4.2 added OSX support + + .. warning:: + + this API is experimental. Backward incompatible changes may occur if + deemed necessary. + Other system info ----------------- @@ -594,9 +791,14 @@ Other system info >>> datetime.datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S") '2014-01-12 22:51:00' + .. note:: + on Windows this function may return a time which is off by 1 second if it's + used across different processes (see + `issue #1007 `__). + .. function:: users() - Return users currently connected on the system as a list of namedtuples + Return users currently connected on the system as a list of named tuples including the following fields: - **user**: the name of the user. @@ -605,13 +807,18 @@ Other system info - **host**: the host name associated with the entry, if any. - **started**: the creation time as a floating point number expressed in seconds since the epoch. + - **pid**: the PID of the login process (like sshd, tmux, gdm-session-worker, + ...). On Windows and OpenBSD this is always set to ``None``. Example:: >>> import psutil >>> psutil.users() - [suser(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0), - suser(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0)] + [suser(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0, pid=1352), + suser(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0, pid=1788)] + + .. versionchanged:: + 5.3.0 added "pid" field Processes ========= @@ -622,14 +829,13 @@ Functions .. function:: pids() Return a list of current running PIDs. To iterate over all processes - :func:`process_iter()` should be preferred. - -.. function:: pid_exists(pid) + and avoid race conditions :func:`process_iter()` should be preferred. - Check whether the given PID exists in the current process list. This is - faster than doing ``"pid in psutil.pids()"`` and should be preferred. + >>> import psutil + >>> psutil.pids() + [1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, ..., 32498] -.. function:: process_iter() +.. function:: process_iter(attrs=None, ad_value=None) Return an iterator yielding a :class:`Process` class instance for all running processes on the local machine. @@ -641,41 +847,96 @@ Functions This is should be preferred over :func:`psutil.pids()` for iterating over processes. Sorting order in which processes are returned is - based on their PID. Example usage:: + based on their PID. + *attrs* and *ad_value* have the same meaning as in :meth:`Process.as_dict()`. + If *attrs* is specified :meth:`Process.as_dict()` is called interanally and + the resulting dict is stored as a ``info`` attribute which is attached to the + returned :class:`Process` instances. + If *attrs* is an empty list it will retrieve all process info (slow). + Example usage:: - import psutil + >>> import psutil + >>> for proc in psutil.process_iter(): + ... try: + ... pinfo = proc.as_dict(attrs=['pid', 'name', 'username']) + ... except psutil.NoSuchProcess: + ... pass + ... else: + ... print(pinfo) + ... + {'name': 'systemd', 'pid': 1, 'username': 'root'} + {'name': 'kthreadd', 'pid': 2, 'username': 'root'} + {'name': 'ksoftirqd/0', 'pid': 3, 'username': 'root'} + ... + + More compact version using *attrs* parameter:: + + >>> import psutil + >>> for proc in psutil.process_iter(attrs=['pid', 'name', 'username']): + ... print(proc.info) + ... + {'name': 'systemd', 'pid': 1, 'username': 'root'} + {'name': 'kthreadd', 'pid': 2, 'username': 'root'} + {'name': 'ksoftirqd/0', 'pid': 3, 'username': 'root'} + ... + + Example of a dict comprehensions to create a ``{pid: info, ...}`` data + structure:: + + >>> import psutil + >>> procs = {p.pid: p.info for p in psutil.process_iter(attrs=['name', 'username'])} + >>> procs + {1: {'name': 'systemd', 'username': 'root'}, + 2: {'name': 'kthreadd', 'username': 'root'}, + 3: {'name': 'ksoftirqd/0', 'username': 'root'}, + ...} + + Example showing how to filter processes by name:: + + >>> import psutil + >>> [p.info for p in psutil.process_iter(attrs=['pid', 'name']) if 'python' in p.info['name']] + [{'name': 'python3', 'pid': 21947}, + {'name': 'python', 'pid': 23835}] + + See also `process filtering <#filtering-and-sorting-processes>`__ section for + more examples. + + .. versionchanged:: + 5.3.0 added "attrs" and "ad_value" parameters. - for proc in psutil.process_iter(): - try: - pinfo = proc.as_dict(attrs=['pid', 'name']) - except psutil.NoSuchProcess: - pass - else: - print(pinfo) +.. function:: pid_exists(pid) + + Check whether the given PID exists in the current process list. This is + faster than doing ``pid in psutil.pids()`` and should be preferred. .. function:: wait_procs(procs, timeout=None, callback=None) Convenience function which waits for a list of :class:`Process` instances to terminate. Return a ``(gone, alive)`` tuple indicating which processes are gone and which ones are still alive. The *gone* ones will have a new - *returncode* attribute indicating process exit status (it may be ``None``). - ``callback`` is a function which gets called every time a process terminates - (a :class:`Process` instance is passed as callback argument). Function will - return as soon as all processes terminate or when timeout occurs. Typical use - case is: + *returncode* attribute indicating process exit status (will be ``None`` for + processes which are not our children). + ``callback`` is a function which gets called when one of the processes being + waited on is terminated and a :class:`Process` instance is passed as callback + argument). + This function will return as soon as all processes terminate or when + *timeout* (seconds) occurs. + Differently from :meth:`Process.wait` it will not raise + :class:`TimeoutExpired` if timeout occurs. + A typical use case may be: - send SIGTERM to a list of processes - give them some time to terminate - send SIGKILL to those ones which are still alive - Example:: + Example which terminates and waits all the children of this process:: import psutil def on_terminate(proc): print("process {} terminated with exit code {}".format(proc, proc.returncode)) - procs = [...] # a list of Process instances + procs = psutil.Process().children() for p in procs: p.terminate() gone, alive = psutil.wait_procs(procs, timeout=3, callback=on_terminate) @@ -692,8 +953,8 @@ Exceptions .. class:: NoSuchProcess(pid, name=None, msg=None) Raised by :class:`Process` class methods when no process with the given - pid* is found in the current process list or when a process no longer - exists. "name" is the name the process had before disappearing + *pid* is found in the current process list or when a process no longer + exists. *name* is the name the process had before disappearing and gets set only if :meth:`Process.name()` was previously called. .. class:: ZombieProcess(pid, name=None, ppid=None, msg=None) @@ -723,10 +984,12 @@ Process class .. class:: Process(pid=None) - Represents an OS process with the given *pid*. If *pid* is omitted current - process *pid* (`os.getpid() `__) - is used. + Represents an OS process with the given *pid*. + If *pid* is omitted current process *pid* + (`os.getpid() `__) is used. Raise :class:`NoSuchProcess` if *pid* does not exist. + On Linux *pid* can also refer to a thread ID (the *id* field returned by + :meth:`threads` method). When accessing methods of this class always be prepared to catch :class:`NoSuchProcess`, :class:`ZombieProcess` and :class:`AccessDenied` exceptions. @@ -742,14 +1005,13 @@ Process class at the same time, make sure to use either :meth:`as_dict` or :meth:`oneshot` context manager. - .. warning:: + .. note:: - the way this class is bound to a process is via its **PID**. - That means that if the :class:`Process` instance is old enough and - the PID has been reused in the meantime you might end up interacting - with another process. + the way this class is bound to a process is uniquely via its **PID**. + That means that if the process terminates and the OS reuses its PID you may + end up interacting with another process. The only exceptions for which process identity is preemptively checked - (via PID + creation time) and guaranteed are for + (via PID + creation time) is for the following methods: :meth:`nice` (set), :meth:`ionice` (set), :meth:`cpu_affinity` (set), @@ -759,12 +1021,13 @@ Process class :meth:`suspend` :meth:`resume`, :meth:`send_signal`, - :meth:`terminate`, and - :meth:`kill` - methods. + :meth:`terminate` + :meth:`kill`. To prevent this problem for all other methods you can use - :meth:`is_running()` before querying the process or use + :meth:`is_running()` before querying the process or :func:`process_iter()` in case you're iterating over all processes. + It must be noted though that unless you deal with very "old" (inactive) + :class:`Process` instances this will hardly represent a problem. .. method:: oneshot() @@ -802,43 +1065,45 @@ Process class The last column (speedup) shows an approximation of the speedup you can get if you call all the methods together (best case scenario). - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | Linux | Windows | OSX | BSD | SunOS | - +==============================+===============================+==============================+==============================+==========================+ - | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`name` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`cmdline` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`create_time` | :meth:`io_counters()` | :meth:`memory_info` | :meth:`create_time` | :meth:`create_time` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`name` | :meth:`ionice` | :meth:`memory_percent` | :meth:`gids` | | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`ppid` | :meth:`memory_info` | :meth:`num_ctx_switches` | :meth:`io_counters` | :meth:`memory_info` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`status` | :meth:`nice` | :meth:`num_threads` | :meth:`name` | :meth:`memory_percent` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`terminal` | :meth:`memory_maps` | | :meth:`memory_info` | :meth:`nice` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | | :meth:`num_ctx_switches` | :meth:`create_time` | :meth:`memory_percent` | :meth:`num_threads` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`gids` | :meth:`num_handles` | :meth:`gids` | :meth:`num_ctx_switches` | :meth:`ppid` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`num_ctx_switches` | :meth:`num_threads` | :meth:`name` | :meth:`ppid` | :meth:`status` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`num_threads` | :meth:`username` | :meth:`ppid` | :meth:`status` | :meth:`terminal` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`uids` | | :meth:`status` | :meth:`terminal` | | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`username` | | :meth:`terminal` | :meth:`uids` | :meth:`gids` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | | | :meth:`uids` | :meth:`username` | :meth:`uids` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`memory_full_info` | | :meth:`username` | | :meth:`username` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`memory_maps` | | | | | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | *speedup: +2.5x* | *speedup: +1.8x / +6.5x* | *speedup: +1.9x* | *speedup: +2.0x* | *speedup: +1.3x* | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | Linux | Windows | OSX | BSD | SunOS | AIX | + +==============================+===============================+==============================+==============================+==========================+==========================+ + | :meth:`cpu_num` | :meth:`cpu_percent` | :meth:`cpu_percent` | :meth:`cpu_num` | :meth:`name` | :meth:`name` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`cpu_percent` | :meth:`cpu_times` | :meth:`cpu_times` | :meth:`cpu_percent` | :meth:`cmdline` | :meth:`cmdline` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`cpu_times` | :meth:`io_counters()` | :meth:`memory_info` | :meth:`cpu_times` | :meth:`create_time` | :meth:`create_time` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`create_time` | :meth:`memory_info` | :meth:`memory_percent` | :meth:`create_time` | | | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`name` | :meth:`memory_maps` | :meth:`num_ctx_switches` | :meth:`gids` | :meth:`memory_info` | :meth:`memory_info` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`ppid` | :meth:`num_ctx_switches` | :meth:`num_threads` | :meth:`io_counters` | :meth:`memory_percent` | :meth:`memory_percent` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`status` | :meth:`num_handles` | | :meth:`name` | :meth:`num_threads` | :meth:`num_threads` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`terminal` | :meth:`num_threads` | :meth:`create_time` | :meth:`memory_info` | :meth:`ppid` | :meth:`ppid` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | | :meth:`username` | :meth:`gids` | :meth:`memory_percent` | :meth:`status` | :meth:`status` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`gids` | | :meth:`name` | :meth:`num_ctx_switches` | :meth:`terminal` | :meth:`terminal` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`num_ctx_switches` | | :meth:`ppid` | :meth:`ppid` | | | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`num_threads` | | :meth:`status` | :meth:`status` | :meth:`gids` | :meth:`gids` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`uids` | | :meth:`terminal` | :meth:`terminal` | :meth:`uids` | :meth:`uids` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`username` | | :meth:`uids` | :meth:`uids` | :meth:`username` | :meth:`username` | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | | | :meth:`username` | :meth:`username` | | | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`memory_full_info` | | | | | | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | :meth:`memory_maps` | | | | | | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | *speedup: +2.6x* | *speedup: +1.8x / +6.5x* | *speedup: +1.9x* | *speedup: +2.0x* | *speedup: +1.3x* | *speedup: +1.3x* | + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ .. versionadded:: 5.0.0 @@ -848,16 +1113,18 @@ Process class .. method:: ppid() - The process parent pid. On Windows the return value is cached after first + The process parent PID. On Windows the return value is cached after first call. Not on POSIX because `ppid may change `__ if process becomes a zombie. + See also :meth:`parent` method. .. method:: name() The process name. On Windows the return value is cached after first - call. Not on POSIX because the process - `name may change `__. + call. Not on POSIX because the process name + `may change `__. + See also how to `find a process by name <#find-process-by-name>`__. .. method:: exe() @@ -865,18 +1132,32 @@ Process class On some systems this may also be an empty string. The return value is cached after first call. + >>> import psutil + >>> psutil.Process().exe() + '/usr/bin/python2.7' + .. method:: cmdline() - The command line this process has been called with. + The command line this process has been called with as a list of strings. + The return value is not cached because the cmdline of a process may change. + + >>> import psutil + >>> psutil.Process().cmdline() + ['python', 'manage.py', 'runserver'] .. method:: environ() The environment variables of the process as a dict. Note: this might not reflect changes made after the process started. - Availability: Linux, OSX, Windows + >>> import psutil + >>> psutil.Process().environ() + {'LC_NUMERIC': 'it_IT.UTF-8', 'QT_QPA_PLATFORMTHEME': 'appmenu-qt5', 'IM_CONFIG_PHASE': '1', 'XDG_GREETER_DATA_DIR': '/var/lib/lightdm-data/giampaolo', 'GNOME_DESKTOP_SESSION_ID': 'this-is-deprecated', 'XDG_CURRENT_DESKTOP': 'Unity', 'UPSTART_EVENTS': 'started starting', 'GNOME_KEYRING_PID': '', 'XDG_VTNR': '7', 'QT_IM_MODULE': 'ibus', 'LOGNAME': 'giampaolo', 'USER': 'giampaolo', 'PATH': '/home/giampaolo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/giampaolo/svn/sysconf/bin', 'LC_PAPER': 'it_IT.UTF-8', 'GNOME_KEYRING_CONTROL': '', 'GTK_IM_MODULE': 'ibus', 'DISPLAY': ':0', 'LANG': 'en_US.UTF-8', 'LESS_TERMCAP_se': '\x1b[0m', 'TERM': 'xterm-256color', 'SHELL': '/bin/bash', 'XDG_SESSION_PATH': '/org/freedesktop/DisplayManager/Session0', 'XAUTHORITY': '/home/giampaolo/.Xauthority', 'LANGUAGE': 'en_US', 'COMPIZ_CONFIG_PROFILE': 'ubuntu', 'LC_MONETARY': 'it_IT.UTF-8', 'QT_LINUX_ACCESSIBILITY_ALWAYS_ON': '1', 'LESS_TERMCAP_me': '\x1b[0m', 'LESS_TERMCAP_md': '\x1b[01;38;5;74m', 'LESS_TERMCAP_mb': '\x1b[01;31m', 'HISTSIZE': '100000', 'UPSTART_INSTANCE': '', 'CLUTTER_IM_MODULE': 'xim', 'WINDOWID': '58786407', 'EDITOR': 'vim', 'SESSIONTYPE': 'gnome-session', 'XMODIFIERS': '@im=ibus', 'GPG_AGENT_INFO': '/home/giampaolo/.gnupg/S.gpg-agent:0:1', 'HOME': '/home/giampaolo', 'HISTFILESIZE': '100000', 'QT4_IM_MODULE': 'xim', 'GTK2_MODULES': 'overlay-scrollbar', 'XDG_SESSION_DESKTOP': 'ubuntu', 'SHLVL': '1', 'XDG_RUNTIME_DIR': '/run/user/1000', 'INSTANCE': 'Unity', 'LC_ADDRESS': 'it_IT.UTF-8', 'SSH_AUTH_SOCK': '/run/user/1000/keyring/ssh', 'VTE_VERSION': '4205', 'GDMSESSION': 'ubuntu', 'MANDATORY_PATH': '/usr/share/gconf/ubuntu.mandatory.path', 'VISUAL': 'vim', 'DESKTOP_SESSION': 'ubuntu', 'QT_ACCESSIBILITY': '1', 'XDG_SEAT_PATH': '/org/freedesktop/DisplayManager/Seat0', 'LESSCLOSE': '/usr/bin/lesspipe %s %s', 'LESSOPEN': '| /usr/bin/lesspipe %s', 'XDG_SESSION_ID': 'c2', 'DBUS_SESSION_BUS_ADDRESS': 'unix:abstract=/tmp/dbus-9GAJpvnt8r', '_': '/usr/bin/python', 'DEFAULTS_PATH': '/usr/share/gconf/ubuntu.default.path', 'LC_IDENTIFICATION': 'it_IT.UTF-8', 'LESS_TERMCAP_ue': '\x1b[0m', 'UPSTART_SESSION': 'unix:abstract=/com/ubuntu/upstart-session/1000/1294', 'XDG_CONFIG_DIRS': '/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg', 'GTK_MODULES': 'gail:atk-bridge:unity-gtk-module', 'XDG_SESSION_TYPE': 'x11', 'PYTHONSTARTUP': '/home/giampaolo/.pythonstart', 'LC_NAME': 'it_IT.UTF-8', 'OLDPWD': '/home/giampaolo/svn/curio_giampaolo/tests', 'GDM_LANG': 'en_US', 'LC_TELEPHONE': 'it_IT.UTF-8', 'HISTCONTROL': 'ignoredups:erasedups', 'LC_MEASUREMENT': 'it_IT.UTF-8', 'PWD': '/home/giampaolo/svn/curio_giampaolo', 'JOB': 'gnome-session', 'LESS_TERMCAP_us': '\x1b[04;38;5;146m', 'UPSTART_JOB': 'unity-settings-daemon', 'LC_TIME': 'it_IT.UTF-8', 'LESS_TERMCAP_so': '\x1b[38;5;246m', 'PAGER': 'less', 'XDG_DATA_DIRS': '/usr/share/ubuntu:/usr/share/gnome:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop', 'XDG_SEAT': 'seat0'} + + Availability: Linux, OSX, Windows, SunOS .. versionadded:: 4.0.0 + .. versionchanged:: 5.3.0 added SunOS support .. method:: create_time() @@ -921,6 +1202,7 @@ Process class Utility method which returns the parent process as a :class:`Process` object preemptively checking whether PID has been reused. If no parent PID is known return ``None``. + See also :meth:`ppid` method. .. method:: status() @@ -939,7 +1221,7 @@ Process class .. method:: uids() The real, effective and saved user ids of this process as a - namedtuple. This is the same as + named tuple. This is the same as `os.getresuid() `__ but can be used for any process PID. @@ -948,7 +1230,7 @@ Process class .. method:: gids() The real, effective and saved group ids of this process as a - namedtuple. This is the same as + named tuple. This is the same as `os.getresgid() `__ but can be used for any process PID. @@ -1011,7 +1293,8 @@ Process class >>> On Windows only *ioclass* is used and it can be set to ``2`` (normal), - ``1`` (low) or ``0`` (very low). + ``1`` (low) or ``0`` (very low). Also it returns an integer instead of a + named tuple. Availability: Linux and Windows > Vista @@ -1049,51 +1332,82 @@ Process class .. method:: io_counters() - Return process I/O statistics as a namedtuple including the number of read - and write operations performed by the process and the amount of bytes read - and written. For Linux refer to - `/proc filesysem documentation `__. - On BSD there's apparently no way to retrieve bytes counters, hence ``-1`` - is returned for **read_bytes** and **write_bytes** fields. OSX is not - supported. + Return process I/O statistics as a named tuple. + For Linux you can refer to + `/proc filesysem documentation `__. + + - **read_count**: the number of read operations performed (cumulative). + This is supposed to count the number of read-related syscalls such as + ``read()`` and ``pread()`` on UNIX. + - **write_count**: the number of write operations performed (cumulative). + This is supposed to count the number of write-related syscalls such as + ``write()`` and ``pwrite()`` on UNIX. + - **read_bytes**: the number of bytes read (cumulative). + Always ``-1`` on BSD. + - **write_bytes**: the number of bytes written (cumulative). + Always ``-1`` on BSD. + + Linux specific: + + - **read_chars** *(Linux)*: the amount of bytes which this process passed + to ``read()`` and ``pread()`` syscalls (cumulative). + Differently from *read_bytes* it doesn't care whether or not actual + physical disk I/O occurred. + - **write_chars** *(Linux)*: the amount of bytes which this process passed + to ``write()`` and ``pwrite()`` syscalls (cumulative). + Differently from *write_bytes* it doesn't care whether or not actual + physical disk I/O occurred. + + Windows specific: + + - **other_count** *(Windows)*: the number of I/O operations performed + other than read and write operations. + - **other_bytes** *(Windows)*: the number of bytes transferred during + operations other than read and write operations. - >>> import psutil - >>> p = psutil.Process() - >>> p.io_counters() - pio(read_count=454556, write_count=3456, read_bytes=110592, write_bytes=0) + >>> import psutil + >>> p = psutil.Process() + >>> p.io_counters() + pio(read_count=454556, write_count=3456, read_bytes=110592, write_bytes=0, read_chars=769931, write_chars=203) + + Availability: Linux, BSD, Windows, AIX - Availability: all platforms except OSX and Solaris + .. versionchanged:: 5.2.0 added *read_chars* and *write_chars* on Linux; + added *other_count* and *other_bytes* on Windows. .. method:: num_ctx_switches() The number voluntary and involuntary context switches performed by - this process. + this process (cumulative). + + .. versionchanged:: 5.4.1 added AIX support .. method:: num_fds() - The number of file descriptors used by this process. + The number of file descriptors currently opened by this process + (non cumulative). Availability: UNIX .. method:: num_handles() - The number of handles used by this process. + The number of handles currently used by this process (non cumulative). Availability: Windows .. method:: num_threads() - The number of threads used by this process. + The number of threads currently used by this process (non cumulative). .. method:: threads() - Return threads opened by process as a list of namedtuples including thread + Return threads opened by process as a list of named tuples including thread id and thread CPU times (user/system). On OpenBSD this method requires - root access. + root privileges. .. method:: cpu_times() - Return a `(user, system, children_user, children_system)` namedtuple + Return a `(user, system, children_user, children_system)` named tuple representing the accumulated process time, in seconds (see `explanation `__). On Windows and OSX only *user* and *system* are filled, the others are @@ -1107,9 +1421,9 @@ Process class .. method:: cpu_percent(interval=None) - Return a float representing the process CPU utilization as a percentage. - The returned value refers to the utilization of a single CPU, i.e. it is - not evenly split between the number of available CPU cores. + Return a float representing the process CPU utilization as a percentage + which can also be ``> 100.0`` in case of a process running multiple threads + on different CPUs. When *interval* is > ``0.0`` compares process times to system CPU times elapsed before and after the interval (blocking). When interval is ``0.0`` or ``None`` compares process times to system CPU times elapsed since last @@ -1121,30 +1435,28 @@ Process class >>> import psutil >>> p = psutil.Process() - >>> >>> # blocking >>> p.cpu_percent(interval=1) 2.0 >>> # non-blocking (percentage since last call) >>> p.cpu_percent(interval=None) 2.9 - >>> .. note:: - a percentage > 100 is legitimate as it can result from a process with - multiple threads running on different CPU cores. + the returned value can be > 100.0 in case of a process running multiple + threads on different CPU cores. .. note:: - the returned value is explcitly **not** split evenly between all CPUs - cores (differently from :func:`psutil.cpu_percent()`). - This means that a busy loop process running on a system with 2 CPU - cores will be reported as having 100% CPU utilization instead of 50%. - This was done in order to be consistent with UNIX's "top" utility + the returned value is explicitly *not* split evenly between all available + CPUs (differently from :func:`psutil.cpu_percent()`). + This means that a busy loop process running on a system with 2 logical + CPUs will be reported as having 100% CPU utilization instead of 50%. + This was done in order to be consistent with ``top`` UNIX utility and also to make it easier to identify processes hogging CPU resources - (independently from the number of CPU cores). - It must be noted that in the example above taskmgr.exe on Windows will - report 50% usage instead. - To emulate Windows's taskmgr.exe behavior you can do: + independently from the number of CPUs. + It must be noted that ``taskmgr.exe`` on Windows does not behave like + this (it would report 50% usage instead). + To emulate Windows ``taskmgr.exe`` behavior you can do: ``p.cpu_percent() / psutil.cpu_count()``. .. warning:: @@ -1156,64 +1468,82 @@ Process class Get or set process current `CPU affinity `__. - CPU affinity consists in telling the OS to run a certain process on a - limited set of CPUs only. The number of eligible CPUs can be obtained with - ``list(range(psutil.cpu_count()))``. ``ValueError`` will be raise on set - in case an invalid CPU number is specified. + CPU affinity consists in telling the OS to run a process on a limited set + of CPUs only (on Linux cmdline, ``taskset`` command is typically used). + If no argument is passed it returns the current CPU affinity as a list + of integers. + If passed it must be a list of integers specifying the new CPUs affinity. + If an empty list is passed all eligible CPUs are assumed (and set). + On some systems such as Linux this may not necessarily mean all available + logical CPUs as in ``list(range(psutil.cpu_count()))``). >>> import psutil >>> psutil.cpu_count() 4 >>> p = psutil.Process() - >>> p.cpu_affinity() # get + >>> # get + >>> p.cpu_affinity() [0, 1, 2, 3] - >>> p.cpu_affinity([0]) # set; from now on, process will run on CPU #0 only + >>> # set; from now on, process will run on CPU #0 and #1 only + >>> p.cpu_affinity([0, 1]) >>> p.cpu_affinity() - [0] - >>> - >>> # reset affinity against all CPUs - >>> all_cpus = list(range(psutil.cpu_count())) - >>> p.cpu_affinity(all_cpus) - >>> + [0, 1] + >>> # reset affinity against all eligible CPUs + >>> p.cpu_affinity([]) Availability: Linux, Windows, FreeBSD .. versionchanged:: 2.2.0 added support for FreeBSD + .. versionchanged:: 5.1.0 an empty list can be passed to set affinity + against all eligible CPUs. + + .. method:: cpu_num() + + Return what CPU this process is currently running on. + The returned number should be ``<=`` :func:`psutil.cpu_count()`. + On FreeBSD certain kernel process may return ``-1``. + It may be used in conjunction with ``psutil.cpu_percent(percpu=True)`` to + observe the system workload distributed across multiple CPUs as shown by + `cpu_distribution.py `__ example script. + + Availability: Linux, FreeBSD, SunOS + + .. versionadded:: 5.1.0 .. method:: memory_info() - Return a namedtuple with variable fields depending on the platform + Return a named tuple with variable fields depending on the platform representing memory information about the process. The "portable" fields available on all plaforms are `rss` and `vms`. All numbers are expressed in bytes. - +---------+---------+-------+---------+------------------------------+ - | Linux | OSX | BSD | Solaris | Windows | - +=========+=========+=======+=========+==============================+ - | rss | rss | rss | rss | rss (alias for ``wset``) | - +---------+---------+-------+---------+------------------------------+ - | vms | vms | vms | vms | vms (alias for ``pagefile``) | - +---------+---------+-------+---------+------------------------------+ - | shared | pfaults | text | | num_page_faults | - +---------+---------+-------+---------+------------------------------+ - | text | pageins | data | | peak_wset | - +---------+---------+-------+---------+------------------------------+ - | lib | | stack | | wset | - +---------+---------+-------+---------+------------------------------+ - | data | | | | peak_paged_pool | - +---------+---------+-------+---------+------------------------------+ - | dirty | | | | paged_pool | - +---------+---------+-------+---------+------------------------------+ - | | | | | peak_nonpaged_pool | - +---------+---------+-------+---------+------------------------------+ - | | | | | nonpaged_pool | - +---------+---------+-------+---------+------------------------------+ - | | | | | pagefile | - +---------+---------+-------+---------+------------------------------+ - | | | | | peak_pagefile | - +---------+---------+-------+---------+------------------------------+ - | | | | | private | - +---------+---------+-------+---------+------------------------------+ + +---------+---------+-------+---------+-----+------------------------------+ + | Linux | OSX | BSD | Solaris | AIX | Windows | + +=========+=========+=======+=========+=====+==============================+ + | rss | rss | rss | rss | rss | rss (alias for ``wset``) | + +---------+---------+-------+---------+-----+------------------------------+ + | vms | vms | vms | vms | vms | vms (alias for ``pagefile``) | + +---------+---------+-------+---------+-----+------------------------------+ + | shared | pfaults | text | | | num_page_faults | + +---------+---------+-------+---------+-----+------------------------------+ + | text | pageins | data | | | peak_wset | + +---------+---------+-------+---------+-----+------------------------------+ + | lib | | stack | | | wset | + +---------+---------+-------+---------+-----+------------------------------+ + | data | | | | | peak_paged_pool | + +---------+---------+-------+---------+-----+------------------------------+ + | dirty | | | | | paged_pool | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | peak_nonpaged_pool | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | nonpaged_pool | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | pagefile | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | peak_pagefile | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | private | + +---------+---------+-------+---------+-----+------------------------------+ - **rss**: aka "Resident Set Size", this is the non-swapped physical memory a process has used. @@ -1262,7 +1592,7 @@ Process class pmem(rss=15491072, vms=84025344, shared=5206016, text=2555904, lib=0, data=9891840, dirty=0) .. versionchanged:: - 4.0.0 mutiple fields are returned, not only `rss` and `vms`. + 4.0.0 multiple fields are returned, not only `rss` and `vms`. .. method:: memory_info_ex() @@ -1277,12 +1607,12 @@ Process class some platform (Linux, OSX, Windows), also provides additional metrics (USS, PSS and swap). The additional metrics provide a better representation of "effective" - process memory consumption (in case of USS) as explained in detail - `here `__. + process memory consumption (in case of USS) as explained in detail in this + `blog post `__. It does so by passing through the whole process address. As such it usually requires higher user privileges than :meth:`memory_info` and is considerably slower. - On platforms where extra fields are not implented this simply returns the + On platforms where extra fields are not implemented this simply returns the same metrics as :meth:`memory_info`. - **uss** *(Linux, OSX, Windows)*: @@ -1321,7 +1651,7 @@ Process class Compare process memory to total physical system memory and calculate process memory utilization as a percentage. *memtype* argument is a string that dictates what type of process memory - you want to compare against. You can choose between the namedtuple field + you want to compare against. You can choose between the named tuple field names returned by :meth:`memory_info` and :meth:`memory_full_info` (defaults to ``"rss"``). @@ -1329,7 +1659,7 @@ Process class .. method:: memory_maps(grouped=True) - Return process's mapped memory regions as a list of namedtuples whose + Return process's mapped memory regions as a list of named tuples whose fields are variable depending on the platform. This method is useful to obtain a detailed representation of process memory usage as explained @@ -1338,7 +1668,7 @@ Process class If *grouped* is ``True`` the mapped regions with the same *path* are grouped together and the different memory fields are summed. If *grouped* is ``False`` each mapped region is shown as a single entity and the - namedtuple will also include the mapped region's address space (*addr*) + named tuple will also include the mapped region's address space (*addr*) and permission set (*perms*). See `pmap.py `__ for an example application. @@ -1376,16 +1706,22 @@ Process class pmmap_grouped(path='[heap]', rss=32768, size=139264, pss=32768, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=32768, referenced=32768, anonymous=32768, swap=0), pmmap_grouped(path='[stack]', rss=2465792, size=2494464, pss=2465792, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=2465792, referenced=2277376, anonymous=2465792, swap=0), ...] - >>> + >>> p.memory_maps(grouped=False) + [pmmap_ext(addr='00400000-006ea000', perms='r-xp', path='/usr/bin/python2.7', rss=2293760, size=3055616, pss=1157120, shared_clean=2273280, shared_dirty=0, private_clean=20480, private_dirty=0, referenced=2293760, anonymous=0, swap=0), + pmmap_ext(addr='008e9000-008eb000', perms='r--p', path='/usr/bin/python2.7', rss=8192, size=8192, pss=6144, shared_clean=4096, shared_dirty=0, private_clean=0, private_dirty=4096, referenced=8192, anonymous=4096, swap=0), + pmmap_ext(addr='008eb000-00962000', perms='rw-p', path='/usr/bin/python2.7', rss=417792, size=487424, pss=317440, shared_clean=200704, shared_dirty=0, private_clean=16384, private_dirty=200704, referenced=417792, anonymous=200704, swap=0), + pmmap_ext(addr='00962000-00985000', perms='rw-p', path='[anon]', rss=139264, size=143360, pss=139264, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=139264, referenced=139264, anonymous=139264, swap=0), + pmmap_ext(addr='02829000-02ccf000', perms='rw-p', path='[heap]', rss=4743168, size=4874240, pss=4743168, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=4743168, referenced=4718592, anonymous=4743168, swap=0), + ...] - Availability: All platforms except OpenBSD and NetBSD. + Availability: All platforms except OpenBSD, NetBSD and AIX. .. method:: children(recursive=False) - Return the children of this process as a list of :Class:`Process` objects, - preemptively checking whether PID has been reused. If recursive is `True` - return all the parent descendants. - Example assuming *A == this process*: + Return the children of this process as a list of :class:`Process` + instances. + If recursive is `True` return all the parent descendants. + Pseudo code example assuming *A == this process*: :: A ─┐ @@ -1403,14 +1739,21 @@ Process class Note that in the example above if process X disappears process Y won't be returned either as the reference to process A is lost. + This concept is well summaried by this + `unit test `__. + See also how to `kill a process tree <#kill-process-tree>`__ and + `terminate my children <#terminate-my-children>`__. .. method:: open_files() - Return regular files opened by process as a list of namedtuples including + Return regular files opened by process as a list of named tuples including the following fields: - **path**: the absolute file name. - **fd**: the file descriptor number; on Windows this is always ``-1``. + + Linux only: + - **position** (*Linux*): the file (offset) position. - **mode** (*Linux*): a string indicating how the file was opened, similarly `open `__'s @@ -1428,22 +1771,23 @@ Process class >>> f = open('file.ext', 'w') >>> p = psutil.Process() >>> p.open_files() - [popenfile(path='/home/giampaolo/svn/psutil/setup.py', fd=3, position=0, mode='r', flags=32768), - popenfile(path='/var/log/monitd', fd=4, position=235542, mode='a', flags=33793)] + [popenfile(path='/home/giampaolo/svn/psutil/file.ext', fd=3, position=0, mode='w', flags=32769)] .. warning:: - on Windows this is not fully reliable as due to some limitations of the - Windows API the underlying implementation may hang when retrieving - certain file handles. - In order to work around that psutil on Windows Vista (and higher) spawns - a thread and kills it if it's not responding after 100ms. - That implies that on Windows this method is not guaranteed to enumerate - all regular file handles (see full - `discussion `_). + on Windows this method is not reliable due to some limitations of the + underlying Windows API which may hang when retrieving certain file + handles. + In order to work around that psutil spawns a thread for each handle and + kills it if it's not responding after 100ms. + That implies that this method on Windows is not guaranteed to enumerate + all regular file handles (see + `issue 597 `_). + Also, it will only list files living in the C:\\ drive (see + `issue 1020 `_). .. warning:: - on BSD this method can return files with a 'null' path due to a kernel - bug hence it's not reliable + on BSD this method can return files with a null path ("") due to a + kernel bug, hence it's not reliable (see `issue 595 `_). .. versionchanged:: @@ -1454,29 +1798,27 @@ Process class .. method:: connections(kind="inet") - Return socket connections opened by process as a list of namedtuples. + Return socket connections opened by process as a list of named tuples. To get system-wide connections use :func:`psutil.net_connections()`. - Every namedtuple provides 6 attributes: + Every named tuple provides 6 attributes: - **fd**: the socket file descriptor. This can be passed to `socket.fromfd() `__ to obtain a usable socket object. - This is only available on UNIX; on Windows ``-1`` is always returned. + On Windows, FreeBSD and SunOS this is always set to ``-1``. - **family**: the address family, either `AF_INET `__, `AF_INET6 `__ or `AF_UNIX `__. - - **type**: the address type, either `SOCK_STREAM - `__ or - `SOCK_DGRAM - `__. - - **laddr**: the local address as a ``(ip, port)`` tuple or a ``path`` - in case of AF_UNIX sockets. - - **raddr**: the remote address as a ``(ip, port)`` tuple or an absolute - ``path`` in case of UNIX sockets. + - **type**: the address type, either + `SOCK_STREAM `__ or + `SOCK_DGRAM `__. + - **laddr**: the local address as a ``(ip, port)`` named tuple or a ``path`` + in case of AF_UNIX sockets. For UNIX sockets see notes below. + - **raddr**: the remote address as a ``(ip, port)`` named tuple or an + absolute ``path`` in case of UNIX sockets. When the remote endpoint is not connected you'll get an empty tuple - (AF_INET) or ``None`` (AF_UNIX). - On Linux AF_UNIX sockets will always have this set to ``None``. + (AF_INET*) or ``""`` (AF_UNIX). For UNIX sockets see notes below. - **status**: represents the status of a TCP connection. The return value is one of the :data:`psutil.CONN_* ` constants. For UDP and UNIX sockets this is always going to be @@ -1488,27 +1830,27 @@ Process class +----------------+-----------------------------------------------------+ | **Kind value** | **Connections using** | +================+=====================================================+ - | "inet" | IPv4 and IPv6 | + | ``"inet"`` | IPv4 and IPv6 | +----------------+-----------------------------------------------------+ - | "inet4" | IPv4 | + | ``"inet4"`` | IPv4 | +----------------+-----------------------------------------------------+ - | "inet6" | IPv6 | + | ``"inet6"`` | IPv6 | +----------------+-----------------------------------------------------+ - | "tcp" | TCP | + | ``"tcp"`` | TCP | +----------------+-----------------------------------------------------+ - | "tcp4" | TCP over IPv4 | + | ``"tcp4"`` | TCP over IPv4 | +----------------+-----------------------------------------------------+ - | "tcp6" | TCP over IPv6 | + | ``"tcp6"`` | TCP over IPv6 | +----------------+-----------------------------------------------------+ - | "udp" | UDP | + | ``"udp"`` | UDP | +----------------+-----------------------------------------------------+ - | "udp4" | UDP over IPv4 | + | ``"udp4"`` | UDP over IPv4 | +----------------+-----------------------------------------------------+ - | "udp6" | UDP over IPv6 | + | ``"udp6"`` | UDP over IPv6 | +----------------+-----------------------------------------------------+ - | "unix" | UNIX socket (both UDP and TCP protocols) | + | ``"unix"`` | UNIX socket (both UDP and TCP protocols) | +----------------+-----------------------------------------------------+ - | "all" | the sum of all the possible families and protocols | + | ``"all"`` | the sum of all the possible families and protocols | +----------------+-----------------------------------------------------+ Example: @@ -1518,10 +1860,27 @@ Process class >>> p.name() 'firefox' >>> p.connections() - [pconn(fd=115, family=, type=, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED'), - pconn(fd=117, family=, type=, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING'), - pconn(fd=119, family=, type=, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED'), - pconn(fd=123, family=, type=, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT')] + [pconn(fd=115, family=, type=, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED'), + pconn(fd=117, family=, type=, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING'), + pconn(fd=119, family=, type=, laddr=addr(ip='10.0.0.1', port=60759), raddr=addr(ip='72.14.234.104', port=80), status='ESTABLISHED'), + pconn(fd=123, family=, type=, laddr=addr(ip='10.0.0.1', port=51314), raddr=addr(ip='72.14.234.83', port=443), status='SYN_SENT')] + + .. note:: + (Solaris) UNIX sockets are not supported. + + .. note:: + (Linux, FreeBSD) "raddr" field for UNIX sockets is always set to "". + This is a limitation of the OS. + + .. note:: + (OpenBSD) "laddr" and "raddr" fields for UNIX sockets are always set to + "". This is a limitation of the OS. + + .. versionchanged:: 5.3.0 : "laddr" and "raddr" are named tuples. + + .. note:: + (AIX) :class:`psutil.AccessDenied` is always raised unless running + as root (lsof does the same). .. method:: is_running() @@ -1542,6 +1901,8 @@ Process class On UNIX this is the same as ``os.kill(pid, sig)``. On Windows only *SIGTERM*, *CTRL_C_EVENT* and *CTRL_BREAK_EVENT* signals are supported and *SIGTERM* is treated as an alias for :meth:`kill()`. + See also how to `kill a process tree <#kill-process-tree>`__ and + `terminate my children <#terminate-my-children>`__. .. versionchanged:: 3.2.0 support for CTRL_C_EVENT and CTRL_BREAK_EVENT signals on Windows @@ -1567,27 +1928,36 @@ Process class whether PID has been reused. On UNIX this is the same as ``os.kill(pid, signal.SIGTERM)``. On Windows this is an alias for :meth:`kill`. + See also how to `kill a process tree <#kill-process-tree>`__ and + `terminate my children <#terminate-my-children>`__. .. method:: kill() - Kill the current process by using *SIGKILL* signal preemptively - checking whether PID has been reused. - On UNIX this is the same as ``os.kill(pid, signal.SIGKILL)``. - On Windows this is done by using - `TerminateProcess `__. + Kill the current process by using *SIGKILL* signal preemptively + checking whether PID has been reused. + On UNIX this is the same as ``os.kill(pid, signal.SIGKILL)``. + On Windows this is done by using + `TerminateProcess `__. + See also how to `kill a process tree <#kill-process-tree>`__ and + `terminate my children <#terminate-my-children>`__. .. method:: wait(timeout=None) - Wait for process termination and if the process is a children of the - current one also return the exit code, else ``None``. On Windows there's + Wait for process termination and if the process is a child of the current + one also return the exit code, else ``None``. On Windows there's no such limitation (exit code is always returned). If the process is already terminated immediately return ``None`` instead of raising - :class:`NoSuchProcess`. If *timeout* is specified and process is still - alive raise :class:`TimeoutExpired` exception. It can also be used in a - non-blocking fashion by specifying ``timeout=0`` in which case it will - either return immediately or raise :class:`TimeoutExpired`. + :class:`NoSuchProcess`. + *timeout* is expressed in seconds. If specified and the process is still + alive raise :class:`TimeoutExpired` exception. + ``timeout=0`` can be used in non-blocking apps: it will either return + immediately or raise :class:`TimeoutExpired`. To wait for multiple processes use :func:`psutil.wait_procs()`. + >>> import psutil + >>> p = psutil.Process(9891) + >>> p.terminate() + >>> p.wait() Popen class ----------- @@ -1742,46 +2112,58 @@ Constants .. _const-oses: .. data:: POSIX - WINDOWS - LINUX - OSX - FREEBSD - NETBSD - OPENBSD - BSD - SUNOS +.. data:: WINDOWS +.. data:: LINUX +.. data:: OSX +.. data:: FREEBSD +.. data:: NETBSD +.. data:: OPENBSD +.. data:: BSD +.. data:: SUNOS +.. data:: AIX ``bool`` constants which define what platform you're on. E.g. if on Windows, - *WINDOWS* constant will be ``True``, all others will be ``False``. + :const:`WINDOWS` constant will be ``True``, all others will be ``False``. .. versionadded:: 4.0.0 + .. versionchanged:: 5.4.0 added AIX .. _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, Solaris and AIX (defaults to + ``"/proc"``). You may want to re-set this constant right after importing psutil in case - your /proc filesystem is mounted elsewhere. - - Availability: Linux, Solaris + 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, AIX .. versionadded:: 3.2.3 .. versionchanged:: 3.4.2 also available on Solaris. + .. versionchanged:: 5.4.0 also available on AIX. .. _const-pstatus: .. data:: STATUS_RUNNING - STATUS_SLEEPING - STATUS_DISK_SLEEP - STATUS_STOPPED - STATUS_TRACING_STOP - STATUS_ZOMBIE - STATUS_DEAD - STATUS_WAKE_KILL - STATUS_WAKING - STATUS_IDLE (OSX, FreeBSD) - STATUS_LOCKED (FreeBSD) - STATUS_WAITING (FreeBSD) - STATUS_SUSPENDED (NetBSD) +.. data:: STATUS_SLEEPING +.. data:: STATUS_DISK_SLEEP +.. data:: STATUS_STOPPED +.. data:: STATUS_TRACING_STOP +.. data:: STATUS_ZOMBIE +.. data:: STATUS_DEAD +.. data:: STATUS_WAKE_KILL +.. data:: STATUS_WAKING +.. data:: STATUS_IDLE (OSX, FreeBSD) +.. data:: STATUS_LOCKED (FreeBSD) +.. data:: STATUS_WAITING (FreeBSD) +.. data:: STATUS_SUSPENDED (NetBSD) A set of strings representing the status of a process. Returned by :meth:`psutil.Process.status()`. @@ -1790,31 +2172,31 @@ Constants .. _const-conn: .. data:: CONN_ESTABLISHED - CONN_SYN_SENT - CONN_SYN_RECV - CONN_FIN_WAIT1 - CONN_FIN_WAIT2 - CONN_TIME_WAIT - CONN_CLOSE - CONN_CLOSE_WAIT - CONN_LAST_ACK - CONN_LISTEN - CONN_CLOSING - CONN_NONE - CONN_DELETE_TCB (Windows) - CONN_IDLE (Solaris) - CONN_BOUND (Solaris) +.. data:: CONN_SYN_SENT +.. data:: CONN_SYN_RECV +.. data:: CONN_FIN_WAIT1 +.. data:: CONN_FIN_WAIT2 +.. data:: CONN_TIME_WAIT +.. data:: CONN_CLOSE +.. data:: CONN_CLOSE_WAIT +.. data:: CONN_LAST_ACK +.. data:: CONN_LISTEN +.. data:: CONN_CLOSING +.. data:: CONN_NONE +.. data:: CONN_DELETE_TCB (Windows) +.. data:: CONN_IDLE (Solaris) +.. data:: CONN_BOUND (Solaris) A set of strings representing the status of a TCP connection. Returned by :meth:`psutil.Process.connections()` (`status` field). .. _const-prio: .. data:: ABOVE_NORMAL_PRIORITY_CLASS - BELOW_NORMAL_PRIORITY_CLASS - HIGH_PRIORITY_CLASS - IDLE_PRIORITY_CLASS - NORMAL_PRIORITY_CLASS - REALTIME_PRIORITY_CLASS +.. data:: BELOW_NORMAL_PRIORITY_CLASS +.. data:: HIGH_PRIORITY_CLASS +.. data:: IDLE_PRIORITY_CLASS +.. data:: NORMAL_PRIORITY_CLASS +.. data:: REALTIME_PRIORITY_CLASS A set of integers representing the priority of a process on Windows (see `MSDN documentation `__). @@ -1830,9 +2212,9 @@ Constants .. _const-ioprio: .. data:: IOPRIO_CLASS_NONE - IOPRIO_CLASS_RT - IOPRIO_CLASS_BE - IOPRIO_CLASS_IDLE +.. data:: IOPRIO_CLASS_RT +.. data:: IOPRIO_CLASS_BE +.. data:: IOPRIO_CLASS_IDLE A set of integers representing the I/O priority of a process on Linux. They can be used in conjunction with :meth:`psutil.Process.ionice()` to get or set @@ -1852,28 +2234,28 @@ Constants Availability: Linux .. versionchanged:: - 3.0.0 on Python >= 3.4 thse constants are + 3.0.0 on Python >= 3.4 these constants are `enums `__ instead of a plain integer. .. _const-rlimit: .. data:: RLIM_INFINITY - RLIMIT_AS - RLIMIT_CORE - RLIMIT_CPU - RLIMIT_DATA - RLIMIT_FSIZE - RLIMIT_LOCKS - RLIMIT_MEMLOCK - RLIMIT_MSGQUEUE - RLIMIT_NICE - RLIMIT_NOFILE - RLIMIT_NPROC - RLIMIT_RSS - RLIMIT_RTPRIO - RLIMIT_RTTIME - RLIMIT_SIGPENDING - RLIMIT_STACK +.. data:: RLIMIT_AS +.. data:: RLIMIT_CORE +.. data:: RLIMIT_CPU +.. data:: RLIMIT_DATA +.. data:: RLIMIT_FSIZE +.. data:: RLIMIT_LOCKS +.. data:: RLIMIT_MEMLOCK +.. data:: RLIMIT_MSGQUEUE +.. data:: RLIMIT_NICE +.. data:: RLIMIT_NOFILE +.. data:: RLIMIT_NPROC +.. data:: RLIMIT_RSS +.. data:: RLIMIT_RTPRIO +.. data:: RLIMIT_RTTIME +.. data:: RLIMIT_SIGPENDING +.. data:: RLIMIT_STACK Constants used for getting and setting process resource limits to be used in conjunction with :meth:`psutil.Process.rlimit()`. See @@ -1891,8 +2273,8 @@ Constants .. _const-duplex: .. data:: NIC_DUPLEX_FULL - NIC_DUPLEX_HALF - NIC_DUPLEX_UNKNOWN +.. data:: NIC_DUPLEX_HALF +.. data:: NIC_DUPLEX_UNKNOWN Constants which identifies whether a NIC (network interface card) has full or half mode speed. NIC_DUPLEX_FULL means the NIC is able to send and receive @@ -1902,6 +2284,16 @@ Constants .. versionadded:: 3.0.0 +.. _const-power: +.. data:: POWER_TIME_UNKNOWN +.. data:: POWER_TIME_UNLIMITED + + Whether the remaining time of the battery cannot be determined or is + unlimited. + May be assigned to :func:`psutil.sensors_battery()`'s *secsleft* field. + + .. versionadded:: 5.1.0 + .. _const-version-info: .. data:: version_info @@ -1911,76 +2303,540 @@ Constants >>> if psutil.version_info >= (4, 5): ... pass +---- -Development guide -================= +Unicode +======= -If you plan on hacking on psutil (e.g. want to add a new feature or fix a bug) -take a look at the -`development guide `_. +Starting from version 5.3.0 psutil fully supports unicode, see +`issue #1040 `__. +The notes below apply to *any* API returning a string such as +:meth:`Process.exe` or :meth:`Process.cwd`, including non-filesystem related +methods such as :meth:`Process.username` or :meth:`WindowsService.description`: + +* all strings are encoded by using the OS filesystem encoding + (``sys.getfilesystemencoding()``) which varies depending on the platform + (e.g. "UTF-8" on OSX, "mbcs" on Win) +* no API call is supposed to crash with ``UnicodeDecodeError`` +* instead, in case of badly encoded data returned by the OS, the following error handlers are used to replace the corrupted characters in the string: + * Python 3: ``sys.getfilesystemencodeerrors()`` (PY 3.6+) or + ``"surrogatescape"`` on POSIX and ``"replace"`` on Windows + * Python 2: ``"replace"`` +* on Python 2 all APIs return bytes (``str`` type), never ``unicode`` +* on Python 2, you can go back to ``unicode`` by doing: + +.. code-block:: python + + >>> unicode(p.exe(), sys.getdefaultencoding(), errors="replace") + +Example which filters processes with a funky name working with both Python 2 +and 3:: + + # -*- coding: utf-8 -*- + import psutil, sys + + PY3 = sys.version_info[0] == 2 + LOOKFOR = u"ƒőő" + for proc in psutil.process_iter(attrs=['name']): + name = proc.info['name'] + if not PY3: + name = unicode(name, sys.getdefaultencoding(), errors="replace") + if LOOKFOR == name: + print("process %s found" % p) + +Recipes +======= + +Follows a collection of utilities and examples which are common but not generic +enough to be part of the public API. +Find process by name +-------------------- -Q&A -=== +Check string against :meth:`Process.name()`: + +:: + + import psutil + + def find_procs_by_name(name): + "Return a list of processes matching 'name'." + ls = [] + for p in psutil.process_iter(attrs=['name']): + if p.info['name'] == name: + ls.append(p) + return ls + +A bit more advanced, check string against :meth:`Process.name()`, +:meth:`Process.exe()` and :meth:`Process.cmdline()`: + +:: + + import os + import psutil + + def find_procs_by_name(name): + "Return a list of processes matching 'name'." + ls = [] + for p in psutil.process_iter(attrs=["name", "exe", "cmdline"]): + if name == p.info['name'] or \ + p.info['exe'] and os.path.basename(p.info['exe']) == name or \ + p.info['cmdline'] and p.info['cmdline'][0] == name: + ls.append(p) + return ls + +Kill process tree +----------------- + +:: + + import os + import signal + import psutil + + def kill_proc_tree(pid, sig=signal.SIGTERM, include_parent=True, + timeout=None, on_terminate=None): + """Kill a process tree (including grandchildren) with signal + "sig" and return a (gone, still_alive) tuple. + "on_terminate", if specified, is a callabck function which is + called as soon as a child terminates. + """ + if pid == os.getpid(): + raise RuntimeError("I refuse to kill myself") + parent = psutil.Process(pid) + children = parent.children(recursive=True) + if include_parent: + children.append(parent) + for p in children: + p.send_signal(sig) + gone, alive = psutil.wait_procs(children, timeout=timeout, + callback=on_terminate) + return (gone, alive) + +Terminate my children +--------------------- + +This may be useful in unit tests whenever sub-processes are started. +This will help ensure that no extra children (zombies) stick around to hog +resources. + +:: + + import psutil + + def reap_children(timeout=3): + "Tries hard to terminate and ultimately kill all the children of this process." + def on_terminate(proc): + print("process {} terminated with exit code {}".format(proc, proc.returncode)) + + procs = psutil.Process().children() + # send SIGTERM + for p in procs: + p.terminate() + gone, alive = psutil.wait_procs(procs, timeout=timeout, callback=on_terminate) + if alive: + # send SIGKILL + for p in alive: + print("process {} survived SIGTERM; trying SIGKILL" % p) + p.kill() + gone, alive = psutil.wait_procs(alive, timeout=timeout, callback=on_terminate) + if alive: + # give up + for p in alive: + print("process {} survived SIGKILL; giving up" % p) + +Filtering and sorting processes +------------------------------- + +This is a collection of one-liners showing how to use :func:`process_iter()` in +order to filter for processes and sort them. + +Setup:: + + >>> import psutil + >>> from pprint import pprint as pp + +Processes having "python" in their name:: + + >>> pp([p.info for p in psutil.process_iter(attrs=['pid', 'name']) if 'python' in p.info['name']]) + [{'name': 'python3', 'pid': 21947}, + {'name': 'python', 'pid': 23835}] + +Processes owned by user:: + + >>> import getpass + >>> pp([(p.pid, p.info['name']) for p in psutil.process_iter(attrs=['name', 'username']) if p.info['username'] == getpass.getuser()]) + (16832, 'bash'), + (19772, 'ssh'), + (20492, 'python')] + +Processes actively running:: + + >>> pp([(p.pid, p.info) for p in psutil.process_iter(attrs=['name', 'status']) if p.info['status'] == psutil.STATUS_RUNNING]) + [(1150, {'name': 'Xorg', 'status': 'running'}), + (1776, {'name': 'unity-panel-service', 'status': 'running'}), + (20492, {'name': 'python', 'status': 'running'})] + +Processes using log files:: + + >>> import os + >>> import psutil + >>> for p in psutil.process_iter(attrs=['name', 'open_files']): + ... for file in p.info['open_files'] or []: + ... if os.path.splitext(file.path)[1] == '.log': + ... print("%-5s %-10s %s" % (p.pid, p.info['name'][:10], file.path)) + ... + 1510 upstart /home/giampaolo/.cache/upstart/unity-settings-daemon.log + 2174 nautilus /home/giampaolo/.local/share/gvfs-metadata/home-ce08efac.log + 2650 chrome /home/giampaolo/.config/google-chrome/Default/data_reduction_proxy_leveldb/000003.log + +Processes consuming more than 500M of memory:: + + >>> pp([(p.pid, p.info['name'], p.info['memory_info'].rss) for p in psutil.process_iter(attrs=['name', 'memory_info']) if p.info['memory_info'].rss > 500 * 1024 * 1024]) + [(2650, 'chrome', 532324352), + (3038, 'chrome', 1120088064), + (21915, 'sublime_text', 615407616)] + +Top 3 most memory consuming processes:: + + >>> pp([(p.pid, p.info) for p in sorted(psutil.process_iter(attrs=['name', 'memory_percent']), key=lambda p: p.info['memory_percent'])][-3:]) + [(21915, {'memory_percent': 3.6815453247662737, 'name': 'sublime_text'}), + (3038, {'memory_percent': 6.732935429979187, 'name': 'chrome'}), + (3249, {'memory_percent': 8.994554843376399, 'name': 'chrome'})] + +Top 3 processes which consumed the most CPU time:: + + >>> pp([(p.pid, p.info['name'], sum(p.info['cpu_times'])) for p in sorted(psutil.process_iter(attrs=['name', 'cpu_times']), key=lambda p: sum(p.info['cpu_times'][:2]))][-3:]) + [(2721, 'chrome', 10219.73), + (1150, 'Xorg', 11116.989999999998), + (2650, 'chrome', 18451.97)] + +Top 3 processes which caused the most I/O:: + + >>> pp([(p.pid, p.info['name']) for p in sorted(psutil.process_iter(attrs=['name', 'io_counters']), key=lambda p: p.info['io_counters'] and p.info['io_counters'][:2])][-3:]) + [(21915, 'sublime_text'), + (1871, 'pulseaudio'), + (1510, 'upstart')] + +Top 3 processes opening more file descriptors:: + + >>> pp([(p.pid, p.info) for p in sorted(psutil.process_iter(attrs=['name', 'num_fds']), key=lambda p: p.info['num_fds'])][-3:]) + [(21915, {'name': 'sublime_text', 'num_fds': 105}), + (2721, {'name': 'chrome', 'num_fds': 185}), + (2650, {'name': 'chrome', 'num_fds': 354})] + +FAQs +==== * Q: What Windows versions are supported? -* A: From Windows **Vista** onwards. Latest binary (wheel / exe) release - supporting Windows **2000**, **XP** and **2003 server** which can installed - via pip without a compiler being installed is +* A: From Windows **Vista** onwards, both 32 and 64 bit versions. + Latest binary (wheel / exe) release which supports Windows **2000**, **XP** + and **2003 server** is `psutil 3.4.2 `__. - More recent psutil versions may still be compiled from sources and work - (more or less) but they are no longer being tested or maintained. + On such old systems psutil is no longer tested or maintained, but it can + still be compiled from sources (you'll need `Visual Studio <(https://github.com/giampaolo/psutil/blob/master/INSTALL.rst#windows>`__) + and it should "work" (more or less). + +---- + +* Q: What Python versions are supported? +* A: From 2.6 to 3.6, both 32 and 64 bit versions. Last version supporting + Python 2.4 and 2.5 is `psutil 2.1.3 `__. + PyPy is also known to work. + +---- + +* Q: What SunOS versions are supported? +* A: From Solaris 10 onwards. + +---- + +* Q: Why do I get :class:`AccessDenied` for certain processes? +* A: This may happen when you query processess owned by another user, + especially on `OSX `__ and + Windows. + Unfortunately there's not much you can do about this except running the + Python process with higher privileges. + On Unix you may run the the Python process as root or use the SUID bit + (this is the trick used by tools such as ``ps`` and ``netstat``). + On Windows you may run the Python process as NT AUTHORITY\\SYSTEM or install + the Python script as a Windows service (this is the trick used by tools + such as ProcessHacker). +---- + +* Q: What about load average? +* A: psutil does not expose any load average function as it's already available + in python as + `os.getloadavg `__. + +Running tests +============= + +There are two ways of running tests. If psutil is already installed use:: + + $ python -m psutil.tests + +You can use this method as a quick way to make sure psutil fully works on your +platform. If you have a copy of the source code you can also use:: + + $ make test + +Development guide +================= + +If you plan on hacking on psutil (e.g. want to add a new feature or fix a bug) +take a look at the +`development guide `_. Timeline ======== -- 2016-11-06: `5.5.0 `__ - `what's new `__ -- 2016-10-26: `4.4.2 `__ - `what's new `__ -- 2016-10-25: `4.4.1 `__ - `what's new `__ -- 2016-10-23: `4.4.0 `__ - `what's new `__ -- 2016-09-01: `4.3.1 `__ - `what's new `__ -- 2016-06-18: `4.3.0 `__ - `what's new `__ -- 2016-05-15: `4.2.0 `__ - `what's new `__ -- 2016-03-12: `4.1.0 `__ - `what's new `__ -- 2016-02-17: `4.0.0 `__ - `what's new `__ -- 2016-01-20: `3.4.2 `__ - `what's new `__ -- 2016-01-15: `3.4.1 `__ - `what's new `__ -- 2015-11-25: `3.3.0 `__ - `what's new `__ -- 2015-10-04: `3.2.2 `__ - `what's new `__ -- 2015-09-03: `3.2.1 `__ - `what's new `__ -- 2015-09-02: `3.2.0 `__ - `what's new `__ -- 2015-07-15: `3.1.1 `__ - `what's new `__ -- 2015-07-15: `3.1.0 `__ - `what's new `__ -- 2015-06-18: `3.0.1 `__ - `what's new `__ -- 2015-06-13: `3.0.0 `__ - `what's new `__ -- 2015-02-02: `2.2.1 `__ - `what's new `__ -- 2015-01-06: `2.2.0 `__ - `what's new `__ -- 2014-09-26: `2.1.3 `__ - `what's new `__ -- 2014-09-21: `2.1.2 `__ - `what's new `__ -- 2014-04-30: `2.1.1 `__ - `what's new `__ -- 2014-04-08: `2.1.0 `__ - `what's new `__ -- 2014-03-10: `2.0.0 `__ - `what's new `__ -- 2013-11-25: `1.2.1 `__ - `what's new `__ -- 2013-11-20: `1.2.0 `__ - `what's new `__ -- 2013-11-07: `1.1.3 `__ - `what's new `__ -- 2013-10-22: `1.1.2 `__ - `what's new `__ -- 2013-10-08: `1.1.1 `__ - `what's new `__ -- 2013-09-28: `1.1.0 `__ - `what's new `__ -- 2013-07-12: `1.0.1 `__ - `what's new `__ -- 2013-07-10: `1.0.0 `__ - `what's new `__ -- 2013-05-03: `0.7.1 `__ - `what's new `__ -- 2013-04-12: `0.7.0 `__ - `what's new `__ -- 2012-08-16: `0.6.1 `__ - `what's new `__ -- 2012-08-13: `0.6.0 `__ - `what's new `__ -- 2012-06-29: `0.5.1 `__ - `what's new `__ -- 2012-06-27: `0.5.0 `__ - `what's new `__ -- 2011-12-14: `0.4.1 `__ - `what's new `__ -- 2011-10-29: `0.4.0 `__ - `what's new `__ -- 2011-07-08: `0.3.0 `__ - `what's new `__ -- 2011-03-20: `0.2.1 `__ - `what's new `__ -- 2010-11-13: `0.2.0 `__ - `what's new `__ -- 2010-03-02: `0.1.3 `__ - `what's new `__ -- 2009-05-06: `0.1.2 `__ - `what's new `__ -- 2009-03-06: `0.1.1 `__ - `what's new `__ -- 2009-01-27: `0.1.0 `__ - `what's new `__ +- 2018-01-01: + `5.4.3 `__ - + `what's new `__ - + `diff `__ +- 2017-12-07: + `5.4.2 `__ - + `what's new `__ - + `diff `__ +- 2017-11-08: + `5.4.1 `__ - + `what's new `__ - + `diff `__ +- 2017-10-12: + `5.4.0 `__ - + `what's new `__ - + `diff `__ +- 2017-09-10: + `5.3.1 `__ - + `what's new `__ - + `diff `__ +- 2017-09-01: + `5.3.0 `__ - + `what's new `__ - + `diff `__ +- 2017-04-10: + `5.2.2 `__ - + `what's new `__ - + `diff `__ +- 2017-03-24: + `5.2.1 `__ - + `what's new `__ - + `diff `__ +- 2017-03-05: + `5.2.0 `__ - + `what's new `__ - + `diff `__ +- 2017-02-07: + `5.1.3 `__ - + `what's new `__ - + `diff `__ +- 2017-02-03: + `5.1.2 `__ - + `what's new `__ - + `diff `__ +- 2017-02-03: + `5.1.1 `__ - + `what's new `__ - + `diff `__ +- 2017-02-01: + `5.1.0 `__ - + `what's new `__ - + `diff `__ +- 2016-12-21: + `5.0.1 `__ - + `what's new `__ - + `diff `__ +- 2016-11-06: + `5.0.0 `__ - + `what's new `__ - + `diff `__ +- 2016-10-05: + `4.4.2 `__ - + `what's new `__ - + `diff `__ +- 2016-10-25: + `4.4.1 `__ - + `what's new `__ - + `diff `__ +- 2016-10-23: + `4.4.0 `__ - + `what's new `__ - + `diff `__ +- 2016-09-01: + `4.3.1 `__ - + `what's new `__ - + `diff `__ +- 2016-06-18: + `4.3.0 `__ - + `what's new `__ - + `diff `__ +- 2016-05-14: + `4.2.0 `__ - + `what's new `__ - + `diff `__ +- 2016-03-12: + `4.1.0 `__ - + `what's new `__ - + `diff `__ +- 2016-02-17: + `4.0.0 `__ - + `what's new `__ - + `diff `__ +- 2016-01-20: + `3.4.2 `__ - + `what's new `__ - + `diff `__ +- 2016-01-15: + `3.4.1 `__ - + `what's new `__ - + `diff `__ +- 2015-11-25: + `3.3.0 `__ - + `what's new `__ - + `diff `__ +- 2015-10-04: + `3.2.2 `__ - + `what's new `__ - + `diff `__ +- 2015-09-03: + `3.2.1 `__ - + `what's new `__ - + `diff `__ +- 2015-09-02: + `3.2.0 `__ - + `what's new `__ - + `diff `__ +- 2015-07-15: + `3.1.1 `__ - + `what's new `__ - + `diff `__ +- 2015-07-15: + `3.1.0 `__ - + `what's new `__ - + `diff `__ +- 2015-06-18: + `3.0.1 `__ - + `what's new `__ - + `diff `__ +- 2015-06-13: + `3.0.0 `__ - + `what's new `__ - + `diff `__ +- 2015-02-02: + `2.2.1 `__ - + `what's new `__ - + `diff `__ +- 2015-01-06: + `2.2.0 `__ - + `what's new `__ - + `diff `__ +- 2014-09-26: + `2.1.3 `__ - + `what's new `__ - + `diff `__ +- 2014-09-21: + `2.1.2 `__ - + `what's new `__ - + `diff `__ +- 2014-04-30: + `2.1.1 `__ - + `what's new `__ - + `diff `__ +- 2014-04-08: + `2.1.0 `__ - + `what's new `__ - + `diff `__ +- 2014-03-10: + `2.0.0 `__ - + `what's new `__ - + `diff `__ +- 2013-11-25: + `1.2.1 `__ - + `what's new `__ - + `diff `__ +- 2013-11-20: + `1.2.0 `__ - + `what's new `__ - + `diff `__ +- 2013-10-22: + `1.1.2 `__ - + `what's new `__ - + `diff `__ +- 2013-10-08: + `1.1.1 `__ - + `what's new `__ - + `diff `__ +- 2013-09-28: + `1.1.0 `__ - + `what's new `__ - + `diff `__ +- 2013-07-12: + `1.0.1 `__ - + `what's new `__ - + `diff `__ +- 2013-07-10: + `1.0.0 `__ - + `what's new `__ - + `diff `__ +- 2013-05-03: + `0.7.1 `__ - + `what's new `__ - + `diff `__ +- 2013-04-12: + `0.7.0 `__ - + `what's new `__ - + `diff `__ +- 2012-08-16: + `0.6.1 `__ - + `what's new `__ - + `diff `__ +- 2012-08-13: + `0.6.0 `__ - + `what's new `__ - + `diff `__ +- 2012-06-29: + `0.5.1 `__ - + `what's new `__ - + `diff `__ +- 2012-06-27: + `0.5.0 `__ - + `what's new `__ - + `diff `__ +- 2011-12-14: + `0.4.1 `__ - + `what's new `__ - + `diff `__ +- 2011-10-29: + `0.4.0 `__ - + `what's new `__ - + `diff `__ +- 2011-07-08: + `0.3.0 `__ - + `what's new `__ - + `diff `__ +- 2011-03-20: + `0.2.1 `__ - + `what's new `__ - + `diff `__ +- 2010-11-13: + `0.2.0 `__ - + `what's new `__ - + `diff `__ +- 2010-03-02: + `0.1.3 `__ - + `what's new `__ - + `diff `__ +- 2009-05-06: + `0.1.2 `__ - + `what's new `__ - + `diff `__ +- 2009-03-06: + `0.1.1 `__ - + `what's new `__ - + `diff `__ +- 2009-01-27: + `0.1.0 `__ - + `what's new `__ - + `diff `__ diff --git a/docs/make.bat b/docs/make.bat index 185fc951b..f8473cff8 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,32 +1,242 @@ -@echo off - -rem ========================================================================== -rem Shortcuts for various tasks, emulating UNIX "make" on Windows. -rem It is primarly intended as a shortcut for compiling / installing -rem psutil ("make.bat build", "make.bat install") and running tests -rem ("make.bat test"). -rem -rem This script is modeled after my Windows installation which uses: -rem - Visual studio 2008 for Python 2.6, 2.7, 3.2 -rem - Visual studio 2010 for Python 3.3+ -rem ...therefore it might not work on your Windows installation. -rem -rem By default C:\Python27\python.exe is used. -rem To compile for a specific Python version run: -rem set PYTHON=C:\Python34\python.exe & make.bat build -rem -rem To use a different test script: -rem set PYTHON=C:\Python34\python.exe & set TSCRIPT=foo.py & make.bat test -rem ========================================================================== - -if "%PYTHON%" == "" ( - set PYTHON=C:\Python27\python.exe -) -if "%TSCRIPT%" == "" ( - set TSCRIPT=psutil\tests\runner.py -) - -rem Needed to locate the .pypirc file and upload exes on PYPI. -set HOME=%USERPROFILE% - -%PYTHON% scripts\internal\winmake.py %1 %2 +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyftpdlib.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyftpdlib.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/make.bat b/make.bat index 185fc951b..cdabe3a61 100644 --- a/make.bat +++ b/make.bat @@ -23,10 +23,10 @@ if "%PYTHON%" == "" ( set PYTHON=C:\Python27\python.exe ) if "%TSCRIPT%" == "" ( - set TSCRIPT=psutil\tests\runner.py + set TSCRIPT=psutil\tests\__main__.py ) rem Needed to locate the .pypirc file and upload exes on PYPI. set HOME=%USERPROFILE% -%PYTHON% scripts\internal\winmake.py %1 %2 +%PYTHON% scripts\internal\winmake.py %1 %2 %3 %4 %5 %6 diff --git a/psutil/DEVNOTES b/psutil/DEVNOTES new file mode 100644 index 000000000..4fd15ea31 --- /dev/null +++ b/psutil/DEVNOTES @@ -0,0 +1,5 @@ +API REFERENCES +============== + +- psutil.sensors_battery: + https://github.com/Kentzo/Power/ diff --git a/psutil/__init__.py b/psutil/__init__.py index 3287a2c7e..5e29a7fc5 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -5,14 +5,26 @@ # found in the LICENSE file. """psutil is a cross-platform library for retrieving information on -running processes and system utilization (CPU, memory, disks, network) -in Python. +running processes and system utilization (CPU, memory, disks, network, +sensors) in Python. Supported platforms: + + - Linux + - Windows + - OSX + - FreeBSD + - OpenBSD + - NetBSD + - Sun Solaris + - AIX + +Works with Python versions from 2.6 to 3.X. """ from __future__ import division import collections import contextlib +import datetime import errno import functools import os @@ -30,6 +42,7 @@ from ._common import deprecated_method from ._common import memoize from ._common import memoize_when_activated +from ._common import wrap_numbers as _wrap_numbers from ._compat import callable from ._compat import long from ._compat import PY3 as _PY3 @@ -62,6 +75,7 @@ from ._common import NIC_DUPLEX_HALF from ._common import NIC_DUPLEX_UNKNOWN +from ._common import AIX from ._common import BSD from ._common import FREEBSD # NOQA from ._common import LINUX @@ -72,6 +86,12 @@ from ._common import SUNOS from ._common import WINDOWS +from ._exceptions import AccessDenied +from ._exceptions import Error +from ._exceptions import NoSuchProcess +from ._exceptions import TimeoutExpired +from ._exceptions import ZombieProcess + if LINUX: # This is public API and it will be retrieved from _pslinux.py # via sys.modules. @@ -143,7 +163,14 @@ from ._pssunos import CONN_BOUND # NOQA from ._pssunos import CONN_IDLE # NOQA - # This is public API and it will be retrieved from _pssunos.py + # This is public writable API which is read from _pslinux.py and + # _pssunos.py via sys.modules. + PROCFS_PATH = "/proc" + +elif AIX: + from . import _psaix as _psplatform + + # This is public API and it will be retrieved from _pslinux.py # via sys.modules. PROCFS_PATH = "/proc" @@ -171,8 +198,10 @@ "NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN", + "POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED", + "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "OSX", "POSIX", "SUNOS", - "WINDOWS", + "WINDOWS", "AIX", # classes "Process", "Popen", @@ -181,17 +210,20 @@ "pid_exists", "pids", "process_iter", "wait_procs", # proc "virtual_memory", "swap_memory", # memory "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu - "cpu_stats", + "cpu_stats", # "cpu_freq", "net_io_counters", "net_connections", "net_if_addrs", # network "net_if_stats", "disk_io_counters", "disk_partitions", "disk_usage", # disk + # "sensors_temperatures", "sensors_battery", "sensors_fans" # sensors "users", "boot_time", # others ] __all__.extend(_psplatform.__extra__all__) __author__ = "Giampaolo Rodola'" -__version__ = "5.0.1" +__version__ = "5.4.3" version_info = tuple([int(num) for num in __version__.split('.')]) AF_LINK = _psplatform.AF_LINK +POWER_TIME_UNLIMITED = _common.POWER_TIME_UNLIMITED +POWER_TIME_UNKNOWN = _common.POWER_TIME_UNKNOWN _TOTAL_PHYMEM = None _timer = getattr(time, 'monotonic', time.time) @@ -219,110 +251,29 @@ # ===================================================================== -# --- exceptions +# --- Utils # ===================================================================== -class Error(Exception): - """Base exception class. All other psutil exceptions inherit - from this one. - """ - - def __init__(self, msg=""): - self.msg = msg - - def __repr__(self): - ret = "%s.%s %s" % (self.__class__.__module__, - self.__class__.__name__, self.msg) - return ret.strip() - - __str__ = __repr__ - -class NoSuchProcess(Error): - """Exception raised when a process with a certain PID doesn't - or no longer exists. - """ - - def __init__(self, pid, name=None, msg=None): - Error.__init__(self, msg) - self.pid = pid - self.name = name - self.msg = msg - if msg is None: - if name: - details = "(pid=%s, name=%s)" % (self.pid, repr(self.name)) - else: - details = "(pid=%s)" % self.pid - self.msg = "process no longer exists " + details - - -class ZombieProcess(NoSuchProcess): - """Exception raised when querying a zombie process. This is - raised on OSX, BSD and Solaris only, and not always: depending - on the query the OS may be able to succeed anyway. - On Linux all zombie processes are querable (hence this is never - raised). Windows doesn't have zombie processes. - """ - - def __init__(self, pid, name=None, ppid=None, msg=None): - Error.__init__(self, msg) - self.pid = pid - self.ppid = ppid - self.name = name - self.msg = msg - if msg is None: - args = ["pid=%s" % pid] - if name: - args.append("name=%s" % repr(self.name)) - if ppid: - args.append("ppid=%s" % self.ppid) - details = "(%s)" % ", ".join(args) - self.msg = "process still exists but it's a zombie " + details - - -class AccessDenied(Error): - """Exception raised when permission to perform an action is denied.""" - - def __init__(self, pid=None, name=None, msg=None): - Error.__init__(self, msg) - self.pid = pid - self.name = name - self.msg = msg - if msg is None: - if (pid is not None) and (name is not None): - self.msg = "(pid=%s, name=%s)" % (pid, repr(name)) - elif (pid is not None): - self.msg = "(pid=%s)" % self.pid +if hasattr(_psplatform, 'ppid_map'): + # Faster version (Windows and Linux). + _ppid_map = _psplatform.ppid_map +else: + def _ppid_map(): + """Return a {pid: ppid, ...} dict for all running processes in + one shot. Used to speed up Process.children(). + """ + ret = {} + for pid in pids(): + try: + proc = _psplatform.Process(pid) + ppid = proc.ppid() + except (NoSuchProcess, AccessDenied): + # Note: AccessDenied is unlikely to happen. + pass else: - self.msg = "" - - -class TimeoutExpired(Error): - """Raised on Process.wait(timeout) if timeout expires and process - is still alive. - """ - - def __init__(self, seconds, pid=None, name=None): - Error.__init__(self, "timeout after %s seconds" % seconds) - self.seconds = seconds - self.pid = pid - self.name = name - if (pid is not None) and (name is not None): - self.msg += " (pid=%s, name=%s)" % (pid, repr(name)) - elif (pid is not None): - self.msg += " (pid=%s)" % self.pid - - -# push exception classes into platform specific module namespace -_psplatform.NoSuchProcess = NoSuchProcess -_psplatform.ZombieProcess = ZombieProcess -_psplatform.AccessDenied = AccessDenied -_psplatform.TimeoutExpired = TimeoutExpired - - -# ===================================================================== -# --- Process class -# ===================================================================== + ret[pid] = ppid + return ret def _assert_pid_not_reused(fun): @@ -337,6 +288,22 @@ def wrapper(self, *args, **kwargs): return wrapper +def _pprint_secs(secs): + """Format seconds in a human readable form.""" + now = time.time() + secs_ago = int(now - secs) + if secs_ago < 60 * 60 * 24: + fmt = "%H:%M:%S" + else: + fmt = "%Y-%m-%d %H:%M:%S" + return datetime.datetime.fromtimestamp(secs).strftime(fmt) + + +# ===================================================================== +# --- Process class +# ===================================================================== + + class Process(object): """Represents an OS process with the given PID. If PID is omitted current process PID (os.getpid()) is used. @@ -364,10 +331,10 @@ class Process(object): - kill() To prevent this problem for all other methods you can: - - use is_running() before querying the process - - if you're continuously iterating over a set of Process - instances use process_iter() which pre-emptively checks - process identity for every yielded instance + - use is_running() before querying the process + - if you're continuously iterating over a set of Process + instances use process_iter() which pre-emptively checks + process identity for every yielded instance """ def __init__(self, pid=None): @@ -422,21 +389,26 @@ def _init(self, pid, _ignore_nsp=False): def __str__(self): try: - pid = self.pid - name = repr(self.name()) + info = collections.OrderedDict() + except AttributeError: + info = {} # Python 2.6 + info["pid"] = self.pid + try: + info["name"] = self.name() + if self._create_time: + info['started'] = _pprint_secs(self._create_time) except ZombieProcess: - details = "(pid=%s (zombie))" % self.pid + info["status"] = "zombie" except NoSuchProcess: - details = "(pid=%s (terminated))" % self.pid + info["status"] = "terminated" except AccessDenied: - details = "(pid=%s)" % (self.pid) - else: - details = "(pid=%s, name=%s)" % (pid, name) - return "%s.%s%s" % (self.__class__.__module__, - self.__class__.__name__, details) + pass + return "%s.%s(%s)" % ( + self.__class__.__module__, + self.__class__.__name__, + ", ".join(["%s=%r" % (k, v) for k, v in info.items()])) - def __repr__(self): - return "<%s at %s>" % (self.__str__(), id(self)) + __repr__ = __str__ def __eq__(self, other): # Test for equality with another Process object based @@ -453,6 +425,11 @@ def __hash__(self): self._hash = hash(self._ident) return self._hash + @property + def pid(self): + """The process PID.""" + return self._pid + # --- utility methods @contextlib.contextmanager @@ -520,11 +497,11 @@ def oneshot(self): def as_dict(self, attrs=None, ad_value=None): """Utility method returning process information as a hashable dictionary. - If 'attrs' is specified it must be a list of strings + If *attrs* is specified it must be a list of strings reflecting available Process class' attribute names (e.g. ['cpu_times', 'name']) else all public (read only) attributes are assumed. - 'ad_value' is the value which gets assigned in case + *ad_value* is the value which gets assigned in case AccessDenied or ZombieProcess exception is raised when retrieving that particular process information. """ @@ -601,11 +578,6 @@ def is_running(self): # --- actual API - @property - def pid(self): - """The process PID.""" - return self._pid - @memoize_when_activated def ppid(self): """The process parent PID. @@ -620,7 +592,7 @@ def ppid(self): # Process.parent()? if POSIX: return self._proc.ppid() - else: + else: # pragma: no cover self._ppid = self._ppid or self._proc.ppid() return self._ppid @@ -767,7 +739,7 @@ def num_fds(self): """ return self._proc.num_fds() - # Linux, BSD and Windows only + # Linux, BSD, AIX and Windows only if hasattr(_psplatform.Process, "io_counters"): def io_counters(self): @@ -785,11 +757,11 @@ def io_counters(self): def ionice(self, ioclass=None, value=None): """Get or set process I/O niceness (priority). - On Linux 'ioclass' is one of the IOPRIO_CLASS_* constants. - 'value' is a number which goes from 0 to 7. The higher the + On Linux *ioclass* is one of the IOPRIO_CLASS_* constants. + *value* is a number which goes from 0 to 7. The higher the value, the lower the I/O priority of the process. - On Windows only 'ioclass' is used and it can be set to 2 + On Windows only *ioclass* is used and it can be set to 2 (normal), 1 (low) or 0 (very low). Available on Linux and Windows > Vista only. @@ -808,8 +780,8 @@ def rlimit(self, resource, limits=None): """Get or set process resource limits as a (soft, hard) tuple. - 'resource' is one of the RLIMIT_* constants. - 'limits' is supposed to be a (soft, hard) tuple. + *resource* is one of the RLIMIT_* constants. + *limits* is supposed to be a (soft, hard) tuple. See "man prlimit" for further info. Available on Linux only. @@ -824,8 +796,10 @@ def rlimit(self, resource, limits=None): def cpu_affinity(self, cpus=None): """Get or set process CPU affinity. - If specified 'cpus' must be a list of CPUs for which you + If specified, *cpus* must be a list of CPUs for which you want to set the affinity (e.g. [0, 1]). + If an empty list is passed, all egible CPUs are assumed + (and set). (Windows, Linux and BSD only). """ # Automatically remove duplicates both on get and @@ -834,8 +808,26 @@ def cpu_affinity(self, cpus=None): if cpus is None: return list(set(self._proc.cpu_affinity_get())) else: + if not cpus: + if hasattr(self._proc, "_get_eligible_cpus"): + cpus = self._proc._get_eligible_cpus() + else: + cpus = tuple(range(len(cpu_times(percpu=True)))) self._proc.cpu_affinity_set(list(set(cpus))) + # Linux, FreeBSD, SunOS + if hasattr(_psplatform.Process, "cpu_num"): + + def cpu_num(self): + """Return what CPU this process is currently running on. + The returned number should be <= psutil.cpu_count() + and <= len(psutil.cpu_percent(percpu=True)). + It may be used in conjunction with + psutil.cpu_percent(percpu=True) to observe the system + workload distributed across CPUs. + """ + return self._proc.cpu_num() + # Linux, OSX and Windows only if hasattr(_psplatform.Process, "environ"): @@ -862,19 +854,21 @@ def num_threads(self): """Return the number of threads used by this process.""" return self._proc.num_threads() - def threads(self): - """Return threads opened by process as a list of - (id, user_time, system_time) namedtuples representing - thread id and thread CPU times (user/system). - On OpenBSD this method requires root access. - """ - return self._proc.threads() + if hasattr(_psplatform.Process, "threads"): + + def threads(self): + """Return threads opened by process as a list of + (id, user_time, system_time) namedtuples representing + thread id and thread CPU times (user/system). + On OpenBSD this method requires root access. + """ + return self._proc.threads() @_assert_pid_not_reused def children(self, recursive=False): """Return the children of this process as a list of Process instances, pre-emptively checking whether PID has been reused. - If recursive is True return all the parent descendants. + If *recursive* is True return all the parent descendants. Example (A == this process): @@ -897,90 +891,72 @@ def children(self, recursive=False): process Y won't be listed as the reference to process A is lost. """ - if hasattr(_psplatform, 'ppid_map'): - # Windows only: obtain a {pid:ppid, ...} dict for all running - # processes in one shot (faster). - ppid_map = _psplatform.ppid_map() - else: - ppid_map = None - + ppid_map = _ppid_map() ret = [] if not recursive: - if ppid_map is None: - # 'slow' version, common to all platforms except Windows - for p in process_iter(): + for pid, ppid in ppid_map.items(): + if ppid == self.pid: try: - if p.ppid() == self.pid: - # if child happens to be older than its parent - # (self) it means child's PID has been reused - if self.create_time() <= p.create_time(): - ret.append(p) + child = Process(pid) + # if child happens to be older than its parent + # (self) it means child's PID has been reused + if self.create_time() <= child.create_time(): + ret.append(child) except (NoSuchProcess, ZombieProcess): pass - else: # pragma: no cover - # Windows only (faster) - for pid, ppid in ppid_map.items(): - if ppid == self.pid: - try: - child = Process(pid) - # if child happens to be older than its parent - # (self) it means child's PID has been reused - if self.create_time() <= child.create_time(): - ret.append(child) - except (NoSuchProcess, ZombieProcess): - pass else: - # construct a dict where 'values' are all the processes - # having 'key' as their parent - table = collections.defaultdict(list) - if ppid_map is None: - for p in process_iter(): - try: - table[p.ppid()].append(p) - except (NoSuchProcess, ZombieProcess): - pass - else: # pragma: no cover - for pid, ppid in ppid_map.items(): - try: - p = Process(pid) - table[ppid].append(p) - except (NoSuchProcess, ZombieProcess): - pass - # At this point we have a mapping table where table[self.pid] - # are the current process' children. - # Below, we look for all descendants recursively, similarly - # to a recursive function call. - checkpids = [self.pid] - for pid in checkpids: - for child in table[pid]: + # Construct a {pid: [child pids]} dict + reverse_ppid_map = collections.defaultdict(list) + for pid, ppid in ppid_map.items(): + reverse_ppid_map[ppid].append(pid) + # Recursively traverse that dict, starting from self.pid, + # such that we only call Process() on actual children + seen = set() + stack = [self.pid] + while stack: + pid = stack.pop() + if pid in seen: + # Since pids can be reused while the ppid_map is + # constructed, there may be rare instances where + # there's a cycle in the recorded process "tree". + continue + seen.add(pid) + for child_pid in reverse_ppid_map[pid]: try: + child = Process(child_pid) # if child happens to be older than its parent # (self) it means child's PID has been reused intime = self.create_time() <= child.create_time() - except (NoSuchProcess, ZombieProcess): - pass - else: if intime: ret.append(child) - if child.pid not in checkpids: - checkpids.append(child.pid) + stack.append(child_pid) + except (NoSuchProcess, ZombieProcess): + pass return ret def cpu_percent(self, interval=None): """Return a float representing the current process CPU utilization as a percentage. - When interval is 0.0 or None (default) compares process times + When *interval* is 0.0 or None (default) compares process times to system CPU times elapsed since last call, returning immediately (non-blocking). That means that the first time this is called it will return a meaningful 0.0 value. - When interval is > 0.0 compares process times to system CPU + When *interval* is > 0.0 compares process times to system CPU times elapsed before and after the interval (blocking). In this case is recommended for accuracy that this function be called with at least 0.1 seconds between calls. + A value > 100.0 can be returned in case of processes running + multiple threads on different CPU cores. + + The returned value is explicitly NOT split evenly between + all available logical CPUs. This means that a busy loop process + running on a system with 2 logical CPUs will be reported as + having 100% CPU utilization instead of 50%. + Examples: >>> import psutil @@ -998,12 +974,8 @@ def cpu_percent(self, interval=None): raise ValueError("interval is not positive (got %r)" % interval) num_cpus = cpu_count() or 1 - if POSIX: - def timer(): - return _timer() * num_cpus - else: - def timer(): - return sum(cpu_times()) + def timer(): + return _timer() * num_cpus if blocking: st1 = timer() @@ -1036,7 +1008,7 @@ def timer(): # interval was too low return 0.0 else: - # Note 1. + # Note 1: # in order to emulate "top" we multiply the value for the num # of CPU cores. This way the busy process will be reported as # having 100% (or more) usage. @@ -1045,7 +1017,7 @@ def timer(): # taskmgr.exe on Windows differs in that it will show 50% # instead. # - # Note #3: + # Note 3: # a percentage > 100 is legitimate as it can result from a # process with multiple threads running on different CPU # cores (top does the same), see: @@ -1099,7 +1071,7 @@ def memory_full_info(self): def memory_percent(self, memtype="rss"): """Compare process memory to total physical system memory and calculate process memory utilization as a percentage. - 'memtype' argument is a string that dictates what type of + *memtype* argument is a string that dictates what type of process memory you want to compare against (defaults to "rss"). The list of available strings can be obtained like this: @@ -1107,13 +1079,11 @@ def memory_percent(self, memtype="rss"): ('rss', 'vms', 'shared', 'text', 'lib', 'data', 'dirty', 'uss', 'pss') """ valid_types = list(_psplatform.pfullmem._fields) - if hasattr(_psplatform, "pfullmem"): - valid_types.extend(list(_psplatform.pfullmem._fields)) if memtype not in valid_types: raise ValueError("invalid memtype %r; valid types are %r" % ( memtype, tuple(valid_types))) - fun = self.memory_full_info if memtype in ('uss', 'pss', 'swap') else \ - self.memory_info + fun = self.memory_info if memtype in _psplatform.pmem._fields else \ + self.memory_full_info metrics = fun() value = getattr(metrics, memtype) @@ -1129,15 +1099,14 @@ def memory_percent(self, memtype="rss"): if hasattr(_psplatform.Process, "memory_maps"): # Available everywhere except OpenBSD and NetBSD. - def memory_maps(self, grouped=True): """Return process' mapped memory regions as a list of namedtuples whose fields are variable depending on the platform. - If 'grouped' is True the mapped regions with the same 'path' + If *grouped* is True the mapped regions with the same 'path' are grouped together and the different memory fields are summed. - If 'grouped' is False every mapped region is shown as a single + If *grouped* is False every mapped region is shown as a single entity and the namedtuple will also include the mapped region's address space ('addr') and permission set ('perms'). """ @@ -1165,26 +1134,31 @@ def open_files(self): return self._proc.open_files() def connections(self, kind='inet'): - """Return connections opened by process as a list of + """Return socket connections opened by process as a list of (fd, family, type, laddr, raddr, status) namedtuples. - The 'kind' parameter filters for connections that match the + The *kind* parameter filters for connections that match the following criteria: - Kind Value Connections using - inet IPv4 and IPv6 - inet4 IPv4 - inet6 IPv6 - tcp TCP - tcp4 TCP over IPv4 - tcp6 TCP over IPv6 - udp UDP - udp4 UDP over IPv4 - udp6 UDP over IPv6 - unix UNIX socket (both UDP and TCP protocols) - all the sum of all the possible families and protocols + +------------+----------------------------------------------------+ + | Kind Value | Connections using | + +------------+----------------------------------------------------+ + | inet | IPv4 and IPv6 | + | inet4 | IPv4 | + | inet6 | IPv6 | + | tcp | TCP | + | tcp4 | TCP over IPv4 | + | tcp6 | TCP over IPv6 | + | udp | UDP | + | udp4 | UDP over IPv4 | + | udp6 | UDP over IPv6 | + | unix | UNIX socket (both UDP and TCP protocols) | + | all | the sum of all the possible families and protocols | + +------------+----------------------------------------------------+ """ return self._proc.connections(kind) + # --- signals + if POSIX: def _send_signal(self, sig): assert not self.pid < 0, self.pid @@ -1211,8 +1185,8 @@ def _send_signal(self, sig): @_assert_pid_not_reused def send_signal(self, sig): - """Send a signal to process pre-emptively checking whether - PID has been reused (see signal module constants) . + """Send a signal *sig* to process pre-emptively checking + whether PID has been reused (see signal module constants) . On Windows only SIGTERM is valid and is treated as an alias for kill(). """ @@ -1280,8 +1254,8 @@ def wait(self, timeout=None): If the process is already terminated immediately return None instead of raising NoSuchProcess. - If timeout (in seconds) is specified and process is still alive - raise TimeoutExpired. + If *timeout* (in seconds) is specified and process is still + alive raise TimeoutExpired. To wait for multiple Process(es) use psutil.wait_procs(). """ @@ -1418,7 +1392,7 @@ def pid_exists(pid): _pmap = {} -def process_iter(): +def process_iter(attrs=None, ad_value=None): """Return a generator yielding a Process instance for all running processes. @@ -1431,9 +1405,18 @@ def process_iter(): The sorting order in which processes are yielded is based on their PIDs. + + *attrs* and *ad_value* have the same meaning as in + Process.as_dict(). If *attrs* is specified as_dict() is called + and the resulting dict is stored as a 'info' attribute attached + to returned Process instance. + If *attrs* is an empty list it will retrieve all process info + (slow). """ def add(pid): proc = Process(pid) + if attrs is not None: + proc.info = proc.as_dict(attrs=attrs, ad_value=ad_value) _pmap[proc.pid] = proc return proc @@ -1456,6 +1439,9 @@ def remove(pid): # use is_running() to check whether PID has been reused by # another process in which case yield a new Process instance if proc.is_running(): + if attrs is not None: + proc.info = proc.as_dict( + attrs=attrs, ad_value=ad_value) yield proc else: yield add(pid) @@ -1483,14 +1469,16 @@ def wait_procs(procs, timeout=None, callback=None): Return a (gone, alive) tuple indicating which processes are gone and which ones are still alive. - The gone ones will have a new 'returncode' attribute indicating + The gone ones will have a new *returncode* attribute indicating process exit status (may be None). - 'callback' is a function which gets called every time a process + *callback* is a function which gets called every time a process terminates (a Process instance is passed as callback argument). Function will return as soon as all processes terminate or when - timeout occurs. + *timeout* occurs. + Differently from Process.wait() it will not raise TimeoutExpired if + *timeout* occurs. Typical use case is: @@ -1567,12 +1555,11 @@ def check_gone(proc, timeout): # ===================================================================== -@memoize def cpu_count(logical=True): """Return the number of logical CPUs in the system (same as os.cpu_count() in Python 3.4). - If logical is False return the number of physical cores only + If *logical* is False return the number of physical cores only (e.g. hyper thread CPUs are excluded). Return None if undetermined. @@ -1583,15 +1570,20 @@ def cpu_count(logical=True): >>> psutil.cpu_count.cache_clear() """ if logical: - return _psplatform.cpu_count_logical() + ret = _psplatform.cpu_count_logical() else: - return _psplatform.cpu_count_physical() + ret = _psplatform.cpu_count_physical() + if ret is not None and ret < 1: + ret = None + return ret def cpu_times(percpu=False): """Return system-wide CPU times as a namedtuple. - Every CPU time represents the seconds the CPU has spent in the given mode. - The namedtuple's fields availability varies depending on the platform: + Every CPU time represents the seconds the CPU has spent in the + given mode. The namedtuple's fields availability varies depending on the + platform: + - user - system - idle @@ -1603,7 +1595,7 @@ def cpu_times(percpu=False): - guest (Linux >= 2.6.24) - guest_nice (Linux >= 3.2.0) - When percpu is True return a list of namedtuples for each CPU. + When *percpu* is True return a list of namedtuples for each CPU. First element of the list refers to first CPU, second element to second CPU and so on. The order of the list is consistent across calls. @@ -1629,21 +1621,56 @@ def cpu_times(percpu=False): traceback.print_exc() +def _cpu_tot_time(times): + """Given a cpu_time() ntuple calculates the total CPU time + (including idle time). + """ + tot = sum(times) + if LINUX: + # On Linux guest times are already accounted in "user" or + # "nice" times, so we subtract them from total. + # Htop does the same. References: + # https://github.com/giampaolo/psutil/pull/940 + # http://unix.stackexchange.com/questions/178045 + # https://github.com/torvalds/linux/blob/ + # 447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/ + # cputime.c#L158 + tot -= getattr(times, "guest", 0) # Linux 2.6.24+ + tot -= getattr(times, "guest_nice", 0) # Linux 3.2.0+ + return tot + + +def _cpu_busy_time(times): + """Given a cpu_time() ntuple calculates the busy CPU time. + We do so by subtracting all idle CPU times. + """ + busy = _cpu_tot_time(times) + busy -= times.idle + # Linux: "iowait" is time during which the CPU does not do anything + # (waits for IO to complete). On Linux IO wait is *not* accounted + # in "idle" time so we subtract it. Htop does the same. + # References: + # https://github.com/torvalds/linux/blob/ + # 447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/cputime.c#L244 + busy -= getattr(times, "iowait", 0) + return busy + + def cpu_percent(interval=None, percpu=False): """Return a float representing the current system-wide CPU utilization as a percentage. - When interval is > 0.0 compares system CPU times elapsed before + When *interval* is > 0.0 compares system CPU times elapsed before and after the interval (blocking). - When interval is 0.0 or None compares system CPU times elapsed + When *interval* is 0.0 or None compares system CPU times elapsed since last call or module import, returning immediately (non blocking). That means the first time this is called it will return a meaningless 0.0 value which you should ignore. In this case is recommended for accuracy that this function be called with at least 0.1 seconds between calls. - When percpu is True returns a list of floats representing the + When *percpu* is True returns a list of floats representing the utilization as a percentage for each CPU. First element of the list refers to first CPU, second element to second CPU and so on. @@ -1671,11 +1698,11 @@ def cpu_percent(interval=None, percpu=False): raise ValueError("interval is not positive (got %r)" % interval) def calculate(t1, t2): - t1_all = sum(t1) - t1_busy = t1_all - t1.idle + t1_all = _cpu_tot_time(t1) + t1_busy = _cpu_busy_time(t1) - t2_all = sum(t2) - t2_busy = t2_all - t2.idle + t2_all = _cpu_tot_time(t2) + t2_busy = _cpu_busy_time(t2) # this usually indicates a float precision issue if t2_busy <= t1_busy: @@ -1683,8 +1710,12 @@ def calculate(t1, t2): busy_delta = t2_busy - t1_busy all_delta = t2_all - t1_all - busy_perc = (busy_delta / all_delta) * 100 - return round(busy_perc, 1) + try: + busy_perc = (busy_delta / all_delta) * 100 + except ZeroDivisionError: + return 0.0 + else: + return round(busy_perc, 1) # system-wide usage if not percpu: @@ -1736,7 +1767,7 @@ def cpu_times_percent(interval=None, percpu=False): irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) >>> - interval and percpu arguments have the same meaning as in + *interval* and *percpu* arguments have the same meaning as in cpu_percent(). """ global _last_cpu_times_2 @@ -1747,7 +1778,7 @@ def cpu_times_percent(interval=None, percpu=False): def calculate(t1, t2): nums = [] - all_delta = sum(t2) - sum(t1) + all_delta = _cpu_tot_time(t2) - _cpu_tot_time(t1) for field in t1._fields: field_delta = getattr(t2, field) - getattr(t1, field) try: @@ -1810,6 +1841,39 @@ def cpu_stats(): return _psplatform.cpu_stats() +if hasattr(_psplatform, "cpu_freq"): + + def cpu_freq(percpu=False): + """Return CPU frequency as a nameduple including current, + min and max frequency expressed in Mhz. + + If *percpu* is True and the system supports per-cpu frequency + retrieval (Linux only) a list of frequencies is returned for + each CPU. If not a list with one element is returned. + """ + ret = _psplatform.cpu_freq() + if percpu: + return ret + else: + num_cpus = float(len(ret)) + if num_cpus == 0: + return None + elif num_cpus == 1: + return ret[0] + else: + currs, mins, maxs = 0.0, 0.0, 0.0 + for cpu in ret: + currs += cpu.current + mins += cpu.min + maxs += cpu.max + current = currs / num_cpus + min_ = mins / num_cpus + max_ = maxs / num_cpus + return _common.scpufreq(current, min_, max_) + + __all__.append("cpu_freq") + + # ===================================================================== # --- system memory related functions # ===================================================================== @@ -1833,8 +1897,8 @@ def virtual_memory(): the percentage usage calculated as (total - available) / total * 100 - used: - memory used, calculated differently depending on the platform and - designed for informational purposes only: + memory used, calculated differently depending on the platform and + designed for informational purposes only: OSX: active + inactive + wired BSD: active + wired + cached LINUX: total - free @@ -1896,9 +1960,9 @@ def swap_memory(): def disk_usage(path): - """Return disk usage statistics about the given path as a namedtuple - including total, used and free space expressed in bytes plus the - percentage usage. + """Return disk usage statistics about the given *path* as a + namedtuple including total, used and free space expressed in bytes + plus the percentage usage. """ return _psplatform.disk_usage(path) @@ -1909,13 +1973,13 @@ def disk_partitions(all=False): 'opts' field is a raw string separated by commas indicating mount options which may vary depending on the platform. - If "all" parameter is False return physical devices only and ignore + If *all* parameter is False return physical devices only and ignore all others. """ return _psplatform.disk_partitions(all) -def disk_io_counters(perdisk=False): +def disk_io_counters(perdisk=False, nowrap=True): """Return system disk I/O statistics as a namedtuple including the following fields: @@ -1923,20 +1987,35 @@ def disk_io_counters(perdisk=False): - write_count: number of writes - read_bytes: number of bytes read - write_bytes: number of bytes written - - read_time: time spent reading from disk (in milliseconds) - - write_time: time spent writing to disk (in milliseconds) + - read_time: time spent reading from disk (in ms) + - write_time: time spent writing to disk (in ms) + + Platform specific: + + - busy_time: (Linux, FreeBSD) time spent doing actual I/Os (in ms) + - read_merged_count (Linux): number of merged reads + - write_merged_count (Linux): number of merged writes - If perdisk is True return the same information for every + If *perdisk* is True return the same information for every physical disk installed on the system as a dictionary with partition names as the keys and the namedtuple described above as the values. + If *nowrap* is True it detects and adjust the numbers which overflow + and wrap (restart from 0) and add "old value" to "new value" so that + the returned numbers will always be increasing or remain the same, + but never decrease. + "disk_io_counters.cache_clear()" can be used to invalidate the + cache. + On recent Windows versions 'diskperf -y' command may need to be executed first otherwise this function won't find any disk. """ rawdict = _psplatform.disk_io_counters() if not rawdict: - raise RuntimeError("couldn't find any physical disk") + return {} if perdisk else None + if nowrap: + rawdict = _wrap_numbers(rawdict, 'psutil.disk_io_counters') nt = getattr(_psplatform, "sdiskio", _common.sdiskio) if perdisk: for disk, fields in rawdict.items(): @@ -1946,12 +2025,17 @@ def disk_io_counters(perdisk=False): return nt(*[sum(x) for x in zip(*rawdict.values())]) +disk_io_counters.cache_clear = functools.partial( + _wrap_numbers.cache_clear, 'psutil.disk_io_counters') +disk_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache" + + # ===================================================================== # --- network related functions # ===================================================================== -def net_io_counters(pernic=False): +def net_io_counters(pernic=False, nowrap=True): """Return network I/O statistics as a namedtuple including the following fields: @@ -1965,14 +2049,23 @@ def net_io_counters(pernic=False): - dropout: total number of outgoing packets which were dropped (always 0 on OSX and BSD) - If pernic is True return the same information for every + If *pernic* is True return the same information for every network interface installed on the system as a dictionary with network interface names as the keys and the namedtuple described above as the values. + + If *nowrap* is True it detects and adjust the numbers which overflow + and wrap (restart from 0) and add "old value" to "new value" so that + the returned numbers will always be increasing or remain the same, + but never decrease. + "disk_io_counters.cache_clear()" can be used to invalidate the + cache. """ rawdict = _psplatform.net_io_counters() if not rawdict: - raise RuntimeError("couldn't find any network interface") + return {} if pernic else None + if nowrap: + rawdict = _wrap_numbers(rawdict, 'psutil.net_io_counters') if pernic: for nic, fields in rawdict.items(): rawdict[nic] = _common.snetio(*fields) @@ -1981,26 +2074,34 @@ def net_io_counters(pernic=False): return _common.snetio(*[sum(x) for x in zip(*rawdict.values())]) +net_io_counters.cache_clear = functools.partial( + _wrap_numbers.cache_clear, 'psutil.net_io_counters') +net_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache" + + def net_connections(kind='inet'): - """Return system-wide connections as a list of + """Return system-wide socket connections as a list of (fd, family, type, laddr, raddr, status, pid) namedtuples. In case of limited privileges 'fd' and 'pid' may be set to -1 and None respectively. - The 'kind' parameter filters for connections that fit the + The *kind* parameter filters for connections that fit the following criteria: - Kind Value Connections using - inet IPv4 and IPv6 - inet4 IPv4 - inet6 IPv6 - tcp TCP - tcp4 TCP over IPv4 - tcp6 TCP over IPv6 - udp UDP - udp4 UDP over IPv4 - udp6 UDP over IPv6 - unix UNIX socket (both UDP and TCP protocols) - all the sum of all the possible families and protocols + +------------+----------------------------------------------------+ + | Kind Value | Connections using | + +------------+----------------------------------------------------+ + | inet | IPv4 and IPv6 | + | inet4 | IPv4 | + | inet6 | IPv6 | + | tcp | TCP | + | tcp4 | TCP over IPv4 | + | tcp6 | TCP over IPv6 | + | udp | UDP | + | udp4 | UDP over IPv4 | + | udp6 | UDP over IPv6 | + | unix | UNIX socket (both UDP and TCP protocols) | + | all | the sum of all the possible families and protocols | + +------------+----------------------------------------------------+ On OSX this function requires root privileges. """ @@ -2013,19 +2114,14 @@ def net_if_addrs(): NIC names and value is a list of namedtuples for each address assigned to the NIC. Each namedtuple includes 5 fields: - - family - - address - - netmask - - broadcast - - ptp - - 'family' can be either socket.AF_INET, socket.AF_INET6 or - psutil.AF_LINK, which refers to a MAC address. - 'address' is the primary address and it is always set. - 'netmask' and 'broadcast' and 'ptp' may be None. - 'ptp' stands for "point to point" and references the destination - address on a point to point interface (typically a VPN). - 'broadcast' and 'ptp' are mutually exclusive. + - family: can be either socket.AF_INET, socket.AF_INET6 or + psutil.AF_LINK, which refers to a MAC address. + - address: is the primary address and it is always set. + - netmask: and 'broadcast' and 'ptp' may be None. + - ptp: stands for "point to point" and references the + destination address on a point to point interface + (typically a VPN). + - broadcast: and *ptp* are mutually exclusive. Note: you can have more than one address of the same family associated with each interface. @@ -2075,6 +2171,80 @@ def net_if_stats(): return _psplatform.net_if_stats() +# ===================================================================== +# --- sensors +# ===================================================================== + + +# Linux +if hasattr(_psplatform, "sensors_temperatures"): + + def sensors_temperatures(fahrenheit=False): + """Return hardware temperatures. Each entry is a namedtuple + representing a certain hardware sensor (it may be a CPU, an + hard disk or something else, depending on the OS and its + configuration). + All temperatures are expressed in celsius unless *fahrenheit* + is set to True. + """ + def convert(n): + if n is not None: + return (float(n) * 9 / 5) + 32 if fahrenheit else n + + ret = collections.defaultdict(list) + rawdict = _psplatform.sensors_temperatures() + + for name, values in rawdict.items(): + while values: + label, current, high, critical = values.pop(0) + current = convert(current) + high = convert(high) + critical = convert(critical) + + if high and not critical: + critical = high + elif critical and not high: + high = critical + + ret[name].append( + _common.shwtemp(label, current, high, critical)) + + return dict(ret) + + __all__.append("sensors_temperatures") + + +# Linux +if hasattr(_psplatform, "sensors_fans"): + + def sensors_fans(): + """Return fans speed. Each entry is a namedtuple + representing a certain hardware sensor. + All speed are expressed in RPM (rounds per minute). + """ + return _psplatform.sensors_fans() + + __all__.append("sensors_fans") + + +# Linux, Windows, FreeBSD, OSX +if hasattr(_psplatform, "sensors_battery"): + + def sensors_battery(): + """Return battery information. If no battery is installed + returns None. + + - percent: battery power left as a percentage. + - secsleft: a rough approximation of how many seconds are left + before the battery runs out of power. May be + POWER_TIME_UNLIMITED or POWER_TIME_UNLIMITED. + - power_plugged: True if the AC power cable is connected. + """ + return _psplatform.sensors_battery() + + __all__.append("sensors_battery") + + # ===================================================================== # --- other system related functions # ===================================================================== @@ -2114,7 +2284,7 @@ def win_service_iter(): return _psplatform.win_service_iter() def win_service_get(name): - """Get a Windows service by name. + """Get a Windows service by *name*. Raise NoSuchProcess if no service with such name exists. """ return _psplatform.win_service_get(name) @@ -2127,56 +2297,48 @@ def test(): # pragma: no cover """List info of all currently running processes emulating ps aux output. """ - import datetime - today_day = datetime.date.today() - templ = "%-10s %5s %4s %4s %7s %7s %-13s %5s %7s %s" - attrs = ['pid', 'cpu_percent', 'memory_percent', 'name', 'cpu_times', - 'create_time', 'memory_info'] + templ = "%-10s %5s %4s %7s %7s %-13s %5s %7s %s" + attrs = ['pid', 'memory_percent', 'name', 'cpu_times', 'create_time', + 'memory_info'] if POSIX: attrs.append('uids') attrs.append('terminal') - print(templ % ("USER", "PID", "%CPU", "%MEM", "VSZ", "RSS", "TTY", - "START", "TIME", "COMMAND")) - for p in process_iter(): - try: - pinfo = p.as_dict(attrs, ad_value='') - except NoSuchProcess: - pass - else: - if pinfo['create_time']: - ctime = datetime.datetime.fromtimestamp(pinfo['create_time']) - if ctime.date() == today_day: - ctime = ctime.strftime("%H:%M") - else: - ctime = ctime.strftime("%b%d") + print(templ % ("USER", "PID", "%MEM", "VSZ", "RSS", "TTY", "START", "TIME", + "COMMAND")) + for p in process_iter(attrs=attrs, ad_value=''): + if p.info['create_time']: + ctime = datetime.datetime.fromtimestamp(p.info['create_time']) + if ctime.date() == today_day: + ctime = ctime.strftime("%H:%M") else: - ctime = '' - cputime = time.strftime("%M:%S", - time.localtime(sum(pinfo['cpu_times']))) - try: - user = p.username() - except Error: - user = '' - if WINDOWS and '\\' in user: - user = user.split('\\')[1] - vms = pinfo['memory_info'] and \ - int(pinfo['memory_info'].vms / 1024) or '?' - rss = pinfo['memory_info'] and \ - int(pinfo['memory_info'].rss / 1024) or '?' - memp = pinfo['memory_percent'] and \ - round(pinfo['memory_percent'], 1) or '?' - print(templ % ( - user[:10], - pinfo['pid'], - pinfo['cpu_percent'], - memp, - vms, - rss, - pinfo.get('terminal', '') or '?', - ctime, - cputime, - pinfo['name'].strip() or '?')) + ctime = ctime.strftime("%b%d") + else: + ctime = '' + cputime = time.strftime("%M:%S", + time.localtime(sum(p.info['cpu_times']))) + try: + user = p.username() + except Error: + user = '' + if WINDOWS and '\\' in user: + user = user.split('\\')[1] + vms = p.info['memory_info'] and \ + int(p.info['memory_info'].vms / 1024) or '?' + rss = p.info['memory_info'] and \ + int(p.info['memory_info'].rss / 1024) or '?' + memp = p.info['memory_percent'] and \ + round(p.info['memory_percent'], 1) or '?' + print(templ % ( + user[:10], + p.info['pid'], + memp, + vms, + rss, + p.info.get('terminal', '') or '?', + ctime, + cputime, + p.info['name'].strip() or '?')) del memoize, memoize_when_activated, division, deprecated_method diff --git a/psutil/_common.py b/psutil/_common.py index 3879a1d73..870971e41 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -4,6 +4,9 @@ """Common objects shared by __init__.py and _ps*.py modules.""" +# Note: this module is imported by setup.py so it should not import +# psutil or third-party modules. + from __future__ import division import contextlib @@ -13,7 +16,9 @@ import socket import stat import sys +import threading import warnings +from collections import defaultdict from collections import namedtuple from socket import AF_INET from socket import SOCK_DGRAM @@ -32,10 +37,14 @@ else: enum = None +# can't take it from _common.py as this script is imported by setup.py +PY3 = sys.version_info[0] == 3 + __all__ = [ - # OS constants + # constants 'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'OSX', 'POSIX', 'SUNOS', 'WINDOWS', + 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', # connection constants 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED', 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN', @@ -54,7 +63,7 @@ # utility functions 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize', 'parse_environ_block', 'path_exists_strict', 'usage_percent', - 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', + 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", ] @@ -72,6 +81,7 @@ NETBSD = sys.platform.startswith("netbsd") BSD = FREEBSD or OPENBSD or NETBSD SUNOS = sys.platform.startswith("sunos") or sys.platform.startswith("solaris") +AIX = sys.platform.startswith("aix") # =================================================================== @@ -121,6 +131,28 @@ class NicDuplex(enum.IntEnum): globals().update(NicDuplex.__members__) +# sensors_battery() +if enum is None: + POWER_TIME_UNKNOWN = -1 + POWER_TIME_UNLIMITED = -2 +else: + class BatteryTime(enum.IntEnum): + POWER_TIME_UNKNOWN = -1 + POWER_TIME_UNLIMITED = -2 + + globals().update(BatteryTime.__members__) + +# --- others + +ENCODING = sys.getfilesystemencoding() +if not PY3: + ENCODING_ERRS = "replace" +else: + try: + ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6 + except AttributeError: + ENCODING_ERRS = "surrogateescape" if POSIX else "replace" + # =================================================================== # --- namedtuples @@ -145,7 +177,7 @@ class NicDuplex(enum.IntEnum): 'errin', 'errout', 'dropin', 'dropout']) # psutil.users() -suser = namedtuple('suser', ['name', 'terminal', 'host', 'started']) +suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid']) # psutil.net_connections() sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', 'status', 'pid']) @@ -156,6 +188,15 @@ class NicDuplex(enum.IntEnum): # psutil.cpu_stats() scpustats = namedtuple( 'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls']) +# psutil.cpu_freq() +scpufreq = namedtuple('scpufreq', ['current', 'min', 'max']) +# psutil.sensors_temperatures() +shwtemp = namedtuple( + 'shwtemp', ['label', 'current', 'high', 'critical']) +# psutil.sensors_battery() +sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged']) +# psutil.sensors_battery() +sfan = namedtuple('sfan', ['label', 'current']) # --- for Process methods @@ -181,6 +222,9 @@ class NicDuplex(enum.IntEnum): pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr', 'status']) +# psutil.connections() and psutil.Process.connections() +addr = namedtuple('addr', ['ip', 'port']) + # =================================================================== # --- Process.connections() 'kind' parameter mapping @@ -209,7 +253,7 @@ class NicDuplex(enum.IntEnum): "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), }) -del AF_INET, AF_INET6, AF_UNIX, SOCK_STREAM, SOCK_DGRAM +del AF_INET, AF_UNIX, SOCK_STREAM, SOCK_DGRAM # =================================================================== @@ -344,12 +388,13 @@ def path_exists_strict(path): return True +@memoize def supports_ipv6(): """Return True if IPv6 is supported on this platform.""" - if not socket.has_ipv6 or not hasattr(socket, "AF_INET6"): + if not socket.has_ipv6 or AF_INET6 is None: return False try: - sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + sock = socket.socket(AF_INET6, socket.SOCK_STREAM) with contextlib.closing(sock): sock.bind(("::1", 0)) return True @@ -416,14 +461,115 @@ def deprecated_method(replacement): 'replcement' is the method name which will be called instead. """ def outer(fun): - msg = "%s() is deprecated; use %s() instead" % ( + msg = "%s() is deprecated and will be removed; use %s() instead" % ( fun.__name__, replacement) if fun.__doc__ is None: fun.__doc__ = msg @functools.wraps(fun) def inner(self, *args, **kwargs): - warnings.warn(msg, category=DeprecationWarning, stacklevel=2) + warnings.warn(msg, category=FutureWarning, stacklevel=2) return getattr(self, replacement)(*args, **kwargs) return inner return outer + + +class _WrapNumbers: + """Watches numbers so that they don't overflow and wrap + (reset to zero). + """ + + def __init__(self): + self.lock = threading.Lock() + self.cache = {} + self.reminders = {} + self.reminder_keys = {} + + def _add_dict(self, input_dict, name): + assert name not in self.cache + assert name not in self.reminders + assert name not in self.reminder_keys + self.cache[name] = input_dict + self.reminders[name] = defaultdict(int) + self.reminder_keys[name] = defaultdict(set) + + def _remove_dead_reminders(self, input_dict, name): + """In case the number of keys changed between calls (e.g. a + disk disappears) this removes the entry from self.reminders. + """ + old_dict = self.cache[name] + gone_keys = set(old_dict.keys()) - set(input_dict.keys()) + for gone_key in gone_keys: + for remkey in self.reminder_keys[name][gone_key]: + del self.reminders[name][remkey] + del self.reminder_keys[name][gone_key] + + def run(self, input_dict, name): + """Cache dict and sum numbers which overflow and wrap. + Return an updated copy of `input_dict` + """ + if name not in self.cache: + # This was the first call. + self._add_dict(input_dict, name) + return input_dict + + self._remove_dead_reminders(input_dict, name) + + old_dict = self.cache[name] + new_dict = {} + for key in input_dict.keys(): + input_tuple = input_dict[key] + try: + old_tuple = old_dict[key] + except KeyError: + # The input dict has a new key (e.g. a new disk or NIC) + # which didn't exist in the previous call. + new_dict[key] = input_tuple + continue + + bits = [] + for i in range(len(input_tuple)): + input_value = input_tuple[i] + old_value = old_tuple[i] + remkey = (key, i) + if input_value < old_value: + # it wrapped! + self.reminders[name][remkey] += old_value + self.reminder_keys[name][key].add(remkey) + bits.append(input_value + self.reminders[name][remkey]) + + new_dict[key] = tuple(bits) + + self.cache[name] = input_dict + return new_dict + + def cache_clear(self, name=None): + """Clear the internal cache, optionally only for function 'name'.""" + with self.lock: + if name is None: + self.cache.clear() + self.reminders.clear() + self.reminder_keys.clear() + else: + self.cache.pop(name, None) + self.reminders.pop(name, None) + self.reminder_keys.pop(name, None) + + def cache_info(self): + """Return internal cache dicts as a tuple of 3 elements.""" + with self.lock: + return (self.cache, self.reminders, self.reminder_keys) + + +def wrap_numbers(input_dict, name): + """Given an `input_dict` and a function `name`, adjust the numbers + which "wrap" (restart from zero) across different calls by adding + "old value" to "new value" and return an updated dict. + """ + with _wn.lock: + return _wn.run(input_dict, name) + + +_wn = _WrapNumbers() +wrap_numbers.cache_clear = _wn.cache_clear +wrap_numbers.cache_info = _wn.cache_info diff --git a/psutil/_exceptions.py b/psutil/_exceptions.py new file mode 100644 index 000000000..c08e6d83c --- /dev/null +++ b/psutil/_exceptions.py @@ -0,0 +1,94 @@ +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +class Error(Exception): + """Base exception class. All other psutil exceptions inherit + from this one. + """ + + def __init__(self, msg=""): + Exception.__init__(self, msg) + self.msg = msg + + def __repr__(self): + ret = "psutil.%s %s" % (self.__class__.__name__, self.msg) + return ret.strip() + + __str__ = __repr__ + + +class NoSuchProcess(Error): + """Exception raised when a process with a certain PID doesn't + or no longer exists. + """ + + def __init__(self, pid, name=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.name = name + self.msg = msg + if msg is None: + if name: + details = "(pid=%s, name=%s)" % (self.pid, repr(self.name)) + else: + details = "(pid=%s)" % self.pid + self.msg = "process no longer exists " + details + + +class ZombieProcess(NoSuchProcess): + """Exception raised when querying a zombie process. This is + raised on OSX, BSD and Solaris only, and not always: depending + on the query the OS may be able to succeed anyway. + On Linux all zombie processes are querable (hence this is never + raised). Windows doesn't have zombie processes. + """ + + def __init__(self, pid, name=None, ppid=None, msg=None): + NoSuchProcess.__init__(self, msg) + self.pid = pid + self.ppid = ppid + self.name = name + self.msg = msg + if msg is None: + args = ["pid=%s" % pid] + if name: + args.append("name=%s" % repr(self.name)) + if ppid: + args.append("ppid=%s" % self.ppid) + details = "(%s)" % ", ".join(args) + self.msg = "process still exists but it's a zombie " + details + + +class AccessDenied(Error): + """Exception raised when permission to perform an action is denied.""" + + def __init__(self, pid=None, name=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.name = name + self.msg = msg + if msg is None: + if (pid is not None) and (name is not None): + self.msg = "(pid=%s, name=%s)" % (pid, repr(name)) + elif (pid is not None): + self.msg = "(pid=%s)" % self.pid + else: + self.msg = "" + + +class TimeoutExpired(Error): + """Raised on Process.wait(timeout) if timeout expires and process + is still alive. + """ + + def __init__(self, seconds, pid=None, name=None): + Error.__init__(self, "timeout after %s seconds" % seconds) + self.seconds = seconds + self.pid = pid + self.name = name + if (pid is not None) and (name is not None): + self.msg += " (pid=%s, name=%s)" % (pid, repr(name)) + elif (pid is not None): + self.msg += " (pid=%s)" % self.pid diff --git a/psutil/_psaix.py b/psutil/_psaix.py new file mode 100644 index 000000000..9abc8d17e --- /dev/null +++ b/psutil/_psaix.py @@ -0,0 +1,573 @@ +# Copyright (c) 2009, Giampaolo Rodola' +# Copyright (c) 2017, Arnon Yaari +# All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""AIX platform implementation.""" + +import errno +import glob +import os +import re +import subprocess +import sys +from collections import namedtuple +from socket import AF_INET + +from . import _common +from . import _psposix +from . import _psutil_aix as cext +from . import _psutil_posix as cext_posix +from ._common import AF_INET6 +from ._common import memoize_when_activated +from ._common import NIC_DUPLEX_FULL +from ._common import NIC_DUPLEX_HALF +from ._common import NIC_DUPLEX_UNKNOWN +from ._common import sockfam_to_enum +from ._common import socktype_to_enum +from ._common import usage_percent +from ._compat import PY3 +from ._exceptions import AccessDenied +from ._exceptions import NoSuchProcess +from ._exceptions import ZombieProcess + + +__extra__all__ = ["PROCFS_PATH"] + + +# ===================================================================== +# --- globals +# ===================================================================== + + +HAS_THREADS = hasattr(cext, "proc_threads") + +PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') +AF_LINK = cext_posix.AF_LINK + +PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SACTIVE: _common.STATUS_RUNNING, + cext.SSWAP: _common.STATUS_RUNNING, # TODO what status is this? + cext.SSTOP: _common.STATUS_STOPPED, +} + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +proc_info_map = dict( + ppid=0, + rss=1, + vms=2, + create_time=3, + nice=4, + num_threads=5, + status=6, + ttynr=7) + + +# ===================================================================== +# --- named tuples +# ===================================================================== + + +# psutil.Process.memory_info() +pmem = namedtuple('pmem', ['rss', 'vms']) +# psutil.Process.memory_full_info() +pfullmem = pmem +# psutil.Process.cpu_times() +scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait']) +# psutil.virtual_memory() +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +# psutil.Process.memory_maps(grouped=True) +pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss', 'anon', 'locked']) +# psutil.Process.memory_maps(grouped=False) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + + +# ===================================================================== +# --- utils +# ===================================================================== + + +def get_procfs_path(): + """Return updated psutil.PROCFS_PATH constant.""" + return sys.modules['psutil'].PROCFS_PATH + + +# ===================================================================== +# --- memory +# ===================================================================== + + +def virtual_memory(): + total, avail, free, pinned, inuse = cext.virtual_mem() + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, inuse, free) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + total, free, sin, sout = cext.swap_mem() + used = total - free + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +# ===================================================================== +# --- CPU +# ===================================================================== + + +def cpu_times(): + """Return system-wide CPU times as a named tuple""" + ret = cext.per_cpu_times() + return scputimes(*[sum(x) for x in zip(*ret)]) + + +def per_cpu_times(): + """Return system per-CPU times as a list of named tuples""" + ret = cext.per_cpu_times() + return [scputimes(*x) for x in ret] + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # mimic os.cpu_count() behavior + return None + + +def cpu_count_physical(): + cmd = "lsdev -Cc processor" + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout, stderr = [x.decode(sys.stdout.encoding) + for x in (stdout, stderr)] + if p.returncode != 0: + raise RuntimeError("%r command error\n%s" % (cmd, stderr)) + processors = stdout.strip().splitlines() + return len(processors) or None + + +def cpu_stats(): + """Return various CPU stats as a named tuple.""" + ctx_switches, interrupts, soft_interrupts, syscalls = cext.cpu_stats() + return _common.scpustats( + ctx_switches, interrupts, soft_interrupts, syscalls) + + +# ===================================================================== +# --- disks +# ===================================================================== + + +disk_io_counters = cext.disk_io_counters +disk_usage = _psposix.disk_usage + + +def disk_partitions(all=False): + """Return system disk partitions.""" + # TODO - the filtering logic should be better checked so that + # it tries to reflect 'df' as much as possible + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + # Differently from, say, Linux, we don't have a list of + # common fs types so the best we can do, AFAIK, is to + # filter by filesystem having a total size > 0. + if not disk_usage(mountpoint).total: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +# ===================================================================== +# --- network +# ===================================================================== + + +net_if_addrs = cext_posix.net_if_addrs +net_io_counters = cext.net_io_counters + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + connections (as opposed to connections opened by one process only). + """ + cmap = _common.conn_tmap + if kind not in cmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in cmap]))) + families, types = _common.conn_tmap[kind] + rawlist = cext.net_connections(_pid) + ret = set() + for item in rawlist: + fd, fam, type_, laddr, raddr, status, pid = item + if fam not in families: + continue + if type_ not in types: + continue + status = TCP_STATUSES[status] + if fam in (AF_INET, AF_INET6): + if laddr: + laddr = _common.addr(*laddr) + if raddr: + raddr = _common.addr(*raddr) + fam = sockfam_to_enum(fam) + type_ = socktype_to_enum(type_) + if _pid == -1: + nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid) + else: + nt = _common.pconn(fd, fam, type_, laddr, raddr, status) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + duplex_map = {"Full": NIC_DUPLEX_FULL, + "Half": NIC_DUPLEX_HALF} + names = set([x[0] for x in net_if_addrs()]) + ret = {} + for name in names: + isup, mtu = cext.net_if_stats(name) + + # try to get speed and duplex + # TODO: rewrite this in C (entstat forks, so use truss -f to follow. + # looks like it is using an undocumented ioctl?) + duplex = "" + speed = 0 + p = subprocess.Popen(["/usr/bin/entstat", "-d", name], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout, stderr = [x.decode(sys.stdout.encoding) + for x in (stdout, stderr)] + if p.returncode == 0: + re_result = re.search("Running: (\d+) Mbps.*?(\w+) Duplex", stdout) + if re_result is not None: + speed = int(re_result.group(1)) + duplex = re_result.group(2) + + duplex = duplex_map.get(duplex, NIC_DUPLEX_UNKNOWN) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +# ===================================================================== +# --- other system functions +# ===================================================================== + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + localhost = (':0.0', ':0') + for item in rawlist: + user, tty, hostname, tstamp, user_process, pid = item + # note: the underlying C function includes entries about + # system boot, run level and others. We might want + # to use them in the future. + if not user_process: + continue + if hostname in localhost: + hostname = 'localhost' + nt = _common.suser(user, tty, hostname, tstamp, pid) + retlist.append(nt) + return retlist + + +# ===================================================================== +# --- processes +# ===================================================================== + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir(get_procfs_path()) if x.isdigit()] + + +def pid_exists(pid): + """Check for the existence of a unix pid.""" + return os.path.exists(os.path.join(get_procfs_path(), str(pid), "psinfo")) + + +def wrap_exceptions(fun): + """Call callable into a try/except clause and translate ENOENT, + EACCES and EPERM in NoSuchProcess or AccessDenied exceptions. + """ + + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except EnvironmentError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if err.errno in (errno.ENOENT, errno.ESRCH): + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid", "_procfs_path"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + self._procfs_path = get_procfs_path() + + def oneshot_enter(self): + self._proc_name_and_args.cache_activate() + self._proc_basic_info.cache_activate() + self._proc_cred.cache_activate() + + def oneshot_exit(self): + self._proc_name_and_args.cache_deactivate() + self._proc_basic_info.cache_deactivate() + self._proc_cred.cache_deactivate() + + @memoize_when_activated + def _proc_name_and_args(self): + return cext.proc_name_and_args(self.pid, self._procfs_path) + + @memoize_when_activated + def _proc_basic_info(self): + return cext.proc_basic_info(self.pid, self._procfs_path) + + @memoize_when_activated + def _proc_cred(self): + return cext.proc_cred(self.pid, self._procfs_path) + + @wrap_exceptions + def name(self): + if self.pid == 0: + return "swapper" + # note: this is limited to 15 characters + return self._proc_name_and_args()[0].rstrip("\x00") + + @wrap_exceptions + def exe(self): + # there is no way to get executable path in AIX other than to guess, + # and guessing is more complex than what's in the wrapping class + exe = self.cmdline()[0] + if os.path.sep in exe: + # relative or absolute path + if not os.path.isabs(exe): + # if cwd has changed, we're out of luck - this may be wrong! + exe = os.path.abspath(os.path.join(self.cwd(), exe)) + if (os.path.isabs(exe) and + os.path.isfile(exe) and + os.access(exe, os.X_OK)): + return exe + # not found, move to search in PATH using basename only + exe = os.path.basename(exe) + # search for exe name PATH + for path in os.environ["PATH"].split(":"): + possible_exe = os.path.abspath(os.path.join(path, exe)) + if (os.path.isfile(possible_exe) and + os.access(possible_exe, os.X_OK)): + return possible_exe + return '' + + @wrap_exceptions + def cmdline(self): + return self._proc_name_and_args()[1].split(' ') + + @wrap_exceptions + def create_time(self): + return self._proc_basic_info()[proc_info_map['create_time']] + + @wrap_exceptions + def num_threads(self): + return self._proc_basic_info()[proc_info_map['num_threads']] + + if HAS_THREADS: + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + # The underlying C implementation retrieves all OS threads + # and filters them by PID. At this point we can't tell whether + # an empty list means there were no connections for process or + # process is no longer active so we force NSP in case the PID + # is no longer there. + if not retlist: + # will raise NSP if process is gone + os.stat('%s/%s' % (self._procfs_path, self.pid)) + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + ret = net_connections(kind, _pid=self.pid) + # The underlying C implementation retrieves all OS connections + # and filters them by PID. At this point we can't tell whether + # an empty list means there were no connections for process or + # process is no longer active so we force NSP in case the PID + # is no longer there. + if not ret: + # will raise NSP if process is gone + os.stat('%s/%s' % (self._procfs_path, self.pid)) + return ret + + @wrap_exceptions + def nice_get(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def ppid(self): + self._ppid = self._proc_basic_info()[proc_info_map['ppid']] + return self._ppid + + @wrap_exceptions + def uids(self): + real, effective, saved, _, _, _ = self._proc_cred() + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + _, _, _, real, effective, saved = self._proc_cred() + return _common.puids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + cpu_times = cext.proc_cpu_times(self.pid, self._procfs_path) + return _common.pcputimes(*cpu_times) + + @wrap_exceptions + def terminal(self): + ttydev = self._proc_basic_info()[proc_info_map['ttynr']] + # convert from 64-bit dev_t to 32-bit dev_t and then map the device + ttydev = (((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF)) + # try to match rdev of /dev/pts/* files ttydev + for dev in glob.glob("/dev/**/*"): + if os.stat(dev).st_rdev == ttydev: + return dev + return None + + @wrap_exceptions + def cwd(self): + procfs_path = self._procfs_path + try: + result = os.readlink("%s/%s/cwd" % (procfs_path, self.pid)) + return result.rstrip('/') + except OSError as err: + if err.errno == errno.ENOENT: + os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD + return None + raise + + @wrap_exceptions + def memory_info(self): + ret = self._proc_basic_info() + rss = ret[proc_info_map['rss']] * 1024 + vms = ret[proc_info_map['vms']] * 1024 + return pmem(rss, vms) + + memory_full_info = memory_info + + @wrap_exceptions + def status(self): + code = self._proc_basic_info()[proc_info_map['status']] + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + def open_files(self): + # TODO rewrite without using procfiles (stat /proc/pid/fd/* and then + # find matching name of the inode) + p = subprocess.Popen(["/usr/bin/procfiles", "-n", str(self.pid)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout, stderr = [x.decode(sys.stdout.encoding) + for x in (stdout, stderr)] + if "no such process" in stderr.lower(): + raise NoSuchProcess(self.pid, self._name) + procfiles = re.findall("(\d+): S_IFREG.*\s*.*name:(.*)\n", stdout) + retlist = [] + for fd, path in procfiles: + path = path.strip() + if path.startswith("//"): + path = path[1:] + if path.lower() == "cannot be retrieved": + continue + retlist.append(_common.popenfile(path, int(fd))) + return retlist + + @wrap_exceptions + def num_fds(self): + if self.pid == 0: # no /proc/0/fd + return 0 + return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw( + *cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def wait(self, timeout=None): + return _psposix.wait_pid(self.pid, timeout, self._name) + + @wrap_exceptions + def io_counters(self): + try: + rc, wc, rb, wb = cext.proc_io_counters(self.pid) + except OSError: + # if process is terminated, proc_io_counters returns OSError + # instead of NSP + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + raise + return _common.pio(rc, wc, rb, wb) diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index a33015048..0553401a5 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -10,13 +10,16 @@ import os import xml.etree.ElementTree as ET from collections import namedtuple +from socket import AF_INET from . import _common from . import _psposix from . import _psutil_bsd as cext from . import _psutil_posix as cext_posix +from ._common import AF_INET6 from ._common import conn_tmap from ._common import FREEBSD +from ._common import memoize from ._common import memoize_when_activated from ._common import NETBSD from ._common import OPENBSD @@ -24,12 +27,15 @@ from ._common import socktype_to_enum from ._common import usage_percent from ._compat import which +from ._exceptions import AccessDenied +from ._exceptions import NoSuchProcess +from ._exceptions import ZombieProcess __extra__all__ = [] # ===================================================================== -# --- constants +# --- globals # ===================================================================== @@ -121,7 +127,8 @@ memtext=20, memdata=21, memstack=22, - name=23, + cpunum=23, + name=24, ) @@ -130,20 +137,27 @@ # ===================================================================== -# extend base mem ntuple with BSD-specific memory metrics +# psutil.virtual_memory() svmem = namedtuple( 'svmem', ['total', 'available', 'percent', 'used', 'free', 'active', 'inactive', 'buffers', 'cached', 'shared', 'wired']) +# psutil.cpu_times() scputimes = namedtuple( 'scputimes', ['user', 'nice', 'system', 'idle', 'irq']) +# psutil.Process.memory_info() pmem = namedtuple('pmem', ['rss', 'vms', 'text', 'data', 'stack']) +# psutil.Process.memory_full_info() pfullmem = pmem +# psutil.Process.cpu_times() pcputimes = namedtuple('pcputimes', ['user', 'system', 'children_user', 'children_system']) +# psutil.Process.memory_maps(grouped=True) pmmap_grouped = namedtuple( 'pmmap_grouped', 'path rss, private, ref_count, shadow_count') +# psutil.Process.memory_maps(grouped=False) pmmap_ext = namedtuple( 'pmmap_ext', 'addr, perms path rss, private, ref_count, shadow_count') +# psutil.disk_io_counters() if FREEBSD: sdiskio = namedtuple('sdiskio', ['read_count', 'write_count', 'read_bytes', 'write_bytes', @@ -154,18 +168,6 @@ 'read_bytes', 'write_bytes']) -# ===================================================================== -# --- exceptions -# ===================================================================== - - -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -ZombieProcess = None -AccessDenied = None -TimeoutExpired = None - - # ===================================================================== # --- memory # ===================================================================== @@ -227,6 +229,7 @@ def per_cpu_times(): # crash at psutil import time. # Next calls will fail with NotImplementedError def per_cpu_times(): + """Return system CPU times as a namedtuple""" if cpu_count_logical() == 1: return [cpu_times()] if per_cpu_times.__called__: @@ -276,6 +279,7 @@ def cpu_count_physical(): def cpu_stats(): + """Return various CPU stats as a named tuple.""" if FREEBSD: # Note: the C ext is returning some metrics we are not exposing: # traps. @@ -351,6 +355,7 @@ def net_if_stats(): def net_connections(kind): + """System-wide network connections.""" if OPENBSD: ret = [] for pid in pids(): @@ -386,6 +391,11 @@ def net_connections(kind): # have a very short lifetime so maybe the kernel # can't initialize their status? status = TCP_STATUSES[cext.PSUTIL_CONN_NONE] + if fam in (AF_INET, AF_INET6): + if laddr: + laddr = _common.addr(*laddr) + if raddr: + raddr = _common.addr(*raddr) fam = sockfam_to_enum(fam) type = socktype_to_enum(type) nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid) @@ -393,6 +403,30 @@ def net_connections(kind): return list(ret) +# ===================================================================== +# --- sensors +# ===================================================================== + + +if FREEBSD: + + def sensors_battery(): + """Return battery info.""" + try: + percent, minsleft, power_plugged = cext.sensors_battery() + except NotImplementedError: + # See: https://github.com/giampaolo/psutil/issues/1074 + return None + power_plugged = power_plugged == 1 + if power_plugged: + secsleft = _common.POWER_TIME_UNLIMITED + elif minsleft == -1: + secsleft = _common.POWER_TIME_UNKNOWN + else: + secsleft = minsleft * 60 + return _common.sbattery(percent, secsleft, power_plugged) + + # ===================================================================== # --- other system functions # ===================================================================== @@ -404,13 +438,17 @@ def boot_time(): def users(): + """Return currently connected users as a list of namedtuples.""" retlist = [] rawlist = cext.users() for item in rawlist: - user, tty, hostname, tstamp = item + user, tty, hostname, tstamp, pid = item + if pid == -1: + assert OPENBSD + pid = None if tty == '~': continue # reboot or shutdown - nt = _common.suser(user, tty or None, hostname, tstamp) + nt = _common.suser(user, tty or None, hostname, tstamp, pid) retlist.append(nt) return retlist @@ -420,10 +458,31 @@ def users(): # ===================================================================== -pids = cext.pids +@memoize +def _pid_0_exists(): + try: + Process(0).name() + except NoSuchProcess: + return False + except AccessDenied: + return True + else: + return True + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + ret = cext.pids() + if OPENBSD and (0 not in ret) and _pid_0_exists(): + # On OpenBSD the kernel does not return PID 0 (neither does + # ps) but it's actually querable (Process(0) will succeed). + ret.insert(0, 0) + return ret + if OPENBSD or NETBSD: def pid_exists(pid): + """Return True if pid exists.""" exists = _psposix.pid_exists(pid) if not exists: # We do this because _psposix.pid_exists() lies in case of @@ -462,6 +521,7 @@ def wrapper(self, *args, **kwargs): @contextlib.contextmanager def wrap_exceptions_procfs(inst): + """Same as above, for routines relying on reading /proc fs.""" try: yield except EnvironmentError as err: @@ -589,6 +649,11 @@ def cpu_times(self): rawtuple[kinfo_proc_map['ch_user_time']], rawtuple[kinfo_proc_map['ch_sys_time']]) + if FREEBSD: + @wrap_exceptions + def cpu_num(self): + return self.oneshot()[kinfo_proc_map['cpunum']] + @wrap_exceptions def memory_info(self): rawtuple = self.oneshot() @@ -653,6 +718,11 @@ def connections(self, kind='inet'): status = TCP_STATUSES[status] except KeyError: status = TCP_STATUSES[cext.PSUTIL_CONN_NONE] + if fam in (AF_INET, AF_INET6): + if laddr: + laddr = _common.addr(*laddr) + if raddr: + raddr = _common.addr(*raddr) fam = sockfam_to_enum(fam) type = socktype_to_enum(type) nt = _common.pconn(fd, fam, type, laddr, raddr, status) @@ -668,6 +738,11 @@ def connections(self, kind='inet'): ret = [] for item in rawlist: fd, fam, type, laddr, raddr, status = item + if fam in (AF_INET, AF_INET6): + if laddr: + laddr = _common.addr(*laddr) + if raddr: + raddr = _common.addr(*raddr) fam = sockfam_to_enum(fam) type = socktype_to_enum(type) status = TCP_STATUSES[status] @@ -682,10 +757,7 @@ def connections(self, kind='inet'): @wrap_exceptions def wait(self, timeout=None): - try: - return _psposix.wait_pid(self.pid, timeout) - except _psposix.TimeoutExpired: - raise TimeoutExpired(timeout, self.pid, self._name) + return _psposix.wait_pid(self.pid, timeout, self._name) @wrap_exceptions def nice_get(self): diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index 91fdae4f8..b57adb34e 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -7,8 +7,10 @@ from __future__ import division import base64 +import collections import errno import functools +import glob import os import re import socket @@ -23,13 +25,15 @@ from . import _psposix from . import _psutil_linux as cext from . import _psutil_posix as cext_posix +from ._common import ENCODING +from ._common import ENCODING_ERRS from ._common import isfile_strict from ._common import memoize from ._common import memoize_when_activated -from ._common import parse_environ_block from ._common import NIC_DUPLEX_FULL from ._common import NIC_DUPLEX_HALF from ._common import NIC_DUPLEX_UNKNOWN +from ._common import parse_environ_block from ._common import path_exists_strict from ._common import supports_ipv6 from ._common import usage_percent @@ -37,6 +41,9 @@ from ._compat import basestring from ._compat import long from ._compat import PY3 +from ._exceptions import AccessDenied +from ._exceptions import NoSuchProcess +from ._exceptions import ZombieProcess if sys.version_info >= (3, 4): import enum @@ -57,12 +64,14 @@ # ===================================================================== -# --- constants +# --- globals # ===================================================================== +POWER_SUPPLY_PATH = "/sys/class/power_supply" HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid()) HAS_PRLIMIT = hasattr(cext, "linux_prlimit") +_DEFAULT = object() # RLIMIT_* constants, not guaranteed to be present on all kernels if HAS_PRLIMIT: @@ -77,11 +86,9 @@ # Used when reading "big" files, namely /proc/{pid}/smaps and /proc/net/*. # On Python 2, using a buffer with open() for such files may result in a # speedup, see: https://github.com/giampaolo/psutil/issues/708 -BIGGER_FILE_BUFFERING = -1 if PY3 else 8192 +BIGFILE_BUFFERING = -1 if PY3 else 8192 LITTLE_ENDIAN = sys.byteorder == 'little' -if PY3: - FS_ENCODING = sys.getfilesystemencoding() - ENCODING_ERRORS_HANDLER = 'surrogateescape' +SECTOR_SIZE_FALLBACK = 512 if enum is None: AF_LINK = socket.AF_PACKET else: @@ -118,7 +125,7 @@ class IOPriority(enum.IntEnum): "W": _common.STATUS_WAKING } -# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h +# https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h TCP_STATUSES = { "01": _common.CONN_ESTABLISHED, "02": _common.CONN_SYN_SENT, @@ -134,42 +141,41 @@ class IOPriority(enum.IntEnum): } -# ===================================================================== -# -- exceptions -# ===================================================================== - - -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -ZombieProcess = None -AccessDenied = None -TimeoutExpired = None - - # ===================================================================== # --- named tuples # ===================================================================== +# psutil.virtual_memory() svmem = namedtuple( 'svmem', ['total', 'available', 'percent', 'used', 'free', 'active', 'inactive', 'buffers', 'cached', 'shared']) +# psutil.disk_io_counters() sdiskio = namedtuple( 'sdiskio', ['read_count', 'write_count', 'read_bytes', 'write_bytes', 'read_time', 'write_time', 'read_merged_count', 'write_merged_count', 'busy_time']) +# psutil.Process().open_files() popenfile = namedtuple( 'popenfile', ['path', 'fd', 'position', 'mode', 'flags']) +# psutil.Process().memory_info() pmem = namedtuple('pmem', 'rss vms shared text lib data dirty') +# psutil.Process().memory_full_info() pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', 'pss', 'swap')) +# psutil.Process().memory_maps(grouped=True) pmmap_grouped = namedtuple( 'pmmap_grouped', ['path', 'rss', 'size', 'pss', 'shared_clean', 'shared_dirty', 'private_clean', 'private_dirty', 'referenced', 'anonymous', 'swap']) +# psutil.Process().memory_maps(grouped=False) pmmap_ext = namedtuple( 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) +# psutil.Process.io_counters() +pio = namedtuple('pio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'read_chars', 'write_chars']) # ===================================================================== @@ -190,20 +196,21 @@ def open_text(fname, **kwargs): # See: # https://github.com/giampaolo/psutil/issues/675 # https://github.com/giampaolo/psutil/pull/733 - kwargs.setdefault('encoding', FS_ENCODING) - kwargs.setdefault('errors', ENCODING_ERRORS_HANDLER) + kwargs.setdefault('encoding', ENCODING) + kwargs.setdefault('errors', ENCODING_ERRS) return open(fname, "rt", **kwargs) if PY3: def decode(s): - return s.decode(encoding=FS_ENCODING, errors=ENCODING_ERRORS_HANDLER) + return s.decode(encoding=ENCODING, errors=ENCODING_ERRS) else: def decode(s): return s def get_procfs_path(): + """Return updated psutil.PROCFS_PATH constant.""" return sys.modules['psutil'].PROCFS_PATH @@ -228,6 +235,9 @@ def readlink(path): def file_flags_to_mode(flags): + """Convert file's open() flags into a readable string. + Used by Process.open_files(). + """ modes_map = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'} mode = modes_map[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)] if flags & os.O_APPEND: @@ -237,26 +247,26 @@ def file_flags_to_mode(flags): return mode -def get_sector_size(): +def get_sector_size(partition): + """Return the sector size of a partition. + Used by disk_io_counters(). + """ try: - with open(b"/sys/block/sda/queue/hw_sector_size") as f: + with open("/sys/block/%s/queue/hw_sector_size" % partition, "rt") as f: return int(f.read()) except (IOError, ValueError): # man iostat states that sectors are equivalent with blocks and - # have a size of 512 bytes since 2.4 kernels. This value is - # needed to calculate the amount of disk I/O in bytes. - return 512 - - -SECTOR_SIZE = get_sector_size() + # have a size of 512 bytes since 2.4 kernels. + return SECTOR_SIZE_FALLBACK @memoize def set_scputimes_ntuple(procfs_path): - """Return a namedtuple of variable fields depending on the - CPU times available on this Linux kernel version which may be: + """Set a namedtuple of variable fields depending on the CPU times + available on this Linux kernel version which may be: (user, nice, system, idle, iowait, irq, softirq, [steal, [guest, [guest_nice]]]) + Used by cpu_times() function. """ global scputimes with open_binary('%s/stat' % procfs_path) as f: @@ -273,11 +283,26 @@ def set_scputimes_ntuple(procfs_path): # Linux >= 3.2.0 fields.append('guest_nice') scputimes = namedtuple('scputimes', fields) - return scputimes + + +def cat(fname, fallback=_DEFAULT, binary=True): + """Return file content. + fallback: the value returned in case the file does not exist or + cannot be read + binary: whether to open the file in binary or text mode. + """ + try: + with open_binary(fname) if binary else open_text(fname) as f: + return f.read().strip() + except IOError: + if fallback is not _DEFAULT: + return fallback + else: + raise try: - scputimes = set_scputimes_ntuple("/proc") + set_scputimes_ntuple("/proc") except Exception: # Don't want to crash at import time. traceback.print_exc() @@ -291,7 +316,8 @@ def set_scputimes_ntuple(procfs_path): def calculate_avail_vmem(mems): """Fallback for kernels < 3.14 where /proc/meminfo does not provide - "MemAvailable:" column (see: https://blog.famzah.net/2014/09/24/). + "MemAvailable:" column, see: + https://blog.famzah.net/2014/09/24/ This code reimplements the algorithm outlined here: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 @@ -369,8 +395,12 @@ def virtual_memory(): # returned by sysinfo(2); as such we assume they are always there. total = mems[b'MemTotal:'] free = mems[b'MemFree:'] - buffers = mems[b'Buffers:'] - + try: + buffers = mems[b'Buffers:'] + except KeyError: + # https://github.com/giampaolo/psutil/issues/1010 + buffers = 0 + missing_fields.append('buffers') try: cached = mems[b"Cached:"] except KeyError: @@ -455,9 +485,24 @@ def virtual_memory(): def swap_memory(): - _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo() - total *= unit_multiplier - free *= unit_multiplier + """Return swap memory metrics.""" + 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[b'SwapTotal:'] + free = mems[b'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 @@ -493,7 +538,7 @@ def swap_memory(): # ===================================================================== -# --- CPUs +# --- CPU # ===================================================================== @@ -549,7 +594,7 @@ def cpu_count_logical(): # https://github.com/giampaolo/psutil/issues/200 # try to parse /proc/stat as a last resort if num == 0: - search = re.compile('cpu\d') + search = re.compile(r'cpu\d') with open_text('%s/stat' % get_procfs_path()) as f: for line in f: line = line.split(' ')[0] @@ -588,6 +633,7 @@ def cpu_count_physical(): def cpu_stats(): + """Return various CPU stats as a named tuple.""" with open_binary('%s/stat' % get_procfs_path()) as f: ctx_switches = None interrupts = None @@ -607,6 +653,45 @@ def cpu_stats(): ctx_switches, interrupts, soft_interrupts, syscalls) +if os.path.exists("/sys/devices/system/cpu/cpufreq") or \ + os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq"): + def cpu_freq(): + """Return frequency metrics for all CPUs. + Contrarily to other OSes, Linux updates these values in + real-time. + """ + # scaling_* files seem preferable to cpuinfo_*, see: + # http://unix.stackexchange.com/a/87537/168884 + ret = [] + ls = glob.glob("/sys/devices/system/cpu/cpufreq/policy*") + if ls: + # Sort the list so that '10' comes after '2'. This should + # ensure the CPU order is consistent with other CPU functions + # having a 'percpu' argument and returning results for multiple + # CPUs (cpu_times(), cpu_percent(), cpu_times_percent()). + ls.sort(key=lambda x: int(os.path.basename(x)[6:])) + else: + # https://github.com/giampaolo/psutil/issues/981 + ls = glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq") + ls.sort(key=lambda x: int(re.search('[0-9]+', x).group(0))) + + pjoin = os.path.join + for path in ls: + curr = cat(pjoin(path, "scaling_cur_freq"), fallback=None) + if curr is None: + # Likely an old RedHat, see: + # https://github.com/giampaolo/psutil/issues/1071 + curr = cat(pjoin(path, "cpuinfo_cur_freq"), fallback=None) + if curr is None: + raise NotImplementedError( + "can't find current frequency file") + curr = int(curr) / 1000 + max_ = int(cat(pjoin(path, "scaling_max_freq"))) / 1000 + min_ = int(cat(pjoin(path, "scaling_min_freq"))) / 1000 + ret.append(_common.scpufreq(curr, min_, max_)) + return ret + + # ===================================================================== # --- network # ===================================================================== @@ -746,7 +831,7 @@ def decode_address(addr, family): raise _Ipv6UnsupportedError else: raise - return (ip, port) + return _common.addr(ip, port) @staticmethod def process_inet(file, family, type_, inodes, filter_pid=None): @@ -754,7 +839,7 @@ def process_inet(file, family, type_, inodes, filter_pid=None): if file.endswith('6') and not os.path.exists(file): # IPv6 not supported return - with open_text(file, buffering=BIGGER_FILE_BUFFERING) as f: + with open_text(file, buffering=BIGFILE_BUFFERING) as f: f.readline() # skip the first line for lineno, line in enumerate(f, 1): try: @@ -791,7 +876,7 @@ def process_inet(file, family, type_, inodes, filter_pid=None): @staticmethod def process_unix(file, family, inodes, filter_pid=None): """Parse /proc/net/unix files.""" - with open_text(file, buffering=BIGGER_FILE_BUFFERING) as f: + with open_text(file, buffering=BIGFILE_BUFFERING) as f: f.readline() # skip the first line for line in f: tokens = line.split() @@ -819,7 +904,10 @@ def process_unix(file, family, inodes, filter_pid=None): else: path = "" type_ = int(type_) - raddr = None + # XXX: determining the remote endpoint of a + # UNIX socket on Linux is not possible, see: + # https://serverfault.com/questions/252723/ + raddr = "" status = _common.CONN_NONE yield (fd, family, type_, path, raddr, status, pid) @@ -988,15 +1076,16 @@ def get_partitions(): raise ValueError("not sure how to interpret line %r" % line) if name in partitions: - rbytes = rbytes * SECTOR_SIZE - wbytes = wbytes * SECTOR_SIZE + ssize = get_sector_size(name) + rbytes *= ssize + wbytes *= ssize retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime, reads_merged, writes_merged, busy_time) return retdict def disk_partitions(all=False): - """Return mounted disk partitions as a list of namedtuples""" + """Return mounted disk partitions as a list of namedtuples.""" fstypes = set() with open_text("%s/filesystems" % get_procfs_path()) as f: for line in f: @@ -1023,6 +1112,171 @@ def disk_partitions(all=False): return retlist +# ===================================================================== +# --- sensors +# ===================================================================== + + +def sensors_temperatures(): + """Return hardware (CPU and others) temperatures as a dict + including hardware name, label, current, max and critical + temperatures. + + Implementation notes: + - /sys/class/hwmon looks like the most recent interface to + retrieve this info, and this implementation relies on it + only (old distros will probably use something else) + - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon + - /sys/class/thermal/thermal_zone* is another one but it's more + difficult to parse + """ + ret = collections.defaultdict(list) + basenames = glob.glob('/sys/class/hwmon/hwmon*/temp*_*') + # CentOS has an intermediate /device directory: + # https://github.com/giampaolo/psutil/issues/971 + # https://github.com/nicolargo/glances/issues/1060 + basenames.extend(glob.glob('/sys/class/hwmon/hwmon*/device/temp*_*')) + basenames = sorted(set([x.split('_')[0] for x in basenames])) + + for base in basenames: + try: + current = float(cat(base + '_input')) / 1000.0 + except (IOError, OSError) as err: + # A lot of things can go wrong here, so let's just skip the + # whole entry. + # https://github.com/giampaolo/psutil/issues/1009 + # https://github.com/giampaolo/psutil/issues/1101 + # https://github.com/giampaolo/psutil/issues/1129 + warnings.warn("ignoring %r" % err, RuntimeWarning) + continue + + unit_name = cat(os.path.join(os.path.dirname(base), 'name'), + binary=False) + high = cat(base + '_max', fallback=None) + critical = cat(base + '_crit', fallback=None) + label = cat(base + '_label', fallback='', binary=False) + + if high is not None: + high = float(high) / 1000.0 + if critical is not None: + critical = float(critical) / 1000.0 + + ret[unit_name].append((label, current, high, critical)) + + return ret + + +def sensors_fans(): + """Return hardware fans info (for CPU and other peripherals) as a + dict including hardware label and current speed. + + Implementation notes: + - /sys/class/hwmon looks like the most recent interface to + retrieve this info, and this implementation relies on it + only (old distros will probably use something else) + - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon + """ + ret = collections.defaultdict(list) + basenames = glob.glob('/sys/class/hwmon/hwmon*/fan*_*') + if not basenames: + # CentOS has an intermediate /device directory: + # https://github.com/giampaolo/psutil/issues/971 + basenames = glob.glob('/sys/class/hwmon/hwmon*/device/fan*_*') + + basenames = sorted(set([x.split('_')[0] for x in basenames])) + for base in basenames: + try: + current = int(cat(base + '_input')) + except (IOError, OSError) as err: + warnings.warn("ignoring %r" % err, RuntimeWarning) + continue + unit_name = cat(os.path.join(os.path.dirname(base), 'name'), + binary=False) + label = cat(base + '_label', fallback='', binary=False) + ret[unit_name].append(_common.sfan(label, current)) + + return dict(ret) + + +def sensors_battery(): + """Return battery information. + Implementation note: it appears /sys/class/power_supply/BAT0/ + directory structure may vary and provide files with the same + meaning but under different names, see: + https://github.com/giampaolo/psutil/issues/966 + """ + null = object() + + def multi_cat(*paths): + """Attempt to read the content of multiple files which may + not exist. If none of them exist return None. + """ + for path in paths: + ret = cat(path, fallback=null) + if ret != null: + return int(ret) if ret.isdigit() else ret + return None + + root = os.path.join(POWER_SUPPLY_PATH, "BAT0") + if not os.path.exists(root): + return None + + # Base metrics. + energy_now = multi_cat( + root + "/energy_now", + root + "/charge_now") + power_now = multi_cat( + root + "/power_now", + root + "/current_now") + energy_full = multi_cat( + root + "/energy_full", + root + "/charge_full") + if energy_now is None or power_now is None: + return None + + # Percent. If we have energy_full the percentage will be more + # accurate compared to reading /capacity file (float vs. int). + if energy_full is not None: + try: + percent = 100.0 * energy_now / energy_full + except ZeroDivisionError: + percent = 0.0 + else: + percent = int(cat(root + "/capacity", fallback=-1)) + if percent == -1: + return None + + # Is AC power cable plugged in? + # Note: AC0 is not always available and sometimes (e.g. CentOS7) + # it's called "AC". + power_plugged = None + online = multi_cat( + os.path.join(POWER_SUPPLY_PATH, "AC0/online"), + os.path.join(POWER_SUPPLY_PATH, "AC/online")) + if online is not None: + power_plugged = online == 1 + else: + status = cat(root + "/status", fallback="", binary=False).lower() + if status == "discharging": + power_plugged = False + elif status in ("charging", "full"): + power_plugged = True + + # Seconds left. + # Note to self: we may also calculate the charging ETA as per: + # https://github.com/thialfihar/dotfiles/blob/ + # 013937745fd9050c30146290e8f963d65c0179e6/bin/battery.py#L55 + if power_plugged: + secsleft = _common.POWER_TIME_UNLIMITED + else: + try: + secsleft = int(energy_now / power_now * 3600) + except ZeroDivisionError: + secsleft = _common.POWER_TIME_UNKNOWN + + return _common.sbattery(percent, secsleft, power_plugged) + + # ===================================================================== # --- other system functions # ===================================================================== @@ -1033,15 +1287,15 @@ def users(): retlist = [] rawlist = cext.users() for item in rawlist: - user, tty, hostname, tstamp, user_process = item + user, tty, hostname, tstamp, user_process, pid = item # note: the underlying C function includes entries about # system boot, run level and others. We might want # to use them in the future. if not user_process: continue - if hostname == ':0.0' or hostname == ':0': + if hostname in (':0.0', ':0'): hostname = 'localhost' - nt = _common.suser(user, tty or None, hostname, tstamp) + nt = _common.suser(user, tty or None, hostname, tstamp, pid) retlist.append(nt) return retlist @@ -1049,14 +1303,15 @@ def users(): def boot_time(): """Return the system boot time expressed in seconds since the epoch.""" global BOOT_TIME - with open_binary('%s/stat' % get_procfs_path()) as f: + path = '%s/stat' % get_procfs_path() + with open_binary(path) as f: for line in f: if line.startswith(b'btime'): ret = float(line.strip().split()[1]) BOOT_TIME = ret return ret raise RuntimeError( - "line 'btime' not found in %s/stat" % get_procfs_path()) + "line 'btime' not found in %s" % path) # ===================================================================== @@ -1070,8 +1325,59 @@ def pids(): def pid_exists(pid): - """Check For the existence of a unix pid.""" - return _psposix.pid_exists(pid) + """Check for the existence of a unix PID. Linux TIDs are not + supported (always return False). + """ + if not _psposix.pid_exists(pid): + return False + else: + # Linux's apparently does not distinguish between PIDs and TIDs + # (thread IDs). + # listdir("/proc") won't show any TID (only PIDs) but + # os.stat("/proc/{tid}") will succeed if {tid} exists. + # os.kill() can also be passed a TID. This is quite confusing. + # In here we want to enforce this distinction and support PIDs + # only, see: + # https://github.com/giampaolo/psutil/issues/687 + try: + # Note: already checked that this is faster than using a + # regular expr. Also (a lot) faster than doing + # 'return pid in pids()' + path = "%s/%s/status" % (get_procfs_path(), pid) + with open_binary(path) as f: + for line in f: + if line.startswith(b"Tgid:"): + tgid = int(line.split()[1]) + # If tgid and pid are the same then we're + # dealing with a process PID. + return tgid == pid + raise ValueError("'Tgid' line not found in %s" % path) + except (EnvironmentError, ValueError): + return pid in pids() + + +def ppid_map(): + """Obtain a {pid: ppid, ...} dict for all running processes in + one shot. Used to speed up Process.children(). + """ + ret = {} + procfs_path = get_procfs_path() + for pid in pids(): + try: + with open_binary("%s/%s/stat" % (procfs_path, pid)) as f: + data = f.read() + except EnvironmentError as err: + # Note: we should be able to access /stat for all processes + # so we won't bump into EPERM, which is good. + if err.errno not in (errno.ENOENT, errno.ESRCH, + errno.EPERM, errno.EACCES): + raise + else: + rpar = data.rfind(b')') + dset = data[rpar + 2:].split() + ppid = int(dset[1]) + ret[pid] = ppid + return ret def wrap_exceptions(fun): @@ -1083,13 +1389,18 @@ def wrapper(self, *args, **kwargs): try: return fun(self, *args, **kwargs) except EnvironmentError as err: - # ENOENT (no such file or directory) gets raised on open(). - # ESRCH (no such process) can get raised on read() if - # process is gone in meantime. - if err.errno in (errno.ENOENT, errno.ESRCH): - raise NoSuchProcess(self.pid, self._name) if err.errno in (errno.EPERM, errno.EACCES): raise AccessDenied(self.pid, self._name) + # ESRCH (no such process) can be raised on read() if + # process is gone in the meantime. + if err.errno == errno.ESRCH: + raise NoSuchProcess(self.pid, self._name) + # ENOENT (no such file or directory) can be raised on open(). + if err.errno == errno.ENOENT and not os.path.exists("%s/%s" % ( + self._procfs_path, self.pid)): + raise NoSuchProcess(self.pid, self._name) + # Note: zombies will keep existing under /proc until they're + # gone so there's no way to distinguish them in here. raise return wrapper @@ -1112,7 +1423,6 @@ def _parse_stat_file(self): Using "man proc" as a reference: where "man proc" refers to position N, always substract 2 (e.g starttime pos 22 in 'man proc' == pos 20 in the list returned here). - The return value is cached in case oneshot() ctx manager is in use. """ @@ -1123,13 +1433,12 @@ def _parse_stat_file(self): # 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 + others = data[rpar + 2:].split() + return [name] + others @memoize_when_activated def _read_status_file(self): """Read /proc/{pid}/stat file and return its content. - The return value is cached in case oneshot() ctx manager is in use. """ @@ -1139,7 +1448,7 @@ def _read_status_file(self): @memoize_when_activated def _read_smaps_file(self): with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid), - buffering=BIGGER_FILE_BUFFERING) as f: + buffering=BIGFILE_BUFFERING) as f: return f.read().strip() def oneshot_enter(self): @@ -1186,9 +1495,17 @@ def cmdline(self): if not data: # may happen in case of zombie process return [] - if data.endswith('\x00'): + # 'man proc' states that args are separated by null bytes '\0' + # and last char is supposed to be a null byte. Nevertheless + # some processes may change their cmdline after being started + # (via setproctitle() or similar), they are usually not + # compliant with this rule and use spaces instead. Google + # Chrome process is an example. See: + # https://github.com/giampaolo/psutil/issues/1179 + sep = '\x00' if data.endswith('\x00') else ' ' + if data.endswith(sep): data = data[:-1] - return [x for x in data.split('\x00')] + return [x for x in data.split(sep)] @wrap_exceptions def environ(self): @@ -1209,22 +1526,24 @@ def terminal(self): @wrap_exceptions def io_counters(self): fname = "%s/%s/io" % (self._procfs_path, self.pid) + fields = {} with open_binary(fname) as f: - rcount = wcount = rbytes = wbytes = None for line in f: - if rcount is None and line.startswith(b"syscr"): - rcount = int(line.split()[1]) - elif wcount is None and line.startswith(b"syscw"): - wcount = int(line.split()[1]) - elif rbytes is None and line.startswith(b"read_bytes"): - rbytes = int(line.split()[1]) - elif wbytes is None and line.startswith(b"write_bytes"): - wbytes = int(line.split()[1]) - for x in (rcount, wcount, rbytes, wbytes): - if x is None: - raise NotImplementedError( - "couldn't read all necessary info from %r" % fname) - return _common.pio(rcount, wcount, rbytes, wbytes) + # https://github.com/giampaolo/psutil/issues/1004 + line = line.strip() + if line: + name, value = line.split(b': ') + fields[name] = int(value) + if not fields: + raise RuntimeError("%s file was empty" % fname) + return pio( + fields[b'syscr'], # read syscalls + fields[b'syscw'], # write syscalls + fields[b'read_bytes'], # read bytes + fields[b'write_bytes'], # write bytes + fields[b'rchar'], # read chars + fields[b'wchar'], # write chars + ) else: def io_counters(self): raise NotImplementedError("couldn't find /proc/%s/io (kernel " @@ -1239,12 +1558,14 @@ def cpu_times(self): children_stime = float(values[15]) / CLOCK_TICKS return _common.pcputimes(utime, stime, children_utime, children_stime) + @wrap_exceptions + def cpu_num(self): + """What CPU the process is on.""" + return int(self._parse_stat_file()[37]) + @wrap_exceptions def wait(self, timeout=None): - try: - return _psposix.wait_pid(self.pid, timeout) - except _psposix.TimeoutExpired: - raise TimeoutExpired(timeout, self.pid, self._name) + return _psposix.wait_pid(self.pid, timeout, self._name) @wrap_exceptions def create_time(self): @@ -1282,9 +1603,9 @@ def memory_info(self): @wrap_exceptions def memory_full_info( self, - _private_re=re.compile(b"Private.*:\s+(\d+)"), - _pss_re=re.compile(b"Pss.*:\s+(\d+)"), - _swap_re=re.compile(b"Swap.*:\s+(\d+)")): + _private_re=re.compile(br"Private.*:\s+(\d+)"), + _pss_re=re.compile(br"Pss.*:\s+(\d+)"), + _swap_re=re.compile(br"Swap.*:\s+(\d+)")): basic_mem = self.memory_info() # Note: using 3 regexes is faster than reading the file # line by line. @@ -1376,7 +1697,7 @@ def get_blocks(lines, current_block): )) return ls - else: + else: # pragma: no cover def memory_maps(self): raise NotImplementedError( "/proc/%s/smaps does not exist on kernels < 2.6.14 or " @@ -1385,10 +1706,20 @@ def memory_maps(self): @wrap_exceptions def cwd(self): - return readlink("%s/%s/cwd" % (self._procfs_path, self.pid)) + try: + return readlink("%s/%s/cwd" % (self._procfs_path, self.pid)) + except OSError as err: + # https://github.com/giampaolo/psutil/issues/986 + if err.errno in (errno.ENOENT, errno.ESRCH): + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + raise @wrap_exceptions - def num_ctx_switches(self, _ctxsw_re=re.compile(b'ctxt_switches:\t(\d+)')): + def num_ctx_switches(self, + _ctxsw_re=re.compile(br'ctxt_switches:\t(\d+)')): data = self._read_status_file() ctxsw = _ctxsw_re.findall(data) if not ctxsw: @@ -1396,12 +1727,12 @@ def num_ctx_switches(self, _ctxsw_re=re.compile(b'ctxt_switches:\t(\d+)')): "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" "lines were not found in %s/%s/status; the kernel is " "probably older than 2.6.23" % ( - self._procfs_path, self.self.pid)) + self._procfs_path, self.pid)) else: return _common.pctxsw(int(ctxsw[0]), int(ctxsw[1])) @wrap_exceptions - def num_threads(self, _num_threads_re=re.compile(b'Threads:\t(\d+)')): + def num_threads(self, _num_threads_re=re.compile(br'Threads:\t(\d+)')): # Note: on Python 3 using a re is faster than iterating over file # line by line. On Python 2 is the exact opposite, and iterating # over a file on Python 3 is slower than on Python 2. @@ -1456,18 +1787,33 @@ def nice_set(self, value): def cpu_affinity_get(self): return cext.proc_cpu_affinity_get(self.pid) + def _get_eligible_cpus( + self, _re=re.compile(br"Cpus_allowed_list:\t(\d+)-(\d+)")): + # See: https://github.com/giampaolo/psutil/issues/956 + data = self._read_status_file() + match = _re.findall(data) + if match: + return list(range(int(match[0][0]), int(match[0][1]) + 1)) + else: + return list(range(len(per_cpu_times()))) + @wrap_exceptions def cpu_affinity_set(self, cpus): try: cext.proc_cpu_affinity_set(self.pid, cpus) except (OSError, ValueError) as err: if isinstance(err, ValueError) or err.errno == errno.EINVAL: - allcpus = tuple(range(len(per_cpu_times()))) + eligible_cpus = self._get_eligible_cpus() + all_cpus = tuple(range(len(per_cpu_times()))) for cpu in cpus: - if cpu not in allcpus: + if cpu not in all_cpus: raise ValueError( "invalid CPU number %r; choose between %s" % ( - cpu, allcpus)) + cpu, eligible_cpus)) + if cpu not in eligible_cpus: + raise ValueError( + "CPU number %r is not eligible; choose " + "between %s" % (cpu, eligible_cpus)) raise # only starting from kernel 2.6.13 @@ -1577,12 +1923,23 @@ def open_files(self): # 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) + try: + with open_binary(file) as f: + pos = int(f.readline().split()[1]) + flags = int(f.readline().split()[1], 8) + except IOError as err: + if err.errno == errno.ENOENT: + # fd gone in the meantime; does not + # necessarily mean the process disappeared + # on us. + hit_enoent = True + else: + raise + else: + 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 os.stat('%s/%s' % (self._procfs_path, self.pid)) @@ -1604,13 +1961,13 @@ def ppid(self): return int(self._parse_stat_file()[2]) @wrap_exceptions - def uids(self, _uids_re=re.compile(b'Uid:\t(\d+)\t(\d+)\t(\d+)')): + def uids(self, _uids_re=re.compile(br'Uid:\t(\d+)\t(\d+)\t(\d+)')): data = self._read_status_file() real, effective, saved = _uids_re.findall(data)[0] return _common.puids(int(real), int(effective), int(saved)) @wrap_exceptions - def gids(self, _gids_re=re.compile(b'Gid:\t(\d+)\t(\d+)\t(\d+)')): + def gids(self, _gids_re=re.compile(br'Gid:\t(\d+)\t(\d+)\t(\d+)')): data = self._read_status_file() real, effective, saved = _gids_re.findall(data)[0] return _common.pgids(int(real), int(effective), int(saved)) diff --git a/psutil/_psosx.py b/psutil/_psosx.py index 2665080e0..308756a81 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -4,15 +4,18 @@ """OSX platform implementation.""" +import contextlib import errno import functools import os +from socket import AF_INET from collections import namedtuple from . import _common from . import _psposix from . import _psutil_osx as cext from . import _psutil_posix as cext_posix +from ._common import AF_INET6 from ._common import conn_tmap from ._common import isfile_strict from ._common import memoize_when_activated @@ -20,20 +23,22 @@ from ._common import sockfam_to_enum from ._common import socktype_to_enum from ._common import usage_percent +from ._exceptions import AccessDenied +from ._exceptions import NoSuchProcess +from ._exceptions import ZombieProcess __extra__all__ = [] # ===================================================================== -# --- constants +# --- globals # ===================================================================== PAGESIZE = os.sysconf("SC_PAGE_SIZE") AF_LINK = cext_posix.AF_LINK -# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h TCP_STATUSES = { cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, @@ -82,28 +87,30 @@ volctxsw=7, ) -scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle']) +# ===================================================================== +# --- named tuples +# ===================================================================== + + +# psutil.cpu_times() +scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle']) +# psutil.virtual_memory() svmem = namedtuple( 'svmem', ['total', 'available', 'percent', 'used', 'free', 'active', 'inactive', 'wired']) - +# psutil.Process.memory_info() pmem = namedtuple('pmem', ['rss', 'vms', 'pfaults', 'pageins']) +# psutil.Process.memory_full_info() pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', )) - +# psutil.Process.memory_maps(grouped=True) pmmap_grouped = namedtuple( 'pmmap_grouped', 'path rss private swapped dirtied ref_count shadow_depth') - +# psutil.Process.memory_maps(grouped=False) pmmap_ext = namedtuple( 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -ZombieProcess = None -AccessDenied = None -TimeoutExpired = None - # ===================================================================== # --- memory @@ -165,6 +172,16 @@ def cpu_stats(): ctx_switches, interrupts, soft_interrupts, syscalls) +def cpu_freq(): + """Return CPU frequency. + On OSX per-cpu frequency is not supported. + Also, the returned frequency never changes, see: + https://arstechnica.com/civis/viewtopic.php?f=19&t=465002 + """ + curr, min_, max_ = cext.cpu_freq() + return [_common.scpufreq(curr, min_, max_)] + + # ===================================================================== # --- disks # ===================================================================== @@ -175,6 +192,7 @@ def cpu_stats(): def disk_partitions(all=False): + """Return mounted disk partitions as a list of namedtuples.""" retlist = [] partitions = cext.disk_partitions() for partition in partitions: @@ -189,6 +207,29 @@ def disk_partitions(all=False): return retlist +# ===================================================================== +# --- sensors +# ===================================================================== + + +def sensors_battery(): + """Return battery information. + """ + try: + percent, minsleft, power_plugged = cext.sensors_battery() + except NotImplementedError: + # no power source - return None according to interface + return None + power_plugged = power_plugged == 1 + if power_plugged: + secsleft = _common.POWER_TIME_UNLIMITED + elif minsleft == -1: + secsleft = _common.POWER_TIME_UNKNOWN + else: + secsleft = minsleft * 60 + return _common.sbattery(percent, secsleft, power_plugged) + + # ===================================================================== # --- network # ===================================================================== @@ -199,6 +240,7 @@ def disk_partitions(all=False): def net_connections(kind='inet'): + """System-wide network connections.""" # Note: on OSX this will fail with AccessDenied unless # the process is owned by root. ret = [] @@ -240,15 +282,16 @@ def boot_time(): def users(): + """Return currently connected users as a list of namedtuples.""" retlist = [] rawlist = cext.users() for item in rawlist: - user, tty, hostname, tstamp = item + user, tty, hostname, tstamp, pid = item if tty == '~': continue # reboot or shutdown if not tstamp: continue - nt = _common.suser(user, tty or None, hostname or None, tstamp) + nt = _common.suser(user, tty or None, hostname or None, tstamp, pid) retlist.append(nt) return retlist @@ -258,7 +301,22 @@ def users(): # ===================================================================== -pids = cext.pids +def pids(): + ls = cext.pids() + if 0 not in ls: + # On certain OSX versions pids() C doesn't return PID 0 but + # "ps" does and the process is querable via sysctl(): + # https://travis-ci.org/giampaolo/psutil/jobs/309619941 + try: + Process(0).create_time() + ls.append(0) + except NoSuchProcess: + pass + except AccessDenied: + ls.append(0) + return ls + + pid_exists = _psposix.pid_exists @@ -271,22 +329,40 @@ def wrapper(self, *args, **kwargs): try: return fun(self, *args, **kwargs) except OSError as err: - if self.pid == 0: - if 0 in pids(): - raise AccessDenied(self.pid, self._name) - else: - raise if err.errno == errno.ESRCH: - if not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name) - else: - raise ZombieProcess(self.pid, self._name, self._ppid) + raise NoSuchProcess(self.pid, self._name) if err.errno in (errno.EPERM, errno.EACCES): raise AccessDenied(self.pid, self._name) raise return wrapper +@contextlib.contextmanager +def catch_zombie(proc): + """There are some poor C APIs which incorrectly raise ESRCH when + the process is still alive or it's a zombie, or even RuntimeError + (those who don't set errno). This is here in order to solve: + https://github.com/giampaolo/psutil/issues/1044 + """ + try: + yield + except (OSError, RuntimeError) as err: + if isinstance(err, RuntimeError) or err.errno == errno.ESRCH: + try: + # status() is not supposed to lie and correctly detect + # zombies so if it raises ESRCH it's true. + status = proc.status() + except NoSuchProcess: + raise err + else: + if status == _common.STATUS_ZOMBIE: + raise ZombieProcess(proc.pid, proc._name, proc._ppid) + else: + raise AccessDenied(proc.pid, proc._name) + else: + raise + + class Process(object): """Wrapper class around underlying C implementation.""" @@ -307,7 +383,8 @@ def _get_kinfo_proc(self): @memoize_when_activated def _get_pidtaskinfo(self): # Note: should work for PIDs owned by user only. - ret = cext.proc_pidtaskinfo_oneshot(self.pid) + with catch_zombie(self): + ret = cext.proc_pidtaskinfo_oneshot(self.pid) assert len(ret) == len(pidtaskinfo_map) return ret @@ -326,19 +403,18 @@ def name(self): @wrap_exceptions def exe(self): - return cext.proc_exe(self.pid) + with catch_zombie(self): + return cext.proc_exe(self.pid) @wrap_exceptions def cmdline(self): - if not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name) - return cext.proc_cmdline(self.pid) + with catch_zombie(self): + return cext.proc_cmdline(self.pid) @wrap_exceptions def environ(self): - if not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name) - return parse_environ_block(cext.proc_environ(self.pid)) + with catch_zombie(self): + return parse_environ_block(cext.proc_environ(self.pid)) @wrap_exceptions def ppid(self): @@ -347,7 +423,8 @@ def ppid(self): @wrap_exceptions def cwd(self): - return cext.proc_cwd(self.pid) + with catch_zombie(self): + return cext.proc_cwd(self.pid) @wrap_exceptions def uids(self): @@ -397,7 +474,7 @@ def cpu_times(self): rawtuple[pidtaskinfo_map['cpuutime']], rawtuple[pidtaskinfo_map['cpustime']], # children user / system times are not retrievable (set to 0) - 0, 0) + 0.0, 0.0) @wrap_exceptions def create_time(self): @@ -420,7 +497,8 @@ def open_files(self): if self.pid == 0: return [] files = [] - rawlist = cext.proc_open_files(self.pid) + with catch_zombie(self): + rawlist = cext.proc_open_files(self.pid) for path, fd in rawlist: if isfile_strict(path): ntuple = _common.popenfile(path, fd) @@ -433,13 +511,19 @@ def connections(self, kind='inet'): raise ValueError("invalid %r kind argument; choose between %s" % (kind, ', '.join([repr(x) for x in conn_tmap]))) families, types = conn_tmap[kind] - rawlist = cext.proc_connections(self.pid, families, types) + with catch_zombie(self): + rawlist = cext.proc_connections(self.pid, families, types) ret = [] for item in rawlist: fd, fam, type, laddr, raddr, status = item status = TCP_STATUSES[status] fam = sockfam_to_enum(fam) type = socktype_to_enum(type) + if fam in (AF_INET, AF_INET6): + if laddr: + laddr = _common.addr(*laddr) + if raddr: + raddr = _common.addr(*raddr) nt = _common.pconn(fd, fam, type, laddr, raddr, status) ret.append(nt) return ret @@ -448,22 +532,22 @@ def connections(self, kind='inet'): def num_fds(self): if self.pid == 0: return 0 - return cext.proc_num_fds(self.pid) + with catch_zombie(self): + return cext.proc_num_fds(self.pid) @wrap_exceptions def wait(self, timeout=None): - try: - return _psposix.wait_pid(self.pid, timeout) - except _psposix.TimeoutExpired: - raise TimeoutExpired(timeout, self.pid, self._name) + return _psposix.wait_pid(self.pid, timeout, self._name) @wrap_exceptions def nice_get(self): - return cext_posix.getpriority(self.pid) + with catch_zombie(self): + return cext_posix.getpriority(self.pid) @wrap_exceptions def nice_set(self, value): - return cext_posix.setpriority(self.pid, value) + with catch_zombie(self): + return cext_posix.setpriority(self.pid, value) @wrap_exceptions def status(self): @@ -473,7 +557,8 @@ def status(self): @wrap_exceptions def threads(self): - rawlist = cext.proc_threads(self.pid) + with catch_zombie(self): + rawlist = cext.proc_threads(self.pid) retlist = [] for thread_id, utime, stime in rawlist: ntuple = _common.pthread(thread_id, utime, stime) @@ -482,4 +567,5 @@ def threads(self): @wrap_exceptions def memory_maps(self): - return cext.proc_memory_maps(self.pid) + with catch_zombie(self): + return cext.proc_memory_maps(self.pid) diff --git a/psutil/_psposix.py b/psutil/_psposix.py index acbc7855b..6bb8444d8 100644 --- a/psutil/_psposix.py +++ b/psutil/_psposix.py @@ -15,14 +15,10 @@ from ._common import usage_percent from ._compat import PY3 from ._compat import unicode +from ._exceptions import TimeoutExpired -__all__ = ['TimeoutExpired', 'pid_exists', 'wait_pid', 'disk_usage', - 'get_terminal_map'] - - -class TimeoutExpired(Exception): - pass +__all__ = ['pid_exists', 'wait_pid', 'disk_usage', 'get_terminal_map'] def pid_exists(pid): @@ -53,7 +49,7 @@ def pid_exists(pid): return True -def wait_pid(pid, timeout=None): +def wait_pid(pid, timeout=None, proc_name=None): """Wait for process with pid 'pid' to terminate and return its exit status code as an integer. @@ -67,7 +63,7 @@ def wait_pid(pid, timeout=None): def check_timeout(delay): if timeout is not None: if timer() >= stop_at: - raise TimeoutExpired() + raise TimeoutExpired(timeout, pid=pid, name=proc_name) time.sleep(delay) return min(delay * 2, 0.04) @@ -110,7 +106,7 @@ def waitcall(): # process exited due to a signal; return the integer of # that signal if os.WIFSIGNALED(status): - return os.WTERMSIG(status) + return -os.WTERMSIG(status) # process exited using exit(2) system call; return the # integer exit(2) system call has been called with elif os.WIFEXITED(status): @@ -127,21 +123,23 @@ def disk_usage(path): total and used disk space whereas "free" and "percent" represent the "free" and "used percent" user disk space. """ - try: + if PY3: st = os.statvfs(path) - except UnicodeEncodeError: - if not PY3 and isinstance(path, unicode): - # this is a bug with os.statvfs() and unicode on - # Python 2, see: - # - https://github.com/giampaolo/psutil/issues/416 - # - http://bugs.python.org/issue18695 - try: - path = path.encode(sys.getfilesystemencoding()) - except UnicodeEncodeError: - pass + else: + # os.statvfs() does not support unicode on Python 2: + # - https://github.com/giampaolo/psutil/issues/416 + # - http://bugs.python.org/issue18695 + try: st = os.statvfs(path) - else: - raise + except UnicodeEncodeError: + if isinstance(path, unicode): + try: + path = path.encode(sys.getfilesystemencoding()) + except UnicodeEncodeError: + pass + st = os.statvfs(path) + else: + raise # Total space which is only available to root (unless changed # at system level). @@ -169,6 +167,9 @@ def disk_usage(path): @memoize def get_terminal_map(): + """Get a map of device-id -> path as a dict. + Used by Process.terminal() + """ ret = {} ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*') for name in ls: diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py index a62e0bf55..5471d5aa4 100644 --- a/psutil/_pssunos.py +++ b/psutil/_pssunos.py @@ -10,11 +10,13 @@ import subprocess import sys from collections import namedtuple +from socket import AF_INET from . import _common from . import _psposix from . import _psutil_posix as cext_posix from . import _psutil_sunos as cext +from ._common import AF_INET6 from ._common import isfile_strict from ._common import memoize_when_activated from ._common import sockfam_to_enum @@ -22,13 +24,16 @@ from ._common import usage_percent from ._compat import b from ._compat import PY3 +from ._exceptions import AccessDenied +from ._exceptions import NoSuchProcess +from ._exceptions import ZombieProcess __extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"] # ===================================================================== -# --- constants +# --- globals # ===================================================================== @@ -66,42 +71,47 @@ cext.TCPS_BOUND: CONN_BOUND, # sunos specific } +proc_info_map = dict( + ppid=0, + rss=1, + vms=2, + create_time=3, + nice=4, + num_threads=5, + status=6, + ttynr=7) + # ===================================================================== # --- named tuples # ===================================================================== +# psutil.cpu_times() scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait']) +# psutil.cpu_times(percpu=True) pcputimes = namedtuple('pcputimes', ['user', 'system', 'children_user', 'children_system']) +# psutil.virtual_memory() svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +# psutil.Process.memory_info() pmem = namedtuple('pmem', ['rss', 'vms']) pfullmem = pmem +# psutil.Process.memory_maps(grouped=True) pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss', 'anonymous', 'locked']) +# psutil.Process.memory_maps(grouped=False) pmmap_ext = namedtuple( 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) -# ===================================================================== -# --- exceptions -# ===================================================================== - - -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -ZombieProcess = None -AccessDenied = None -TimeoutExpired = None - - # ===================================================================== # --- utils # ===================================================================== def get_procfs_path(): + """Return updated psutil.PROCFS_PATH constant.""" return sys.modules['psutil'].PROCFS_PATH @@ -111,7 +121,8 @@ def get_procfs_path(): def virtual_memory(): - # we could have done this with kstat, but imho this is good enough + """Report virtual memory metrics.""" + # we could have done this with kstat, but IMHO this is good enough total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE # note: there's no difference on Solaris free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE @@ -121,6 +132,7 @@ def virtual_memory(): def swap_memory(): + """Report swap memory metrics.""" sin, sout = cext.swap_mem() # XXX # we are supposed to get total/free by doing so: @@ -184,6 +196,7 @@ def cpu_count_physical(): def cpu_stats(): + """Return various CPU stats as a named tuple.""" ctx_switches, interrupts, syscalls, traps = cext.cpu_stats() soft_interrupts = 0 return _common.scpustats(ctx_switches, interrupts, soft_interrupts, @@ -249,6 +262,11 @@ def net_connections(kind, _pid=-1): continue if type_ not in types: continue + if fam in (AF_INET, AF_INET6): + if laddr: + laddr = _common.addr(*laddr) + if raddr: + raddr = _common.addr(*raddr) status = TCP_STATUSES[status] fam = sockfam_to_enum(fam) type_ = socktype_to_enum(type_) @@ -287,7 +305,7 @@ def users(): rawlist = cext.users() localhost = (':0.0', ':0') for item in rawlist: - user, tty, hostname, tstamp, user_process = item + user, tty, hostname, tstamp, user_process, pid = item # note: the underlying C function includes entries about # system boot, run level and others. We might want # to use them in the future. @@ -295,7 +313,7 @@ def users(): continue if hostname in localhost: hostname = 'localhost' - nt = _common.suser(user, tty, hostname, tstamp) + nt = _common.suser(user, tty, hostname, tstamp, pid) retlist.append(nt) return retlist @@ -370,7 +388,9 @@ def _proc_name_and_args(self): @memoize_when_activated def _proc_basic_info(self): - return cext.proc_basic_info(self.pid, self._procfs_path) + ret = cext.proc_basic_info(self.pid, self._procfs_path) + assert len(ret) == len(proc_info_map) + return ret @memoize_when_activated def _proc_cred(self): @@ -398,23 +418,32 @@ def exe(self): def cmdline(self): return self._proc_name_and_args()[1].split(' ') + @wrap_exceptions + def environ(self): + return cext.proc_environ(self.pid, self._procfs_path) + @wrap_exceptions def create_time(self): - return self._proc_basic_info()[3] + return self._proc_basic_info()[proc_info_map['create_time']] @wrap_exceptions def num_threads(self): - return self._proc_basic_info()[5] + return self._proc_basic_info()[proc_info_map['num_threads']] @wrap_exceptions def nice_get(self): - # For some reason getpriority(3) return ESRCH (no such process) - # for certain low-pid processes, no matter what (even as root). + # Note #1: for some reason getpriority(3) return ESRCH (no such + # process) for certain low-pid processes, no matter what (even + # as root). # The process actually exists though, as it has a name, # creation time, etc. # The best thing we can do here appears to be raising AD. # Note: tested on Solaris 11; on Open Solaris 5 everything is # fine. + # + # Note #2: we also can get niceness from /proc/pid/psinfo + # but it's wrong, see: + # https://github.com/giampaolo/psutil/issues/1082 try: return cext_posix.getpriority(self.pid) except EnvironmentError as err: @@ -437,7 +466,7 @@ def nice_set(self, value): @wrap_exceptions def ppid(self): - self._ppid = self._proc_basic_info()[0] + self._ppid = self._proc_basic_info()[proc_info_map['ppid']] return self._ppid @wrap_exceptions @@ -468,12 +497,16 @@ def cpu_times(self): raise return _common.pcputimes(*times) + @wrap_exceptions + def cpu_num(self): + return cext.proc_cpu_num(self.pid, self._procfs_path) + @wrap_exceptions def terminal(self): procfs_path = self._procfs_path hit_enoent = False tty = wrap_exceptions( - self._proc_basic_info()[0]) + self._proc_basic_info()[proc_info_map['ttynr']]) if tty != cext.PRNODEV: for x in (0, 1, 2, 255): try: @@ -506,14 +539,15 @@ def cwd(self): @wrap_exceptions def memory_info(self): ret = self._proc_basic_info() - rss, vms = ret[1] * 1024, ret[2] * 1024 + rss = ret[proc_info_map['rss']] * 1024 + vms = ret[proc_info_map['vms']] * 1024 return pmem(rss, vms) memory_full_info = memory_info @wrap_exceptions def status(self): - code = self._proc_basic_info()[6] + code = self._proc_basic_info()[proc_info_map['status']] # XXX is '?' legit? (we're not supposed to return it anyway) return PROC_STATUSES.get(code, '?') @@ -688,7 +722,4 @@ def num_ctx_switches(self): @wrap_exceptions def wait(self, timeout=None): - try: - return _psposix.wait_pid(self.pid, timeout) - except _psposix.TimeoutExpired: - raise TimeoutExpired(timeout, self.pid, self._name) + return _psposix.wait_pid(self.pid, timeout, self._name) diff --git a/psutil/_psutil_aix.c b/psutil/_psutil_aix.c new file mode 100644 index 000000000..916254d5a --- /dev/null +++ b/psutil/_psutil_aix.c @@ -0,0 +1,988 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola' + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * AIX support is experimental at this time. + * The following functions and methods are unsupported on the AIX platform: + * - psutil.Process.memory_maps + * + * Known limitations: + * - psutil.Process.io_counters read count is always 0 + * - psutil.Process.threads may not be available on older AIX versions + * - reading basic process info may fail or return incorrect values when + * process is starting (see IBM APAR IV58499 - fixed in newer AIX versions) + * - sockets and pipes may not be counted in num_fds (fixed in newer AIX + * versions) + * + * Useful resources: + * - proc filesystem: http://www-01.ibm.com/support/knowledgecenter/ + * ssw_aix_61/com.ibm.aix.files/proc.htm + * - libperfstat: http://www-01.ibm.com/support/knowledgecenter/ + * ssw_aix_61/com.ibm.aix.files/libperfstat.h.htm + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arch/aix/ifaddrs.h" +#include "arch/aix/net_connections.h" +#include "arch/aix/common.h" +#include "_psutil_common.h" +#include "_psutil_posix.h" + + +#define TV2DOUBLE(t) (((t).tv_nsec * 0.000000001) + (t).tv_sec) + +/* + * Read a file content and fills a C structure with it. + */ +int +psutil_file_to_struct(char *path, void *fstruct, size_t size) { + int fd; + size_t nbytes; + fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return 0; + } + nbytes = read(fd, fstruct, size); + if (nbytes <= 0) { + close(fd); + PyErr_SetFromErrno(PyExc_OSError); + return 0; + } + if (nbytes != size) { + close(fd); + PyErr_SetString(PyExc_RuntimeError, "structure size mismatch"); + return 0; + } + close(fd); + return nbytes; +} + + +/* + * Return process ppid, rss, vms, ctime, nice, nthreads, status and tty + * as a Python tuple. + */ +static PyObject * +psutil_proc_basic_info(PyObject *self, PyObject *args) { + int pid; + char path[100]; + psinfo_t info; + pstatus_t status; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + + sprintf(path, "%s/%i/psinfo", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + + if (info.pr_nlwp == 0 && info.pr_lwp.pr_lwpid == 0) { + // From the /proc docs: "If the process is a zombie, the pr_nlwp + // and pr_lwp.pr_lwpid flags are zero." + status.pr_stat = SZOMB; + } else if (info.pr_flag & SEXIT) { + // "exiting" processes don't have /proc//status + // There are other "exiting" processes that 'ps' shows as "active" + status.pr_stat = SACTIVE; + } else { + sprintf(path, "%s/%i/status", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&status, sizeof(status))) + return NULL; + } + + return Py_BuildValue("KKKdiiiK", + (unsigned long long) info.pr_ppid, // parent pid + (unsigned long long) info.pr_rssize, // rss + (unsigned long long) info.pr_size, // vms + TV2DOUBLE(info.pr_start), // create time + (int) info.pr_lwp.pr_nice, // nice + (int) info.pr_nlwp, // no. of threads + (int) status.pr_stat, // status code + (unsigned long long)info.pr_ttydev // tty nr + ); +} + + +/* + * Return process name and args as a Python tuple. + */ +static PyObject * +psutil_proc_name_and_args(PyObject *self, PyObject *args) { + int pid; + char path[100]; + psinfo_t info; + const char *procfs_path; + PyObject *py_name = NULL; + PyObject *py_args = NULL; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/psinfo", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + + py_name = PyUnicode_DecodeFSDefault(info.pr_fname); + if (!py_name) + goto error; + py_args = PyUnicode_DecodeFSDefault(info.pr_psargs); + if (!py_args) + goto error; + py_retlist = Py_BuildValue("OO", py_name, py_args); + if (!py_retlist) + goto error; + Py_DECREF(py_name); + Py_DECREF(py_args); + return py_retlist; + +error: + Py_XDECREF(py_name); + Py_XDECREF(py_args); + Py_XDECREF(py_retlist); + return NULL; +} + + +#ifdef CURR_VERSION_THREAD +/* + * Retrieves all threads used by process returning a list of tuples + * including thread id, user time and system time. + */ +static PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + long pid; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + perfstat_thread_t *threadt = NULL; + perfstat_id_t id; + int i, rc, thread_count; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + /* Get the count of threads */ + thread_count = perfstat_thread(NULL, NULL, sizeof(perfstat_thread_t), 0); + if (thread_count <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + /* Allocate enough memory */ + threadt = (perfstat_thread_t *)calloc(thread_count, + sizeof(perfstat_thread_t)); + if (threadt == NULL) { + PyErr_NoMemory(); + goto error; + } + + strcpy(id.name, ""); + rc = perfstat_thread(&id, threadt, sizeof(perfstat_thread_t), + thread_count); + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < thread_count; i++) { + if (threadt[i].pid != pid) + continue; + + py_tuple = Py_BuildValue("Idd", + threadt[i].tid, + threadt[i].ucpu_time, + threadt[i].scpu_time); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + free(threadt); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (threadt != NULL) + free(threadt); + return NULL; +} +#endif + + +static PyObject * +psutil_proc_io_counters(PyObject *self, PyObject *args) { + long pid; + int rc; + perfstat_process_t procinfo; + perfstat_id_t id; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + snprintf(id.name, sizeof(id.name), "%ld", pid); + rc = perfstat_process(&id, &procinfo, sizeof(perfstat_process_t), 1); + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("(KKKK)", + procinfo.inOps, // XXX always 0 + procinfo.outOps, + procinfo.inBytes, // XXX always 0 + procinfo.outBytes); +} + + +/* + * Return process user and system CPU times as a Python tuple. + */ +static PyObject * +psutil_proc_cpu_times(PyObject *self, PyObject *args) { + int pid; + char path[100]; + pstatus_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/status", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + // results are more precise than os.times() + return Py_BuildValue("dddd", + TV2DOUBLE(info.pr_utime), + TV2DOUBLE(info.pr_stime), + TV2DOUBLE(info.pr_cutime), + TV2DOUBLE(info.pr_cstime)); +} + + +/* + * Return process uids/gids as a Python tuple. + */ +static PyObject * +psutil_proc_cred(PyObject *self, PyObject *args) { + int pid; + char path[100]; + prcred_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/cred", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("iiiiii", + info.pr_ruid, info.pr_euid, info.pr_suid, + info.pr_rgid, info.pr_egid, info.pr_sgid); +} + + +/* + * Return process voluntary and involuntary context switches as a Python tuple. + */ +static PyObject * +psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) { + PyObject *py_tuple = NULL; + pid32_t requested_pid; + pid32_t pid = 0; + int np = 0; + struct procentry64 *processes = (struct procentry64 *)NULL; + struct procentry64 *p; + + if (! PyArg_ParseTuple(args, "i", &requested_pid)) + return NULL; + + processes = psutil_read_process_table(&np); + if (!processes) + return NULL; + + /* Loop through processes */ + for (p = processes; np > 0; np--, p++) { + pid = p->pi_pid; + if (requested_pid != pid) + continue; + py_tuple = Py_BuildValue("LL", + (long long) p->pi_ru.ru_nvcsw, /* voluntary context switches */ + (long long) p->pi_ru.ru_nivcsw); /* involuntary */ + free(processes); + return py_tuple; + } + + /* finished iteration without finding requested pid */ + free(processes); + return NoSuchProcess(""); +} + + +/* + * Return users currently connected on the system. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) { + struct utmpx *ut; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + PyObject *py_user_proc = NULL; + + if (py_retlist == NULL) + return NULL; + + setutxent(); + while (NULL != (ut = getutxent())) { + if (ut->ut_type == USER_PROCESS) + py_user_proc = Py_True; + else + py_user_proc = Py_False; + py_username = PyUnicode_DecodeFSDefault(ut->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); + if (! py_hostname) + goto error; + py_tuple = Py_BuildValue( + "(OOOfOi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)ut->ut_tv.tv_sec, // tstamp + py_user_proc, // (bool) user process + ut->ut_pid // process id + ); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_username); + Py_DECREF(py_tty); + Py_DECREF(py_hostname); + Py_DECREF(py_tuple); + } + endutxent(); + + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (ut != NULL) + endutxent(); + return NULL; +} + + +/* + * Return disk mounted partitions as a list of tuples including device, + * mount point and filesystem type. + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + FILE *file = NULL; + struct mntent * mt = NULL; + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + file = setmntent(MNTTAB, "rb"); + if (file == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + mt = getmntent(file); + while (mt != NULL) { + py_dev = PyUnicode_DecodeFSDefault(mt->mnt_fsname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(mt->mnt_dir); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue( + "(OOss)", + py_dev, // device + py_mountp, // mount point + mt->mnt_type, // fs type + mt->mnt_opts); // options + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_dev); + Py_DECREF(py_mountp); + Py_DECREF(py_tuple); + mt = getmntent(file); + } + endmntent(file); + return py_retlist; + +error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (file != NULL) + endmntent(file); + return NULL; +} + + +/* + * Return a list of tuples for network I/O statistics. + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) { + perfstat_netinterface_t *statp = NULL; + int tot, i; + perfstat_id_t first; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + + if (py_retdict == NULL) + return NULL; + + /* check how many perfstat_netinterface_t structures are available */ + tot = perfstat_netinterface( + NULL, NULL, sizeof(perfstat_netinterface_t), 0); + if (tot == 0) { + // no network interfaces - return empty dict + return py_retdict; + } + if (tot < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + statp = (perfstat_netinterface_t *) + malloc(tot * sizeof(perfstat_netinterface_t)); + if (statp == NULL) { + PyErr_NoMemory(); + goto error; + } + strcpy(first.name, FIRST_NETINTERFACE); + tot = perfstat_netinterface(&first, statp, + sizeof(perfstat_netinterface_t), tot); + if (tot < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < tot; i++) { + py_ifc_info = Py_BuildValue("(KKKKKKKK)", + statp[i].obytes, /* number of bytes sent on interface */ + statp[i].ibytes, /* number of bytes received on interface */ + statp[i].opackets, /* number of packets sent on interface */ + statp[i].ipackets, /* number of packets received on interface */ + statp[i].ierrors, /* number of input errors on interface */ + statp[i].oerrors, /* number of output errors on interface */ + statp[i].if_iqdrops, /* Dropped on input, this interface */ + statp[i].xmitdrops /* number of packets not transmitted */ + ); + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, statp[i].name, py_ifc_info)) + goto error; + Py_DECREF(py_ifc_info); + } + + free(statp); + return py_retdict; + +error: + if (statp != NULL) + free(statp); + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + return NULL; +} + + +static PyObject* +psutil_net_if_stats(PyObject* self, PyObject* args) { + char *nic_name; + int sock = 0; + int ret; + int mtu; + struct ifreq ifr; + PyObject *py_is_up = NULL; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + goto error; + + strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + + // is up? + ret = ioctl(sock, SIOCGIFFLAGS, &ifr); + if (ret == -1) + goto error; + if ((ifr.ifr_flags & IFF_UP) != 0) + py_is_up = Py_True; + else + py_is_up = Py_False; + Py_INCREF(py_is_up); + + // MTU + ret = ioctl(sock, SIOCGIFMTU, &ifr); + if (ret == -1) + goto error; + mtu = ifr.ifr_mtu; + + close(sock); + py_retlist = Py_BuildValue("[Oi]", py_is_up, mtu); + if (!py_retlist) + goto error; + Py_DECREF(py_is_up); + return py_retlist; + +error: + Py_XDECREF(py_is_up); + if (sock != 0) + close(sock); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + float boot_time = 0.0; + struct utmpx *ut; + + setutxent(); + while (NULL != (ut = getutxent())) { + if (ut->ut_type == BOOT_TIME) { + boot_time = (float)ut->ut_tv.tv_sec; + break; + } + } + endutxent(); + if (boot_time == 0.0) { + /* could not find BOOT_TIME in getutxent loop */ + PyErr_SetString(PyExc_RuntimeError, "can't determine boot time"); + return NULL; + } + return Py_BuildValue("f", boot_time); +} + + +/* + * Return a Python list of tuple representing per-cpu times + */ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + int ncpu, rc, i; + perfstat_cpu_t *cpu = NULL; + perfstat_id_t id; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + /* get the number of cpus in ncpu */ + ncpu = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0); + if (ncpu <= 0){ + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + /* allocate enough memory to hold the ncpu structures */ + cpu = (perfstat_cpu_t *) malloc(ncpu * sizeof(perfstat_cpu_t)); + if (cpu == NULL) { + PyErr_NoMemory(); + goto error; + } + + strcpy(id.name, ""); + rc = perfstat_cpu(&id, cpu, sizeof(perfstat_cpu_t), ncpu); + + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < ncpu; i++) { + py_cputime = Py_BuildValue( + "(dddd)", + (double)cpu[i].user, + (double)cpu[i].sys, + (double)cpu[i].idle, + (double)cpu[i].wait); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + } + free(cpu); + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + if (cpu != NULL) + free(cpu); + return NULL; +} + + +/* + * Return disk IO statistics. + */ +static PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + perfstat_disk_t *diskt = NULL; + perfstat_id_t id; + int i, rc, disk_count; + + if (py_retdict == NULL) + return NULL; + + /* Get the count of disks */ + disk_count = perfstat_disk(NULL, NULL, sizeof(perfstat_disk_t), 0); + if (disk_count <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + /* Allocate enough memory */ + diskt = (perfstat_disk_t *)calloc(disk_count, + sizeof(perfstat_disk_t)); + if (diskt == NULL) { + PyErr_NoMemory(); + goto error; + } + + strcpy(id.name, FIRST_DISK); + rc = perfstat_disk(&id, diskt, sizeof(perfstat_disk_t), + disk_count); + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < disk_count; i++) { + py_disk_info = Py_BuildValue( + "KKKKKK", + diskt[i].__rxfers, + diskt[i].xfers - diskt[i].__rxfers, + diskt[i].rblks * diskt[i].bsize, + diskt[i].wblks * diskt[i].bsize, + diskt[i].rserv / 1000 / 1000, // from nano to milli secs + diskt[i].wserv / 1000 / 1000 // from nano to milli secs + ); + if (py_disk_info == NULL) + goto error; + if (PyDict_SetItemString(py_retdict, diskt[i].name, + py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + free(diskt); + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (diskt != NULL) + free(diskt); + return NULL; +} + + +/* + * Return virtual memory usage statistics. + */ +static PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + int rc; + int pagesize = getpagesize(); + perfstat_memory_total_t memory; + + rc = perfstat_memory_total( + NULL, &memory, sizeof(perfstat_memory_total_t), 1); + if (rc <= 0){ + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("KKKKK", + (unsigned long long) memory.real_total * pagesize, + (unsigned long long) memory.real_avail * pagesize, + (unsigned long long) memory.real_free * pagesize, + (unsigned long long) memory.real_pinned * pagesize, + (unsigned long long) memory.real_inuse * pagesize + ); +} + + +/* + * Return stats about swap memory. + */ +static PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + int rc; + int pagesize = getpagesize(); + perfstat_memory_total_t memory; + + rc = perfstat_memory_total( + NULL, &memory, sizeof(perfstat_memory_total_t), 1); + if (rc <= 0){ + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("KKKK", + (unsigned long long) memory.pgsp_total * pagesize, + (unsigned long long) memory.pgsp_free * pagesize, + (unsigned long long) memory.pgins * pagesize, + (unsigned long long) memory.pgouts * pagesize + ); +} + + +/* + * Return CPU statistics. + */ +static PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + int ncpu, rc, i; + // perfstat_cpu_total_t doesn't have invol/vol cswitch, only pswitch + // which is apparently something else. We have to sum over all cpus + perfstat_cpu_t *cpu = NULL; + perfstat_id_t id; + u_longlong_t cswitches = 0; + u_longlong_t devintrs = 0; + u_longlong_t softintrs = 0; + u_longlong_t syscall = 0; + + /* get the number of cpus in ncpu */ + ncpu = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0); + if (ncpu <= 0){ + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + /* allocate enough memory to hold the ncpu structures */ + cpu = (perfstat_cpu_t *) malloc(ncpu * sizeof(perfstat_cpu_t)); + if (cpu == NULL) { + PyErr_NoMemory(); + goto error; + } + + strcpy(id.name, ""); + rc = perfstat_cpu(&id, cpu, sizeof(perfstat_cpu_t), ncpu); + + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < ncpu; i++) { + cswitches += cpu[i].invol_cswitch + cpu[i].vol_cswitch; + devintrs += cpu[i].devintrs; + softintrs += cpu[i].softintrs; + syscall += cpu[i].syscall; + } + + free(cpu); + + return Py_BuildValue( + "KKKK", + cswitches, + devintrs, + softintrs, + syscall + ); + +error: + if (cpu != NULL) + free(cpu); + return NULL; +} + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = +{ + // --- process-related functions + {"proc_basic_info", psutil_proc_basic_info, METH_VARARGS, + "Return process ppid, rss, vms, ctime, nice, nthreads, status and tty"}, + {"proc_name_and_args", psutil_proc_name_and_args, METH_VARARGS, + "Return process name and args."}, + {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, + "Return process user and system CPU times."}, + {"proc_cred", psutil_proc_cred, METH_VARARGS, + "Return process uids/gids."}, +#ifdef CURR_VERSION_THREAD + {"proc_threads", psutil_proc_threads, METH_VARARGS, + "Return process threads"}, +#endif + {"proc_io_counters", psutil_proc_io_counters, METH_VARARGS, + "Get process I/O counters."}, + {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS, + "Get process I/O counters."}, + + // --- system-related functions + {"users", psutil_users, METH_VARARGS, + "Return currently connected users."}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return disk partitions."}, + {"boot_time", psutil_boot_time, METH_VARARGS, + "Return system boot time in seconds since the EPOCH."}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-cpu times as a list of tuples"}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, + "Return a Python dict of tuples for disk I/O statistics."}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS, + "Return system virtual memory usage statistics"}, + {"swap_mem", psutil_swap_mem, METH_VARARGS, + "Return stats about swap memory, in bytes"}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS, + "Return a Python dict of tuples for network I/O statistics."}, + {"net_connections", psutil_net_connections, METH_VARARGS, + "Return system-wide connections"}, + {"net_if_stats", psutil_net_if_stats, METH_VARARGS, + "Return NIC stats (isup, mtu)"}, + {"cpu_stats", psutil_cpu_stats, METH_VARARGS, + "Return CPU statistics"}, + + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + + {NULL, NULL, 0, NULL} +}; + + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_aix_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_aix_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_aix", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_aix_traverse, + psutil_aix_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_aix(void) + +#else +#define INITERROR return + +void init_psutil_aix(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_aix", PsutilMethods); +#endif + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + + PyModule_AddIntConstant(module, "SIDL", SIDL); + PyModule_AddIntConstant(module, "SZOMB", SZOMB); + PyModule_AddIntConstant(module, "SACTIVE", SACTIVE); + PyModule_AddIntConstant(module, "SSWAP", SSWAP); + PyModule_AddIntConstant(module, "SSTOP", SSTOP); + + PyModule_AddIntConstant(module, "TCPS_CLOSED", TCPS_CLOSED); + PyModule_AddIntConstant(module, "TCPS_CLOSING", TCPS_CLOSING); + PyModule_AddIntConstant(module, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT); + PyModule_AddIntConstant(module, "TCPS_LISTEN", TCPS_LISTEN); + PyModule_AddIntConstant(module, "TCPS_ESTABLISHED", TCPS_ESTABLISHED); + PyModule_AddIntConstant(module, "TCPS_SYN_SENT", TCPS_SYN_SENT); + PyModule_AddIntConstant(module, "TCPS_SYN_RCVD", TCPS_SYN_RECEIVED); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2); + PyModule_AddIntConstant(module, "TCPS_LAST_ACK", TCPS_LAST_ACK); + PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT); + PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + + psutil_setup(); + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c index 33448ccf1..9a2ed04bc 100644 --- a/psutil/_psutil_bsd.c +++ b/psutil/_psutil_bsd.c @@ -7,7 +7,7 @@ * Platform-specific module methods for FreeBSD and OpenBSD. * OpenBSD references: - * - OpenBSD source code: http://anoncvs.spacehopper.org/openbsd-src/ + * - OpenBSD source code: https://github.com/openbsd/src * * OpenBSD / NetBSD: missing APIs compared to FreeBSD implementation: * - psutil.net_connections() @@ -15,8 +15,8 @@ * - psutil.Process.memory_maps() */ -#if defined(__NetBSD__) -#define _KMEMUSER +#if defined(PSUTIL_NETBSD) + #define _KMEMUSER #endif #include @@ -60,18 +60,13 @@ #include #include "_psutil_common.h" +#include "_psutil_posix.h" -#ifdef __FreeBSD__ - #include "arch/bsd/freebsd.h" - #include "arch/bsd/freebsd_socks.h" -#elif __OpenBSD__ - #include "arch/bsd/openbsd.h" -#elif __NetBSD__ - #include "arch/bsd/netbsd.h" - #include "arch/bsd/netbsd_socks.h" -#endif +#ifdef PSUTIL_FREEBSD + #include "arch/freebsd/specific.h" + #include "arch/freebsd/sys_socks.h" + #include "arch/freebsd/proc_socks.h" -#ifdef __FreeBSD__ #include #include // get io counters #include // process open files, shared libs (kinfo_getvmmap) @@ -80,37 +75,39 @@ #else #include #endif -#endif +#elif PSUTIL_OPENBSD + #include "arch/openbsd/specific.h" -#ifdef __OpenBSD__ #include #include // for VREG #define _KERNEL // for DTYPE_VNODE #include #undef _KERNEL #include // for CPUSTATES & CP_* -#endif +#elif PSUTIL_NETBSD + #include "arch/netbsd/specific.h" + #include "arch/netbsd/socks.h" -#if defined(__NetBSD__) #include #include // for VREG #include // for CPUSTATES & CP_* #ifndef DTYPE_VNODE - #define DTYPE_VNODE 1 + #define DTYPE_VNODE 1 #endif #endif + // convert a timeval struct to a double #define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) -#ifdef __FreeBSD__ +#ifdef PSUTIL_FREEBSD // convert a bintime struct to milliseconds #define PSUTIL_BT2MSEC(bt) (bt.sec * 1000 + (((uint64_t) 1000000000 * \ (uint32_t) (bt.frac >> 32) ) >> 32 ) / 1000000) #endif -#if defined(__OpenBSD__) || defined (__NetBSD__) +#if defined(PSUTIL_OPENBSD) || defined (PSUTIL_NETBSD) #define PSUTIL_KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0) #endif @@ -146,9 +143,9 @@ psutil_pids(PyObject *self, PyObject *args) { if (num_processes > 0) { orig_address = proclist; // save so we can free it after we're done for (idx = 0; idx < num_processes; idx++) { -#ifdef __FreeBSD__ +#ifdef PSUTIL_FREEBSD py_pid = Py_BuildValue("i", proclist->ki_pid); -#elif defined(__OpenBSD__) || defined(__NetBSD__) +#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD) py_pid = Py_BuildValue("i", proclist->p_pid); #endif if (!py_pid) @@ -183,10 +180,8 @@ psutil_boot_time(PyObject *self, PyObject *args) { struct timeval boottime; size_t len = sizeof(boottime); - if (sysctl(request, 2, &boottime, &len, NULL, 0) == -1) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } + if (sysctl(request, 2, &boottime, &len, NULL, 0) == -1) + return PyErr_SetFromErrno(PyExc_OSError); return Py_BuildValue("d", (double)boottime.tv_sec); } @@ -203,6 +198,7 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) { long memtext; long memdata; long memstack; + int oncpu; kinfo_proc kp; long pagesize = sysconf(_SC_PAGESIZE); char str[1000]; @@ -215,16 +211,12 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) { return NULL; // Process -#ifdef __FreeBSD__ +#ifdef PSUTIL_FREEBSD sprintf(str, "%s", kp.ki_comm); -#elif defined(__OpenBSD__) || defined(__NetBSD__) +#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD) sprintf(str, "%s", kp.p_comm); #endif -#if PY_MAJOR_VERSION >= 3 py_name = PyUnicode_DecodeFSDefault(str); -#else - py_name = Py_BuildValue("s", str); -#endif if (! py_name) { // Likely a decoding error. We don't want to fail the whole // operation. The python module may retry with proc_name(). @@ -233,7 +225,7 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) { } // Calculate memory. -#ifdef __FreeBSD__ +#ifdef PSUTIL_FREEBSD rss = (long)kp.ki_rssize * pagesize; vms = (long)kp.ki_size; memtext = (long)kp.ki_tsize * pagesize; @@ -241,15 +233,15 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) { memstack = (long)kp.ki_ssize * pagesize; #else rss = (long)kp.p_vm_rssize * pagesize; - #ifdef __OpenBSD__ + #ifdef PSUTIL_OPENBSD // VMS, this is how ps determines it on OpenBSD: - // http://anoncvs.spacehopper.org/openbsd-src/tree/bin/ps/print.c#n461 - // vms + // https://github.com/openbsd/src/blob/ + // 588f7f8c69786211f2d16865c552afb91b1c7cba/bin/ps/print.c#L505 vms = (long)(kp.p_vm_dsize + kp.p_vm_ssize + kp.p_vm_tsize) * pagesize; - #elif __NetBSD__ + #elif PSUTIL_NETBSD // VMS, this is how top determines it on NetBSD: - // ftp://ftp.iij.ad.jp/pub/NetBSD/NetBSD-release-6/src/external/bsd/ - // top/dist/machine/m_netbsd.c + // https://github.com/IIJ-NetBSD/netbsd-src/blob/master/external/ + // bsd/top/dist/machine/m_netbsd.c vms = (long)kp.p_vm_msize * pagesize; #endif memtext = (long)kp.p_vm_tsize * pagesize; @@ -257,10 +249,27 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) { memstack = (long)kp.p_vm_ssize * pagesize; #endif +#ifdef PSUTIL_FREEBSD + // what CPU we're on; top was used as an example: + // https://svnweb.freebsd.org/base/head/usr.bin/top/machine.c? + // view=markup&pathrev=273835 + // XXX - note: for "intr" PID this is -1. + if (kp.ki_stat == SRUN && kp.ki_oncpu != NOCPU) + oncpu = kp.ki_oncpu; + else + oncpu = kp.ki_lastcpu; +#else + // On Net/OpenBSD we have kp.p_cpuid but it appears it's always + // set to KI_NOCPU. Even if it's not, ki_lastcpu does not exist + // so there's no way to determine where "sleeping" processes + // were. Not supported. + oncpu = -1; +#endif + // Return a single big tuple with all process info. py_retlist = Py_BuildValue( - "(lillllllidllllddddlllllO)", -#ifdef __FreeBSD__ + "(lillllllidllllddddlllllbO)", +#ifdef PSUTIL_FREEBSD // (long)kp.ki_ppid, // (long) ppid (int)kp.ki_stat, // (int) status @@ -292,7 +301,9 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) { memtext, // (long) mem text memdata, // (long) mem data memstack, // (long) mem stack -#elif defined(__OpenBSD__) || defined(__NetBSD__) + // others + oncpu, // (int) the CPU we are on +#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD) // (long)kp.p_ppid, // (long) ppid (int)kp.p_stat, // (int) status @@ -326,6 +337,8 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) { memtext, // (long) mem text memdata, // (long) mem data memstack, // (long) mem stack + // others + oncpu, // (int) the CPU we are on #endif py_name // (pystr) name ); @@ -352,17 +365,12 @@ psutil_proc_name(PyObject *self, PyObject *args) { if (psutil_kinfo_proc(pid, &kp) == -1) return NULL; -#ifdef __FreeBSD__ +#ifdef PSUTIL_FREEBSD sprintf(str, "%s", kp.ki_comm); -#elif defined(__OpenBSD__) || defined(__NetBSD__) +#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD) sprintf(str, "%s", kp.p_comm); #endif - -#if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeFSDefault(str); -#else - return Py_BuildValue("s", str); -#endif } @@ -376,12 +384,9 @@ psutil_proc_cmdline(PyObject *self, PyObject *args) { if (! PyArg_ParseTuple(args, "l", &pid)) return NULL; - py_retlist = psutil_get_cmdline(pid); - // psutil_get_cmdline() returns NULL only if psutil_cmd_args - // failed with ESRCH (no process with that PID) - if (NULL == py_retlist) - return PyErr_SetFromErrno(PyExc_OSError); + if (py_retlist == NULL) + return NULL; return Py_BuildValue("N", py_retlist); } @@ -412,7 +417,7 @@ psutil_cpu_count_logical(PyObject *self, PyObject *args) { */ static PyObject * psutil_cpu_times(PyObject *self, PyObject *args) { -#if defined(__NetBSD__) +#ifdef PSUTIL_NETBSD u_int64_t cpu_time[CPUSTATES]; #else long cpu_time[CPUSTATES]; @@ -420,17 +425,14 @@ psutil_cpu_times(PyObject *self, PyObject *args) { size_t size = sizeof(cpu_time); int ret; -#if defined(__FreeBSD__) || defined(__NetBSD__) +#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_NETBSD) ret = sysctlbyname("kern.cp_time", &cpu_time, &size, NULL, 0); -#elif __OpenBSD__ +#elif PSUTIL_OPENBSD int mib[] = {CTL_KERN, KERN_CPTIME}; ret = sysctl(mib, 2, &cpu_time, &size, NULL, 0); #endif - if (ret == -1) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - + if (ret == -1) + return PyErr_SetFromErrno(PyExc_OSError); return Py_BuildValue("(ddddd)", (double)cpu_time[CP_USER] / CLOCKS_PER_SEC, (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC, @@ -447,16 +449,21 @@ psutil_cpu_times(PyObject *self, PyObject *args) { * utility has the same problem see: * https://github.com/giampaolo/psutil/issues/595 */ -#if (defined(__FreeBSD_version) && __FreeBSD_version >= 800000) || __OpenBSD__ || defined(__NetBSD__) +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 800000) || PSUTIL_OPENBSD || defined(PSUTIL_NETBSD) static PyObject * psutil_proc_open_files(PyObject *self, PyObject *args) { long pid; - int i, cnt; + int i; + int cnt; + int regular; + int fd; + char *path; struct kinfo_file *freep = NULL; struct kinfo_file *kif; kinfo_proc kipp; - PyObject *py_retlist = PyList_New(0); PyObject *py_tuple = NULL; + PyObject *py_path = NULL; + PyObject *py_retlist = PyList_New(0); if (py_retlist == NULL) return NULL; @@ -468,32 +475,39 @@ psutil_proc_open_files(PyObject *self, PyObject *args) { errno = 0; freep = kinfo_getfile(pid, &cnt); if (freep == NULL) { - psutil_raise_for_pid(pid, "kinfo_getfile() failed"); + psutil_raise_for_pid(pid, "kinfo_getfile()"); goto error; } for (i = 0; i < cnt; i++) { kif = &freep[i]; -#ifdef __FreeBSD__ - if ((kif->kf_type == KF_TYPE_VNODE) && - (kif->kf_vnode_type == KF_VTYPE_VREG)) - { - py_tuple = Py_BuildValue("(si)", kif->kf_path, kif->kf_fd); -#elif defined(__OpenBSD__) - if ((kif->f_type == DTYPE_VNODE) && - (kif->v_type == VREG)) - { - py_tuple = Py_BuildValue("(si)", "", kif->fd_fd); -#elif defined(__NetBSD__) - if ((kif->ki_ftype == DTYPE_VNODE) && - (kif->ki_vtype == VREG)) - { - py_tuple = Py_BuildValue("(si)", "", kif->ki_fd); + +#ifdef PSUTIL_FREEBSD + regular = (kif->kf_type == KF_TYPE_VNODE) && \ + (kif->kf_vnode_type == KF_VTYPE_VREG); + fd = kif->kf_fd; + path = kif->kf_path; +#elif PSUTIL_OPENBSD + regular = (kif->f_type == DTYPE_VNODE) && (kif->v_type == VREG); + fd = kif->fd_fd; + // XXX - it appears path is not exposed in the kinfo_file struct. + path = ""; +#elif PSUTIL_NETBSD + regular = (kif->ki_ftype == DTYPE_VNODE) && (kif->ki_vtype == VREG); + fd = kif->ki_fd; + // XXX - it appears path is not exposed in the kinfo_file struct. + path = ""; #endif + if (regular == 1) { + py_path = PyUnicode_DecodeFSDefault(path); + if (! py_path) + goto error; + py_tuple = Py_BuildValue("(Oi)", py_path, fd); if (py_tuple == NULL) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_path); Py_DECREF(py_tuple); } } @@ -521,12 +535,14 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { long len; uint64_t flags; char opts[200]; -#if defined(__NetBSD__) +#ifdef PSUTIL_NETBSD struct statvfs *fs = NULL; #else struct statfs *fs = NULL; #endif PyObject *py_retlist = PyList_New(0); + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; PyObject *py_tuple = NULL; if (py_retlist == NULL) @@ -534,7 +550,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { // get the number of mount points Py_BEGIN_ALLOW_THREADS -#if defined(__NetBSD__) +#ifdef PSUTIL_NETBSD num = getvfsstat(NULL, 0, MNT_NOWAIT); #else num = getfsstat(NULL, 0, MNT_NOWAIT); @@ -553,7 +569,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { } Py_BEGIN_ALLOW_THREADS -#if defined(__NetBSD__) +#ifdef PSUTIL_NETBSD num = getvfsstat(fs, len, MNT_NOWAIT); #else num = getfsstat(fs, len, MNT_NOWAIT); @@ -567,7 +583,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { for (i = 0; i < num; i++) { py_tuple = NULL; opts[0] = 0; -#if defined(__NetBSD__) +#ifdef PSUTIL_NETBSD flags = fs[i].f_flag; #else flags = fs[i].f_flags; @@ -590,7 +606,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { strlcat(opts, ",noatime", sizeof(opts)); if (flags & MNT_SOFTDEP) strlcat(opts, ",softdep", sizeof(opts)); -#ifdef __FreeBSD__ +#ifdef PSUTIL_FREEBSD if (flags & MNT_UNION) strlcat(opts, ",union", sizeof(opts)); if (flags & MNT_SUIDDIR) @@ -611,24 +627,24 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { strlcat(opts, ",noclusterw", sizeof(opts)); if (flags & MNT_NFS4ACLS) strlcat(opts, ",nfs4acls", sizeof(opts)); -#elif __NetBSD__ +#elif PSUTIL_NETBSD if (flags & MNT_NODEV) strlcat(opts, ",nodev", sizeof(opts)); if (flags & MNT_UNION) strlcat(opts, ",union", sizeof(opts)); if (flags & MNT_NOCOREDUMP) strlcat(opts, ",nocoredump", sizeof(opts)); -#if defined(MNT_RELATIME) +#ifdef MNT_RELATIME if (flags & MNT_RELATIME) strlcat(opts, ",relatime", sizeof(opts)); #endif if (flags & MNT_IGNORE) strlcat(opts, ",ignore", sizeof(opts)); -#if defined(MNT_DISCARD) +#ifdef MNT_DISCARD if (flags & MNT_DISCARD) strlcat(opts, ",discard", sizeof(opts)); #endif -#if defined(MNT_EXTATTR) +#ifdef MNT_EXTATTR if (flags & MNT_EXTATTR) strlcat(opts, ",extattr", sizeof(opts)); #endif @@ -639,15 +655,23 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { if (flags & MNT_NODEVMTIME) strlcat(opts, ",nodevmtime", sizeof(opts)); #endif - py_tuple = Py_BuildValue("(ssss)", - fs[i].f_mntfromname, // device - fs[i].f_mntonname, // mount point + py_dev = PyUnicode_DecodeFSDefault(fs[i].f_mntfromname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(fs[i].f_mntonname); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue("(OOss)", + py_dev, // device + py_mountp, // mount point fs[i].f_fstypename, // fs type opts); // options if (!py_tuple) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_dev); + Py_DECREF(py_mountp); Py_DECREF(py_tuple); } @@ -655,6 +679,8 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { return py_retlist; error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); if (fs != NULL) @@ -764,12 +790,15 @@ psutil_net_io_counters(PyObject *self, PyObject *args) { static PyObject * psutil_users(PyObject *self, PyObject *args) { PyObject *py_retlist = PyList_New(0); + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; PyObject *py_tuple = NULL; if (py_retlist == NULL) return NULL; -#if (defined(__FreeBSD_version) && (__FreeBSD_version < 900000)) || __OpenBSD__ +#if (defined(__FreeBSD_version) && (__FreeBSD_version < 900000)) || PSUTIL_OPENBSD struct utmp ut; FILE *fp; @@ -782,12 +811,27 @@ psutil_users(PyObject *self, PyObject *args) { while (fread(&ut, sizeof(ut), 1, fp) == 1) { if (*ut.ut_name == '\0') continue; + py_username = PyUnicode_DecodeFSDefault(ut.ut_name); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut.ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(ut.ut_host); + if (! py_hostname) + goto error; py_tuple = Py_BuildValue( - "(sssf)", - ut.ut_name, // username - ut.ut_line, // tty - ut.ut_host, // hostname - (float)ut.ut_time); // start time + "(OOOfi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)ut.ut_time, // start time +#ifdef PSUTIL_OPENBSD + -1 // process id (set to None later) +#else + ut.ut_pid // process id +#endif + ); if (!py_tuple) { fclose(fp); goto error; @@ -796,23 +840,39 @@ psutil_users(PyObject *self, PyObject *args) { fclose(fp); goto error; } + Py_DECREF(py_username); + Py_DECREF(py_tty); + Py_DECREF(py_hostname); Py_DECREF(py_tuple); } fclose(fp); #else struct utmpx *utx; - setutxent(); while ((utx = getutxent()) != NULL) { if (utx->ut_type != USER_PROCESS) continue; + py_username = PyUnicode_DecodeFSDefault(utx->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(utx->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(utx->ut_host); + if (! py_hostname) + goto error; py_tuple = Py_BuildValue( - "(sssf)", - utx->ut_user, // username - utx->ut_line, // tty - utx->ut_host, // hostname - (float)utx->ut_tv.tv_sec // start time + "(OOOfi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)utx->ut_tv.tv_sec, // start time +#ifdef PSUTIL_OPENBSD + -1 // process id (set to None later) +#else + utx->ut_pid // process id +#endif ); if (!py_tuple) { @@ -823,6 +883,9 @@ psutil_users(PyObject *self, PyObject *args) { endutxent(); goto error; } + Py_DECREF(py_username); + Py_DECREF(py_tty); + Py_DECREF(py_hostname); Py_DECREF(py_tuple); } @@ -831,6 +894,9 @@ psutil_users(PyObject *self, PyObject *args) { return py_retlist; error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); return NULL; @@ -842,40 +908,35 @@ psutil_users(PyObject *self, PyObject *args) { */ static PyMethodDef PsutilMethods[] = { - // --- per-process functions {"proc_oneshot_info", psutil_proc_oneshot_info, METH_VARARGS, "Return multiple info about a process"}, {"proc_name", psutil_proc_name, METH_VARARGS, "Return process name"}, -#if !defined(__NetBSD__) - {"proc_connections", psutil_proc_connections, METH_VARARGS, - "Return connections opened by process"}, -#endif {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS, "Return process cmdline as a list of cmdline arguments"}, {"proc_threads", psutil_proc_threads, METH_VARARGS, "Return process threads"}, -#if defined(__FreeBSD__) || defined(__OpenBSD__) +#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_OPENBSD) + {"proc_connections", psutil_proc_connections, METH_VARARGS, + "Return connections opened by process"}, {"proc_cwd", psutil_proc_cwd, METH_VARARGS, "Return process current working directory."}, #endif -#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 || __OpenBSD__ || defined(__NetBSD__) +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 || PSUTIL_OPENBSD || defined(PSUTIL_NETBSD) {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS, "Return the number of file descriptors opened by this process"}, -#endif -#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 || __OpenBSD__ || defined(__NetBSD__) {"proc_open_files", psutil_proc_open_files, METH_VARARGS, "Return files opened by process as a list of (path, fd) tuples"}, #endif - -#if defined(__FreeBSD__) || defined(__NetBSD__) - {"proc_exe", psutil_proc_exe, METH_VARARGS, - "Return process pathname executable"}, +#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_NETBSD) {"proc_num_threads", psutil_proc_num_threads, METH_VARARGS, "Return number of threads used by process"}, -#if defined(__FreeBSD__) +#endif +#if defined(PSUTIL_FREEBSD) + {"proc_exe", psutil_proc_exe, METH_VARARGS, + "Return process pathname executable"}, {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS, "Return a list of tuples for every process's memory map"}, {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS, @@ -884,7 +945,6 @@ PsutilMethods[] = { "Set process CPU affinity."}, {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, "Return an XML string to determine the number physical CPUs."}, -#endif #endif // --- system-related functions @@ -914,10 +974,19 @@ PsutilMethods[] = { "Return currently connected users as a list of tuples"}, {"cpu_stats", psutil_cpu_stats, METH_VARARGS, "Return CPU statistics"}, -#if defined(__FreeBSD__) || defined(__NetBSD__) +#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_NETBSD) {"net_connections", psutil_net_connections, METH_VARARGS, "Return system-wide open connections."}, #endif +#if defined(PSUTIL_FREEBSD) + {"sensors_battery", psutil_sensors_battery, METH_VARARGS, + "Return battery information."}, +#endif + + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + {NULL, NULL, 0, NULL} }; @@ -976,7 +1045,7 @@ void init_psutil_bsd(void) PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); // process status constants -#ifdef __FreeBSD__ +#ifdef PSUTIL_FREEBSD PyModule_AddIntConstant(module, "SIDL", SIDL); PyModule_AddIntConstant(module, "SRUN", SRUN); PyModule_AddIntConstant(module, "SSLEEP", SSLEEP); @@ -984,7 +1053,7 @@ void init_psutil_bsd(void) PyModule_AddIntConstant(module, "SZOMB", SZOMB); PyModule_AddIntConstant(module, "SWAIT", SWAIT); PyModule_AddIntConstant(module, "SLOCK", SLOCK); -#elif __OpenBSD__ +#elif PSUTIL_OPENBSD PyModule_AddIntConstant(module, "SIDL", SIDL); PyModule_AddIntConstant(module, "SRUN", SRUN); PyModule_AddIntConstant(module, "SSLEEP", SSLEEP); @@ -992,7 +1061,7 @@ void init_psutil_bsd(void) PyModule_AddIntConstant(module, "SZOMB", SZOMB); // unused PyModule_AddIntConstant(module, "SDEAD", SDEAD); PyModule_AddIntConstant(module, "SONPROC", SONPROC); -#elif defined(__NetBSD__) +#elif defined(PSUTIL_NETBSD) PyModule_AddIntConstant(module, "SIDL", LSIDL); PyModule_AddIntConstant(module, "SRUN", LSRUN); PyModule_AddIntConstant(module, "SSLEEP", LSSLEEP); @@ -1019,6 +1088,8 @@ void init_psutil_bsd(void) // PSUTIL_CONN_NONE PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", 128); + psutil_setup(); + if (module == NULL) INITERROR; #if PY_MAJOR_VERSION >= 3 diff --git a/psutil/_psutil_common.c b/psutil/_psutil_common.c index 5d025739f..908dbf14a 100644 --- a/psutil/_psutil_common.c +++ b/psutil/_psutil_common.c @@ -6,22 +6,44 @@ * Routines common to all platforms. */ -#ifdef PSUTIL_POSIX -#include -#include -#endif - #include +#include + + +// Global vars. +int PSUTIL_DEBUG = 0; +int PSUTIL_TESTING = 0; + + +/* + * Backport of unicode FS APIs from Python 3. + * On Python 2 we just return a plain byte string + * which is never supposed to raise decoding errors. + * See: https://github.com/giampaolo/psutil/issues/1040 + */ +#if PY_MAJOR_VERSION < 3 +PyObject * +PyUnicode_DecodeFSDefault(char *s) { + return PyString_FromString(s); +} + + +PyObject * +PyUnicode_DecodeFSDefaultAndSize(char *s, Py_ssize_t size) { + return PyString_FromStringAndSize(s, size); +} +#endif /* * Set OSError(errno=ESRCH, strerror="No such process") Python exception. + * If msg != "" the exception message will change in accordance. */ PyObject * -NoSuchProcess(void) { +NoSuchProcess(char *msg) { PyObject *exc; - char *msg = strerror(ESRCH); - exc = PyObject_CallFunction(PyExc_OSError, "(is)", ESRCH, msg); + exc = PyObject_CallFunction( + PyExc_OSError, "(is)", ESRCH, strlen(msg) ? msg : strerror(ESRCH)); PyErr_SetObject(PyExc_OSError, exc); Py_XDECREF(exc); return NULL; @@ -30,97 +52,53 @@ NoSuchProcess(void) { /* * Set OSError(errno=EACCES, strerror="Permission denied") Python exception. + * If msg != "" the exception message will change in accordance. */ PyObject * -AccessDenied(void) { +AccessDenied(char *msg) { PyObject *exc; - char *msg = strerror(EACCES); - exc = PyObject_CallFunction(PyExc_OSError, "(is)", EACCES, msg); + exc = PyObject_CallFunction( + PyExc_OSError, "(is)", EACCES, strlen(msg) ? msg : strerror(EACCES)); PyErr_SetObject(PyExc_OSError, exc); Py_XDECREF(exc); return NULL; } -#ifdef PSUTIL_POSIX /* - * Check if PID exists. Return values: - * 1: exists - * 0: does not exist - * -1: error (Python exception is set) + * Enable testing mode. This has the same effect as setting PSUTIL_TESTING + * env var. This dual method exists because updating os.environ on + * Windows has no effect. Called on unit tests setup. */ -int -psutil_pid_exists(long pid) { - int ret; - - // No negative PID exists, plus -1 is an alias for sending signal - // too all processes except system ones. Not what we want. - if (pid < 0) - return 0; - - // As per "man 2 kill" PID 0 is an alias for sending the signal to - // every process in the process group of the calling process. - // Not what we want. Some platforms have PID 0, some do not. - // We decide that at runtime. - if (pid == 0) { -#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD) - return 0; -#else - return 1; -#endif - } +PyObject * +psutil_set_testing(PyObject *self, PyObject *args) { + PSUTIL_TESTING = 1; + Py_INCREF(Py_None); + return Py_None; +} -#if defined(PSUTIL_OSX) - ret = kill((pid_t)pid , 0); -#else - ret = kill(pid , 0); -#endif - if (ret == 0) - return 1; - else { - if (errno == ESRCH) { - // ESRCH == No such process - return 0; - } - else if (errno == EPERM) { - // EPERM clearly indicates there's a process to deny - // access to. - return 1; - } - else { - // According to "man 2 kill" possible error values are - // (EINVAL, EPERM, ESRCH) therefore we should never get - // here. If we do let's be explicit in considering this - // an error. - PyErr_SetFromErrno(PyExc_OSError); - return -1; - } - } +/* + * Print a debug message on stderr. No-op if PSUTIL_DEBUG env var is not set. + */ +void +psutil_debug(const char* format, ...) { + va_list argptr; + va_start(argptr, format); + fprintf(stderr, "psutil-dubug> "); + vfprintf(stderr, format, argptr); + fprintf(stderr, "\n"); + va_end(argptr); } /* - * Utility used for those syscalls which do not return a meaningful - * error that we can translate into an exception which makes sense. - * As such, we'll have to guess. - * On UNIX, if errno is set, we return that one (OSError). - * Else, if PID does not exist we assume the syscall failed because - * of that so we raise NoSuchProcess. - * If none of this is true we giveup and raise RuntimeError(msg). - * This will always set a Python exception and return NULL. + * Called on module import on all platforms. */ -int -psutil_raise_for_pid(long pid, char *msg) { - // Set exception to AccessDenied if pid exists else NoSuchProcess. - if (errno != 0) { - PyErr_SetFromErrno(PyExc_OSError); - return 0; - } - if (psutil_pid_exists(pid) == 0) - NoSuchProcess(); - else - PyErr_SetString(PyExc_RuntimeError, msg); - return 0; +void +psutil_setup(void) { + if (getenv("PSUTIL_DEBUG") != NULL) + PSUTIL_DEBUG = 1; + if (getenv("PSUTIL_TESTING") != NULL) + PSUTIL_TESTING = 1; } -#endif diff --git a/psutil/_psutil_common.h b/psutil/_psutil_common.h index 982c59c7c..3db3f5ede 100644 --- a/psutil/_psutil_common.h +++ b/psutil/_psutil_common.h @@ -6,10 +6,20 @@ #include -PyObject* AccessDenied(void); -PyObject* NoSuchProcess(void); +extern int PSUTIL_TESTING; +extern int PSUTIL_DEBUG; -#ifdef PSUTIL_POSIX -int psutil_pid_exists(long pid); -void psutil_raise_for_pid(long pid, char *msg); +// a signaler for connections without an actual status +static const int PSUTIL_CONN_NONE = 128; + +#if PY_MAJOR_VERSION < 3 +PyObject* PyUnicode_DecodeFSDefault(char *s); +PyObject* PyUnicode_DecodeFSDefaultAndSize(char *s, Py_ssize_t size); #endif + +PyObject* AccessDenied(char *msg); +PyObject* NoSuchProcess(char *msg); + +PyObject* psutil_set_testing(PyObject *self, PyObject *args); +void psutil_debug(const char* format, ...); +void psutil_setup(void); diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c index 4923ead6a..d1f0d1455 100644 --- a/psutil/_psutil_linux.c +++ b/psutil/_psutil_linux.c @@ -32,6 +32,8 @@ typedef __u16 u16; typedef __u8 u8; #endif +/* Avoid redefinition of struct sysinfo with musl libc */ +#define _LINUX_SYSINFO_H #include /* The minimum number of CPUs allocated in a cpu_set_t */ @@ -52,6 +54,8 @@ static const int NCPUS_START = sizeof(unsigned long) * CHAR_BIT; #include #endif +#include "_psutil_common.h" +#include "_psutil_posix.h" // May happen on old RedHat versions, see: // https://github.com/giampaolo/psutil/issues/607 @@ -191,8 +195,10 @@ static PyObject * psutil_disk_partitions(PyObject *self, PyObject *args) { FILE *file = NULL; struct mntent *entry; - PyObject *py_retlist = PyList_New(0); + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); if (py_retlist == NULL) return NULL; @@ -211,15 +217,23 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { PyErr_Format(PyExc_RuntimeError, "getmntent() syscall failed"); goto error; } - py_tuple = Py_BuildValue("(ssss)", - entry->mnt_fsname, // device - entry->mnt_dir, // mount point + py_dev = PyUnicode_DecodeFSDefault(entry->mnt_fsname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(entry->mnt_dir); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue("(OOss)", + py_dev, // device + py_mountp, // mount point entry->mnt_type, // fs type entry->mnt_opts); // options if (! py_tuple) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_dev); + Py_DECREF(py_mountp); Py_DECREF(py_tuple); } endmntent(file); @@ -228,6 +242,8 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { error: if (file != NULL) endmntent(file); + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); return NULL; @@ -435,6 +451,9 @@ psutil_users(PyObject *self, PyObject *args) { struct utmp *ut; PyObject *py_retlist = PyList_New(0); PyObject *py_tuple = NULL; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; PyObject *py_user_proc = NULL; if (py_retlist == NULL) @@ -447,26 +466,41 @@ psutil_users(PyObject *self, PyObject *args) { py_user_proc = Py_True; else py_user_proc = Py_False; + py_username = PyUnicode_DecodeFSDefault(ut->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); + if (! py_hostname) + goto error; py_tuple = Py_BuildValue( - "(sssfO)", - ut->ut_user, // username - ut->ut_line, // tty - ut->ut_host, // hostname + "(OOOfOi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname (float)ut->ut_tv.tv_sec, // tstamp - py_user_proc // (bool) user process + py_user_proc, // (bool) user process + ut->ut_pid // process id ); if (! py_tuple) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_username); + Py_DECREF(py_tty); + Py_DECREF(py_hostname); Py_DECREF(py_tuple); } endutent(); return py_retlist; error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); Py_XDECREF(py_tuple); - Py_XDECREF(py_user_proc); Py_DECREF(py_retlist); endutent(); return NULL; @@ -531,8 +565,7 @@ psutil_net_if_duplex_speed(PyObject* self, PyObject* args) { error: if (sock != -1) close(sock); - PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return PyErr_SetFromErrno(PyExc_OSError); } @@ -541,7 +574,6 @@ psutil_net_if_duplex_speed(PyObject* self, PyObject* args) { */ static PyMethodDef PsutilMethods[] = { - // --- per-process functions #if PSUTIL_HAVE_IOPRIO @@ -574,6 +606,9 @@ PsutilMethods[] = { "Get or set process resource limits."}, #endif + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, {NULL, NULL, 0, NULL} }; @@ -678,6 +713,8 @@ void init_psutil_linux(void) PyModule_AddIntConstant(module, "DUPLEX_FULL", DUPLEX_FULL); PyModule_AddIntConstant(module, "DUPLEX_UNKNOWN", DUPLEX_UNKNOWN); + psutil_setup(); + if (module == NULL) INITERROR; #if PY_MAJOR_VERSION >= 3 diff --git a/psutil/_psutil_osx.c b/psutil/_psutil_osx.c index a1168c291..55dd64ca5 100644 --- a/psutil/_psutil_osx.c +++ b/psutil/_psutil_osx.c @@ -38,8 +38,11 @@ #include #include #include +#include +#include #include "_psutil_common.h" +#include "_psutil_posix.h" #include "arch/osx/process_info.h" @@ -124,6 +127,8 @@ psutil_pids(PyObject *self, PyObject *args) { * using sysctl() and filling up a kinfo_proc struct. * It should be possible to do this for all processes without * incurring into permission (EPERM) errors. + * This will also succeed for zombie processes returning correct + * information. */ static PyObject * psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args) { @@ -137,11 +142,7 @@ psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args) { if (psutil_get_kinfo_proc(pid, &kp) == -1) return NULL; -#if PY_MAJOR_VERSION >= 3 py_name = PyUnicode_DecodeFSDefault(kp.kp_proc.p_comm); -#else - py_name = Py_BuildValue("s", kp.kp_proc.p_comm); -#endif if (! py_name) { // Likely a decoding error. We don't want to fail the whole // operation. The python module may retry with proc_name(). @@ -176,8 +177,9 @@ psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args) { * Return multiple process info as a Python tuple in one shot by * using proc_pidinfo(PROC_PIDTASKINFO) and filling a proc_taskinfo * struct. - * Contrarily from proc_kinfo above this function will return EACCES - * for PIDs owned by another user. + * Contrarily from proc_kinfo above this function will fail with + * EACCES for PIDs owned by another user and with ESRCH for zombie + * processes. */ static PyObject * psutil_proc_pidtaskinfo_oneshot(PyObject *self, PyObject *args) { @@ -223,16 +225,13 @@ psutil_proc_name(PyObject *self, PyObject *args) { return NULL; if (psutil_get_kinfo_proc(pid, &kp) == -1) return NULL; -#if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeFSDefault(kp.kp_proc.p_comm); -#else - return Py_BuildValue("s", kp.kp_proc.p_comm); -#endif } /* * Return process current working directory. + * Raises NSP in case of zombie process. */ static PyObject * psutil_proc_cwd(PyObject *self, PyObject *args) { @@ -248,11 +247,7 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { return NULL; } -#if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeFSDefault(pathinfo.pvi_cdir.vip_path); -#else - return Py_BuildValue("s", pathinfo.pvi_cdir.vip_path); -#endif } @@ -271,16 +266,12 @@ psutil_proc_exe(PyObject *self, PyObject *args) { ret = proc_pidpath((pid_t)pid, &buf, sizeof(buf)); if (ret == 0) { if (pid == 0) - AccessDenied(); + AccessDenied(""); else - psutil_raise_for_pid(pid, "proc_pidpath() syscall failed"); + psutil_raise_for_pid(pid, "proc_pidpath()"); return NULL; } -#if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeFSDefault(buf); -#else - return Py_BuildValue("s", buf); -#endif } @@ -336,6 +327,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { vm_size_t size = 0; PyObject *py_tuple = NULL; + PyObject *py_path = NULL; PyObject *py_list = PyList_New(0); if (py_list == NULL) @@ -346,10 +338,16 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { err = task_for_pid(mach_task_self(), (pid_t)pid, &task); if (err != KERN_SUCCESS) { - if (psutil_pid_exists(pid) == 0) - NoSuchProcess(); - else - AccessDenied(); + if ((err == 5) && (errno == ENOENT)) { + // See: https://github.com/giampaolo/psutil/issues/1181 + psutil_debug("task_for_pid(MACH_PORT_NULL) failed; err=%i, " + "errno=%i, msg='%s'\n", err, errno, + mach_error_string(err)); + AccessDenied(""); + } + else { + psutil_raise_for_pid(pid, "task_for_pid(MACH_PORT_NULL)"); + } goto error; } @@ -360,8 +358,15 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { err = vm_region_recurse_64(task, &address, &size, &depth, (vm_region_info_64_t)&info, &count); - if (err == KERN_INVALID_ADDRESS) + if (err == KERN_INVALID_ADDRESS) { + // TODO temporary + psutil_debug("vm_region_recurse_64 returned KERN_INVALID_ADDRESS"); break; + } + if (err != KERN_SUCCESS) { + psutil_debug("vm_region_recurse_64 returned != KERN_SUCCESS"); + } + if (info.is_submap) { depth++; } @@ -389,8 +394,9 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { errno = 0; proc_regionfilename((pid_t)pid, address, buf, sizeof(buf)); if ((errno != 0) || ((sizeof(buf)) <= 0)) { - psutil_raise_for_pid( - pid, "proc_regionfilename() syscall failed"); + // TODO temporary + psutil_debug("proc_regionfilename() failed"); + psutil_raise_for_pid(pid, "proc_regionfilename()"); goto error; } @@ -430,11 +436,14 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { } } + py_path = PyUnicode_DecodeFSDefault(buf); + if (! py_path) + goto error; py_tuple = Py_BuildValue( - "sssIIIIIH", + "ssOIIIIIH", addr_str, // "start-end"address perms, // "rwx" permissions - buf, // path + py_path, // path info.pages_resident * pagesize, // rss info.pages_shared_now_private * pagesize, // private info.pages_swapped_out * pagesize, // swapped @@ -447,6 +456,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { if (PyList_Append(py_list, py_tuple)) goto error; Py_DECREF(py_tuple); + Py_DECREF(py_path); } // increment address for the next map/file @@ -462,6 +472,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { if (task != MACH_PORT_NULL) mach_port_deallocate(mach_task_self(), task); Py_XDECREF(py_tuple); + Py_XDECREF(py_path); Py_DECREF(py_list); return NULL; } @@ -568,17 +579,15 @@ psutil_proc_memory_uss(PyObject *self, PyObject *args) { err = task_for_pid(mach_task_self(), (pid_t)pid, &task); if (err != KERN_SUCCESS) { if (psutil_pid_exists(pid) == 0) - NoSuchProcess(); + NoSuchProcess(""); else - AccessDenied(); + AccessDenied(""); return NULL; } len = sizeof(cpu_type); - if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } + if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) + return PyErr_SetFromErrno(PyExc_OSError); // Roughly based on libtop_update_vm_regions in // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c @@ -808,6 +817,31 @@ psutil_per_cpu_times(PyObject *self, PyObject *args) { } +/* + * Retrieve CPU frequency. + */ +static PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + int64_t curr; + int64_t min; + int64_t max; + size_t size = sizeof(int64_t); + + if (sysctlbyname("hw.cpufrequency", &curr, &size, NULL, 0)) + return PyErr_SetFromErrno(PyExc_OSError); + if (sysctlbyname("hw.cpufrequency_min", &min, &size, NULL, 0)) + return PyErr_SetFromErrno(PyExc_OSError); + if (sysctlbyname("hw.cpufrequency_max", &max, &size, NULL, 0)) + return PyErr_SetFromErrno(PyExc_OSError); + + return Py_BuildValue( + "KKK", + curr / 1000 / 1000, + min / 1000 / 1000, + max / 1000 / 1000); +} + + /* * Return a Python float indicating the system boot time expressed in * seconds since the epoch. @@ -820,10 +854,8 @@ psutil_boot_time(PyObject *self, PyObject *args) { size_t result_len = sizeof result; time_t boot_time = 0; - if (sysctl(request, 2, &result, &result_len, NULL, 0) == -1) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } + if (sysctl(request, 2, &result, &result_len, NULL, 0) == -1) + return PyErr_SetFromErrno(PyExc_OSError); boot_time = result.tv_sec; return Py_BuildValue("f", (float)boot_time); } @@ -841,8 +873,10 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { uint64_t flags; char opts[400]; struct statfs *fs = NULL; - PyObject *py_retlist = PyList_New(0); + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); if (py_retlist == NULL) return NULL; @@ -927,15 +961,24 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { if (flags & MNT_CMDFLAGS) strlcat(opts, ",cmdflags", sizeof(opts)); + py_dev = PyUnicode_DecodeFSDefault(fs[i].f_mntfromname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(fs[i].f_mntonname); + if (! py_mountp) + goto error; py_tuple = Py_BuildValue( - "(ssss)", fs[i].f_mntfromname, // device - fs[i].f_mntonname, // mount point + "(OOss)", + py_dev, // device + py_mountp, // mount point fs[i].f_fstypename, // fs type opts); // options if (!py_tuple) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_dev); + Py_DECREF(py_mountp); Py_DECREF(py_tuple); } @@ -943,6 +986,8 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { return py_retlist; error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); if (fs != NULL) @@ -977,13 +1022,13 @@ psutil_proc_threads(PyObject *self, PyObject *args) { if (! PyArg_ParseTuple(args, "l", &pid)) goto error; - // task_for_pid() requires special privileges + // task_for_pid() requires root privileges err = task_for_pid(mach_task_self(), (pid_t)pid, &task); if (err != KERN_SUCCESS) { if (psutil_pid_exists(pid) == 0) - NoSuchProcess(); + NoSuchProcess(""); else - AccessDenied(); + AccessDenied(""); goto error; } @@ -993,7 +1038,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { if (err != KERN_SUCCESS) { // errcode 4 is "invalid argument" (access denied) if (err == 4) { - AccessDenied(); + AccessDenied(""); } else { // otherwise throw a runtime error with appropriate error code @@ -1103,7 +1148,6 @@ psutil_proc_open_files(PyObject *self, PyObject *args) { iterations = (pidinfo_result / PROC_PIDLISTFD_SIZE); for (i = 0; i < iterations; i++) { - py_tuple = NULL; fdp_pointer = &fds_pointer[i]; if (fdp_pointer->proc_fdtype == PROX_FDTYPE_VNODE) { @@ -1122,18 +1166,15 @@ psutil_proc_open_files(PyObject *self, PyObject *args) { continue; } else { - psutil_raise_for_pid(pid, "proc_pidinfo() syscall failed"); + psutil_raise_for_pid( + pid, "proc_pidinfo(PROC_PIDFDVNODEPATHINFO)"); goto error; } } // --- /errors checking // --- construct python list -#if PY_MAJOR_VERSION >= 3 py_path = PyUnicode_DecodeFSDefault(vi.pvip.vip_path); -#else - py_path = Py_BuildValue("s", vi.pvip.vip_path); -#endif if (! py_path) goto error; py_tuple = Py_BuildValue( @@ -1145,7 +1186,9 @@ psutil_proc_open_files(PyObject *self, PyObject *args) { if (PyList_Append(py_retlist, py_tuple)) goto error; Py_DECREF(py_tuple); + py_tuple = NULL; Py_DECREF(py_path); + py_path = NULL; // --- /construct python list } } @@ -1163,11 +1206,9 @@ psutil_proc_open_files(PyObject *self, PyObject *args) { } -// a signaler for connections without an actual status -static int PSUTIL_CONN_NONE = 128; - /* * Return process TCP and UDP connections as a list of tuples. + * Raises NSP in case of zombie process. * References: * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/wNrC0 * - /usr/include/sys/proc_info.h @@ -1238,7 +1279,8 @@ psutil_proc_connections(PyObject *self, PyObject *args) { continue; } else { - psutil_raise_for_pid(pid, "proc_pidinfo() syscall failed"); + psutil_raise_for_pid( + pid, "proc_pidinfo(PROC_PIDFDSOCKETINFO)"); goto error; } } @@ -1329,18 +1371,28 @@ psutil_proc_connections(PyObject *self, PyObject *args) { Py_DECREF(py_tuple); } else if (family == AF_UNIX) { + py_laddr = PyUnicode_DecodeFSDefault( + si.psi.soi_proto.pri_un.unsi_addr.ua_sun.sun_path); + if (!py_laddr) + goto error; + py_raddr = PyUnicode_DecodeFSDefault( + si.psi.soi_proto.pri_un.unsi_caddr.ua_sun.sun_path); + if (!py_raddr) + goto error; // construct the python list py_tuple = Py_BuildValue( - "(iiissi)", + "(iiiOOi)", fd, family, type, - si.psi.soi_proto.pri_un.unsi_addr.ua_sun.sun_path, - si.psi.soi_proto.pri_un.unsi_caddr.ua_sun.sun_path, + py_laddr, + py_raddr, PSUTIL_CONN_NONE); if (!py_tuple) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; Py_DECREF(py_tuple); + Py_DECREF(py_laddr); + Py_DECREF(py_raddr); } } } @@ -1361,6 +1413,7 @@ psutil_proc_connections(PyObject *self, PyObject *args) { /* * Return number of file descriptors opened by process. + * Raises NSP in case of zombie process. */ static PyObject * psutil_proc_num_fds(PyObject *self, PyObject *args) { @@ -1659,20 +1712,33 @@ psutil_disk_io_counters(PyObject *self, PyObject *args) { static PyObject * psutil_users(PyObject *self, PyObject *args) { struct utmpx *utx; - PyObject *py_retlist = PyList_New(0); + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); if (py_retlist == NULL) return NULL; while ((utx = getutxent()) != NULL) { if (utx->ut_type != USER_PROCESS) continue; + py_username = PyUnicode_DecodeFSDefault(utx->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(utx->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(utx->ut_host); + if (! py_hostname) + goto error; py_tuple = Py_BuildValue( - "(sssf)", - utx->ut_user, // username - utx->ut_line, // tty - utx->ut_host, // hostname - (float)utx->ut_tv.tv_sec // start time + "(OOOfi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)utx->ut_tv.tv_sec, // start time + utx->ut_pid // process id ); if (!py_tuple) { endutxent(); @@ -1682,6 +1748,9 @@ psutil_users(PyObject *self, PyObject *args) { endutxent(); goto error; } + Py_DECREF(py_username); + Py_DECREF(py_tty); + Py_DECREF(py_hostname); Py_DECREF(py_tuple); } @@ -1689,6 +1758,9 @@ psutil_users(PyObject *self, PyObject *args) { return py_retlist; error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); return NULL; @@ -1726,13 +1798,98 @@ psutil_cpu_stats(PyObject *self, PyObject *args) { } +/* + * Return battery information. + */ +static PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + PyObject *py_tuple = NULL; + CFTypeRef power_info = NULL; + CFArrayRef power_sources_list = NULL; + CFDictionaryRef power_sources_information = NULL; + CFNumberRef capacity_ref = NULL; + CFNumberRef time_to_empty_ref = NULL; + CFStringRef ps_state_ref = NULL; + uint32_t capacity; /* units are percent */ + int time_to_empty; /* units are minutes */ + int is_power_plugged; + + power_info = IOPSCopyPowerSourcesInfo(); + + if (!power_info) { + PyErr_SetString(PyExc_RuntimeError, + "IOPSCopyPowerSourcesInfo() syscall failed"); + goto error; + } + + power_sources_list = IOPSCopyPowerSourcesList(power_info); + if (!power_sources_list) { + PyErr_SetString(PyExc_RuntimeError, + "IOPSCopyPowerSourcesList() syscall failed"); + goto error; + } + + /* Should only get one source. But in practice, check for > 0 sources */ + if (!CFArrayGetCount(power_sources_list)) { + PyErr_SetString(PyExc_NotImplementedError, "no battery"); + goto error; + } + + power_sources_information = IOPSGetPowerSourceDescription( + power_info, CFArrayGetValueAtIndex(power_sources_list, 0)); + + capacity_ref = (CFNumberRef) CFDictionaryGetValue( + power_sources_information, CFSTR(kIOPSCurrentCapacityKey)); + if (!CFNumberGetValue(capacity_ref, kCFNumberSInt32Type, &capacity)) { + PyErr_SetString(PyExc_RuntimeError, + "No battery capacity infomration in power sources info"); + goto error; + } + + ps_state_ref = (CFStringRef) CFDictionaryGetValue( + power_sources_information, CFSTR(kIOPSPowerSourceStateKey)); + is_power_plugged = CFStringCompare( + ps_state_ref, CFSTR(kIOPSACPowerValue), 0) + == kCFCompareEqualTo; + + time_to_empty_ref = (CFNumberRef) CFDictionaryGetValue( + power_sources_information, CFSTR(kIOPSTimeToEmptyKey)); + if (!CFNumberGetValue(time_to_empty_ref, + kCFNumberIntType, &time_to_empty)) { + /* This value is recommended for non-Apple power sources, so it's not + * an error if it doesn't exist. We'll return -1 for "unknown" */ + /* A value of -1 indicates "Still Calculating the Time" also for + * apple power source */ + time_to_empty = -1; + } + + py_tuple = Py_BuildValue("Iii", + capacity, time_to_empty, is_power_plugged); + if (!py_tuple) { + goto error; + } + + CFRelease(power_info); + CFRelease(power_sources_list); + /* Caller should NOT release power_sources_information */ + + return py_tuple; + +error: + if (power_info) + CFRelease(power_info); + if (power_sources_list) + CFRelease(power_sources_list); + Py_XDECREF(py_tuple); + return NULL; +} + /* * define the psutil C module methods and initialize the module. */ static PyMethodDef PsutilMethods[] = { - // --- per-process functions {"proc_kinfo_oneshot", psutil_proc_kinfo_oneshot, METH_VARARGS, @@ -1778,6 +1935,8 @@ PsutilMethods[] = { "Return system cpu times as a tuple (user, system, nice, idle, irc)"}, {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, "Return system per-cpu times as a list of tuples"}, + {"cpu_freq", psutil_cpu_freq, METH_VARARGS, + "Return cpu current frequency"}, {"boot_time", psutil_boot_time, METH_VARARGS, "Return the system boot time expressed in seconds since the epoch."}, {"disk_partitions", psutil_disk_partitions, METH_VARARGS, @@ -1791,6 +1950,12 @@ PsutilMethods[] = { "Return currently connected users as a list of tuples"}, {"cpu_stats", psutil_cpu_stats, METH_VARARGS, "Return CPU statistics"}, + {"sensors_battery", psutil_sensors_battery, METH_VARARGS, + "Return battery information."}, + + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, {NULL, NULL, 0, NULL} }; @@ -1871,6 +2036,8 @@ init_psutil_osx(void) PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT); PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + psutil_setup(); + if (module == NULL) INITERROR; #if PY_MAJOR_VERSION >= 3 diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c index 2d9630ace..cc827273c 100644 --- a/psutil/_psutil_posix.c +++ b/psutil/_psutil_posix.c @@ -11,32 +11,125 @@ #include #include #include +#include #include #include #include #ifdef PSUTIL_SUNOS10 -#include "arch/solaris/v10/ifaddrs.h" + #include "arch/solaris/v10/ifaddrs.h" +#elif PSUTIL_AIX + #include "arch/aix/ifaddrs.h" #else -#include + #include #endif -#ifdef __linux -#include -#include -#endif // end linux +#if defined(PSUTIL_LINUX) + #include + #include + #include +#elif defined(PSUTIL_BSD) || defined(PSUTIL_OSX) + #include + #include + #include + #include + #include + #include +#elif defined(PSUTIL_SUNOS) + #include + #include +#elif defined(PSUTIL_AIX) + #include +#endif + +#include "_psutil_common.h" + +/* + * Check if PID exists. Return values: + * 1: exists + * 0: does not exist + * -1: error (Python exception is set) + */ +int +psutil_pid_exists(long pid) { + int ret; -#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__NetBSD__) -#include -#include -#include + // No negative PID exists, plus -1 is an alias for sending signal + // too all processes except system ones. Not what we want. + if (pid < 0) + return 0; + + // As per "man 2 kill" PID 0 is an alias for sending the signal to + // every process in the process group of the calling process. + // Not what we want. Some platforms have PID 0, some do not. + // We decide that at runtime. + if (pid == 0) { +#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD) + return 0; +#else + return 1; #endif + } -#if defined(__sun) -#include -#include +#if defined(PSUTIL_OSX) + ret = kill((pid_t)pid , 0); +#else + ret = kill(pid , 0); #endif + if (ret == 0) + return 1; + else { + if (errno == ESRCH) { + // ESRCH == No such process + return 0; + } + else if (errno == EPERM) { + // EPERM clearly indicates there's a process to deny + // access to. + return 1; + } + else { + // According to "man 2 kill" possible error values are + // (EINVAL, EPERM, ESRCH) therefore we should never get + // here. If we do let's be explicit in considering this + // an error. + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + } +} + + +/* + * Utility used for those syscalls which do not return a meaningful + * error that we can translate into an exception which makes sense. + * As such, we'll have to guess. + * On UNIX, if errno is set, we return that one (OSError). + * Else, if PID does not exist we assume the syscall failed because + * of that so we raise NoSuchProcess. + * If none of this is true we giveup and raise RuntimeError(msg). + * This will always set a Python exception and return NULL. + */ +int +psutil_raise_for_pid(long pid, char *syscall_name) { + // Set exception to AccessDenied if pid exists else NoSuchProcess. + if (errno != 0) { + // Unlikely we get here. + PyErr_SetFromErrno(PyExc_OSError); + return 0; + } + else if (psutil_pid_exists(pid) == 0) { + psutil_debug("%s syscall failed and PID %i no longer exists; " + "assume NoSuchProcess", syscall_name, pid); + NoSuchProcess(""); + } + else { + PyErr_Format(PyExc_RuntimeError, "%s syscall failed", syscall_name); + } + return 0; +} + /* * Given a PID return process priority as a Python integer. @@ -50,7 +143,7 @@ psutil_posix_getpriority(PyObject *self, PyObject *args) { if (! PyArg_ParseTuple(args, "l", &pid)) return NULL; -#if defined(__APPLE__) +#ifdef PSUTIL_OSX priority = getpriority(PRIO_PROCESS, (id_t)pid); #else priority = getpriority(PRIO_PROCESS, pid); @@ -73,7 +166,7 @@ psutil_posix_setpriority(PyObject *self, PyObject *args) { if (! PyArg_ParseTuple(args, "li", &pid, &priority)) return NULL; -#if defined(__APPLE__) +#ifdef PSUTIL_OSX retval = setpriority(PRIO_PROCESS, (id_t)pid, priority); #else retval = setpriority(PRIO_PROCESS, pid, priority); @@ -122,14 +215,13 @@ psutil_convert_ipaddr(struct sockaddr *addr, int family) { return Py_BuildValue("s", buf); } } -#ifdef __linux +#ifdef PSUTIL_LINUX else if (family == AF_PACKET) { struct sockaddr_ll *lladdr = (struct sockaddr_ll *)addr; len = lladdr->sll_halen; data = (const char *)lladdr->sll_addr; } -#endif -#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__NetBSD__) +#elif defined(PSUTIL_BSD) || defined(PSUTIL_OSX) else if (addr->sa_family == AF_LINK) { // Note: prior to Python 3.4 socket module does not expose // AF_LINK so we'll do. @@ -264,8 +356,11 @@ psutil_net_if_mtu(PyObject *self, PyObject *args) { char *nic_name; int sock = 0; int ret; - int mtu; +#ifdef PSUTIL_SUNOS10 + struct lifreq lifr; +#else struct ifreq ifr; +#endif if (! PyArg_ParseTuple(args, "s", &nic_name)) return NULL; @@ -274,20 +369,27 @@ psutil_net_if_mtu(PyObject *self, PyObject *args) { if (sock == -1) goto error; +#ifdef PSUTIL_SUNOS10 + strncpy(lifr.lifr_name, nic_name, sizeof(lifr.lifr_name)); + ret = ioctl(sock, SIOCGIFMTU, &lifr); +#else strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); ret = ioctl(sock, SIOCGIFMTU, &ifr); +#endif if (ret == -1) goto error; close(sock); - mtu = ifr.ifr_mtu; - return Py_BuildValue("i", mtu); +#ifdef PSUTIL_SUNOS10 + return Py_BuildValue("i", lifr.lifr_mtu); +#else + return Py_BuildValue("i", ifr.ifr_mtu); +#endif error: if (sock != 0) close(sock); - PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return PyErr_SetFromErrno(PyExc_OSError); } @@ -324,19 +426,14 @@ psutil_net_if_flags(PyObject *self, PyObject *args) { error: if (sock != 0) close(sock); - PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return PyErr_SetFromErrno(PyExc_OSError); } /* * net_if_stats() OSX/BSD implementation. */ -#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__NetBSD__) - -#include -#include -#include +#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) int psutil_get_nic_speed(int ifm_active) { // Determine NIC speed. Taken from: @@ -369,7 +466,7 @@ int psutil_get_nic_speed(int ifm_active) { case(IFM_1000_SX): // 1000BaseSX - multi-mode fiber case(IFM_1000_LX): // 1000baseLX - single-mode fiber case(IFM_1000_CX): // 1000baseCX - 150ohm STP -#if defined(IFM_1000_TX) && !defined(__OpenBSD__) +#if defined(IFM_1000_TX) && !defined(PSUTIL_OPENBSD) // FreeBSD 4 and others (but NOT OpenBSD) -> #define IFM_1000_T in net/if_media.h case(IFM_1000_TX): #endif @@ -521,8 +618,7 @@ psutil_net_if_duplex_speed(PyObject *self, PyObject *args) { error: if (sock != 0) close(sock); - PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return PyErr_SetFromErrno(PyExc_OSError); } #endif // net_if_stats() OSX/BSD implementation @@ -542,7 +638,7 @@ PsutilMethods[] = { "Retrieve NIC MTU"}, {"net_if_flags", psutil_net_if_flags, METH_VARARGS, "Retrieve NIC flags"}, -#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__NetBSD__) +#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS, "Return NIC stats."}, #endif @@ -567,6 +663,7 @@ psutil_posix_traverse(PyObject *m, visitproc visit, void *arg) { return 0; } + static int psutil_posix_clear(PyObject *m) { Py_CLEAR(GETSTATE(m)->error); @@ -601,7 +698,7 @@ void init_psutil_posix(void) PyObject *module = Py_InitModule("_psutil_posix", PsutilMethods); #endif -#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__sun) || defined(__NetBSD__) +#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) || defined(PSUTIL_SUNOS) || defined(PSUTIL_AIX) PyModule_AddIntConstant(module, "AF_LINK", AF_LINK); #endif diff --git a/psutil/_psutil_posix.h b/psutil/_psutil_posix.h index 86708f4b8..fe25b3669 100644 --- a/psutil/_psutil_posix.h +++ b/psutil/_psutil_posix.h @@ -3,3 +3,6 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ + +int psutil_pid_exists(long pid); +void psutil_raise_for_pid(long pid, char *msg); diff --git a/psutil/_psutil_sunos.c b/psutil/_psutil_sunos.c index e98ff7f28..c6673642c 100644 --- a/psutil/_psutil_sunos.c +++ b/psutil/_psutil_sunos.c @@ -9,18 +9,23 @@ * this in Cython which I later on translated in C. */ +/* fix compilation issue on SunOS 5.10, see: + * https://github.com/giampaolo/psutil/issues/421 + * https://github.com/giampaolo/psutil/issues/1077 + * http://us-east.manta.joyent.com/jmc/public/opensolaris/ARChive/PSARC/2010/111/materials/s10ceval.txt + * + * Because LEGACY_MIB_SIZE defined in the same file there is no way to make autoconfiguration =\ +*/ -#include - -// fix for "Cannot use procfs in the large file compilation environment" -// error, see: -// http://sourceware.org/ml/gdb-patches/2010-11/msg00336.html -#undef _FILE_OFFSET_BITS +#define NEW_MIB_COMPLIANT 1 #define _STRUCTURED_PROC 1 -// fix compilation issue on SunOS 5.10, see: -// https://github.com/giampaolo/psutil/issues/421 -#define NEW_MIB_COMPLIANT +#include + +#if !defined(_LP64) && _FILE_OFFSET_BITS == 64 +# undef _FILE_OFFSET_BITS +# undef _LARGEFILE64_SOURCE +#endif #include #include @@ -42,13 +47,12 @@ #include #include +#include "_psutil_common.h" +#include "_psutil_posix.h" + +#include "arch/solaris/environ.h" #define PSUTIL_TV2DOUBLE(t) (((t).tv_nsec * 0.000000001) + (t).tv_sec) -#ifndef EXPER_IP_AND_ALL_IRES -#define EXPER_IP_AND_ALL_IRES (1024+4) -#endif -// a signaler for connections without an actual status -static int PSUTIL_CONN_NONE = 128; /* @@ -97,16 +101,19 @@ psutil_proc_basic_info(PyObject *self, PyObject *args) { sprintf(path, "%s/%i/psinfo", procfs_path, pid); if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) return NULL; - return Py_BuildValue("ikkdiiik", - info.pr_ppid, // parent pid - info.pr_rssize, // rss - info.pr_size, // vms - PSUTIL_TV2DOUBLE(info.pr_start), // create time - info.pr_lwp.pr_nice, // nice - info.pr_nlwp, // no. of threads - info.pr_lwp.pr_state, // status code - info.pr_ttydev // tty nr - ); + return Py_BuildValue( + "ikkdiiik", + info.pr_ppid, // parent pid + info.pr_rssize, // rss + info.pr_size, // vms + PSUTIL_TV2DOUBLE(info.pr_start), // create time + // XXX - niceness is wrong (20 instead of 0), see: + // https://github.com/giampaolo/psutil/issues/1082 + info.pr_lwp.pr_nice, // nice + info.pr_nlwp, // no. of threads + info.pr_lwp.pr_state, // status code + info.pr_ttydev // tty nr + ); } @@ -119,8 +126,9 @@ psutil_proc_name_and_args(PyObject *self, PyObject *args) { char path[1000]; psinfo_t info; const char *procfs_path; - PyObject *py_name; - PyObject *py_args; + PyObject *py_name = NULL; + PyObject *py_args = NULL; + PyObject *py_retlist = NULL; if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) return NULL; @@ -128,17 +136,99 @@ psutil_proc_name_and_args(PyObject *self, PyObject *args) { if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) return NULL; -#if PY_MAJOR_VERSION >= 3 py_name = PyUnicode_DecodeFSDefault(info.pr_fname); if (!py_name) - return NULL; + goto error; py_args = PyUnicode_DecodeFSDefault(info.pr_psargs); if (!py_args) + goto error; + py_retlist = Py_BuildValue("OO", py_name, py_args); + if (!py_retlist) + goto error; + Py_DECREF(py_name); + Py_DECREF(py_args); + return py_retlist; + +error: + Py_XDECREF(py_name); + Py_XDECREF(py_args); + Py_XDECREF(py_retlist); + return NULL; +} + + +/* + * Return process environ block. + */ +static PyObject * +psutil_proc_environ(PyObject *self, PyObject *args) { + int pid; + char path[1000]; + psinfo_t info; + const char *procfs_path; + char **env = NULL; + ssize_t env_count = -1; + char *dm; + int i = 0; + PyObject *py_envname = NULL; + PyObject *py_envval = NULL; + PyObject *py_retdict = PyDict_New(); + + if (! py_retdict) + return PyErr_NoMemory(); + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) return NULL; - return Py_BuildValue("OO", py_name, py_args); -#else - return Py_BuildValue("ss", info.pr_fname, info.pr_psargs); -#endif + + sprintf(path, "%s/%i/psinfo", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + goto error; + + if (! info.pr_envp) { + AccessDenied(""); + goto error; + } + + env = psutil_read_raw_env(info, procfs_path, &env_count); + if (! env && env_count != 0) + goto error; + + for (i=0; i= 0) + psutil_free_cstrings_array(env, env_count); + + Py_XDECREF(py_envname); + Py_XDECREF(py_envval); + Py_XDECREF(py_retdict); + return NULL; } @@ -168,6 +258,86 @@ psutil_proc_cpu_times(PyObject *self, PyObject *args) { } +/* + * Return what CPU the process is running on. + */ +static PyObject * +psutil_proc_cpu_num(PyObject *self, PyObject *args) { + int fd = NULL; + int pid; + char path[1000]; + struct prheader header; + struct lwpsinfo *lwp; + char *lpsinfo = NULL; + char *ptr = NULL; + int nent; + int size; + int proc_num; + size_t nbytes; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + + sprintf(path, "%s/%i/lpsinfo", procfs_path, pid); + fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return NULL; + } + + // read header + nbytes = pread(fd, &header, sizeof(header), 0); + if (nbytes == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (nbytes != sizeof(header)) { + PyErr_SetString( + PyExc_RuntimeError, "read() file structure size mismatch"); + goto error; + } + + // malloc + nent = header.pr_nent; + size = header.pr_entsize * nent; + ptr = lpsinfo = malloc(size); + if (lpsinfo == NULL) { + PyErr_NoMemory(); + goto error; + } + + // read the rest + nbytes = pread(fd, lpsinfo, size, sizeof(header)); + if (nbytes == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (nbytes != size) { + PyErr_SetString( + PyExc_RuntimeError, "read() file structure size mismatch"); + goto error; + } + + // done + lwp = (lwpsinfo_t *)ptr; + proc_num = lwp->pr_onpro; + close(fd); + free(ptr); + free(lpsinfo); + return Py_BuildValue("i", proc_num); + +error: + if (fd != -1) + close(fd); + if (ptr != NULL) + free(ptr); + if (lpsinfo != NULL) + free(lpsinfo); + return NULL; +} + + /* * Return process uids/gids as a Python tuple. */ @@ -190,7 +360,7 @@ psutil_proc_cred(PyObject *self, PyObject *args) { /* - * Return process uids/gids as a Python tuple. + * Return process voluntary and involuntary context switches as a Python tuple. */ static PyObject * psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) { @@ -216,7 +386,7 @@ psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) { * - 'pr_ioch' is a sum of chars read and written, with no distinction * - 'pr_inblk' and 'pr_oublk', which should be the number of bytes * read and written, hardly increase and according to: - * http://www.brendangregg.com/Perf/paper_diskubyp1.pdf + * http://www.brendangregg.com/Solaris/paper_diskubyp1.pdf * ...they should be meaningless anyway. * static PyObject* @@ -236,7 +406,7 @@ proc_io_counters(PyObject* self, PyObject* args) { // *and* written. // 'pr_inblk' and 'pr_oublk' should be expressed in blocks of // 8KB according to: - // http://www.brendangregg.com/Perf/paper_diskubyp1.pdf (pag. 8) + // http://www.brendangregg.com/Solaris/paper_diskubyp1.pdf (pag. 8) return Py_BuildValue("kkkk", info.pr_ioch, info.pr_ioch, @@ -368,40 +538,60 @@ psutil_swap_mem(PyObject *self, PyObject *args) { static PyObject * psutil_users(PyObject *self, PyObject *args) { struct utmpx *ut; - PyObject *py_retlist = PyList_New(0); PyObject *py_tuple = NULL; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; PyObject *py_user_proc = NULL; + PyObject *py_retlist = PyList_New(0); if (py_retlist == NULL) return NULL; + setutxent(); while (NULL != (ut = getutxent())) { if (ut->ut_type == USER_PROCESS) py_user_proc = Py_True; else py_user_proc = Py_False; + py_username = PyUnicode_DecodeFSDefault(ut->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); + if (! py_hostname) + goto error; py_tuple = Py_BuildValue( - "(sssfO)", - ut->ut_user, // username - ut->ut_line, // tty - ut->ut_host, // hostname + "(OOOfOi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname (float)ut->ut_tv.tv_sec, // tstamp - py_user_proc); // (bool) user process + py_user_proc, // (bool) user process + ut->ut_pid // process id + ); if (py_tuple == NULL) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_username); + Py_DECREF(py_tty); + Py_DECREF(py_hostname); Py_DECREF(py_tuple); } - endutent(); + endutxent(); return py_retlist; error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); - if (ut != NULL) - endutent(); + endutxent(); return NULL; } @@ -414,8 +604,10 @@ static PyObject * psutil_disk_partitions(PyObject *self, PyObject *args) { FILE *file; struct mnttab mt; - PyObject *py_retlist = PyList_New(0); + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); if (py_retlist == NULL) return NULL; @@ -427,23 +619,32 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { } while (getmntent(file, &mt) == 0) { + py_dev = PyUnicode_DecodeFSDefault(mt.mnt_special); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(mt.mnt_mountp); + if (! py_mountp) + goto error; py_tuple = Py_BuildValue( - "(ssss)", - mt.mnt_special, // device - mt.mnt_mountp, // mount point + "(OOss)", + py_dev, // device + py_mountp, // mount point mt.mnt_fstype, // fs type mt.mnt_mntopts); // options if (py_tuple == NULL) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_dev); + Py_DECREF(py_mountp); Py_DECREF(py_tuple); - } fclose(file); return py_retlist; error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); if (file != NULL) @@ -584,6 +785,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { const char *procfs_path; PyObject *py_tuple = NULL; + PyObject *py_path = NULL; PyObject *py_retlist = PyList_New(0); if (py_retlist == NULL) @@ -630,12 +832,10 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { pr_addr_sz = p->pr_vaddr + p->pr_size; // perms - sprintf(perms, "%c%c%c%c%c%c", p->pr_mflags & MA_READ ? 'r' : '-', + sprintf(perms, "%c%c%c%c", p->pr_mflags & MA_READ ? 'r' : '-', p->pr_mflags & MA_WRITE ? 'w' : '-', p->pr_mflags & MA_EXEC ? 'x' : '-', - p->pr_mflags & MA_SHARED ? 's' : '-', - p->pr_mflags & MA_NORESERVE ? 'R' : '-', - p->pr_mflags & MA_RESERVED1 ? '*' : ' '); + p->pr_mflags & MA_SHARED ? 's' : '-'); // name if (strlen(p->pr_mapname) > 0) { @@ -664,19 +864,23 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { } } + py_path = PyUnicode_DecodeFSDefault(name); + if (! py_path) + goto error; py_tuple = Py_BuildValue( - "iisslll", - p->pr_vaddr, - pr_addr_sz, + "kksOkkk", + (unsigned long)p->pr_vaddr, + (unsigned long)pr_addr_sz, perms, - name, - (long)p->pr_rss * p->pr_pagesize, - (long)p->pr_anon * p->pr_pagesize, - (long)p->pr_locked * p->pr_pagesize); + py_path, + (unsigned long)p->pr_rss * p->pr_pagesize, + (unsigned long)p->pr_anon * p->pr_pagesize, + (unsigned long)p->pr_locked * p->pr_pagesize); if (!py_tuple) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_path); Py_DECREF(py_tuple); // increment pointer @@ -691,6 +895,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { if (fd != -1) close(fd); Py_XDECREF(py_tuple); + Py_XDECREF(py_path); Py_DECREF(py_retlist); if (xmap != NULL) free(xmap); @@ -837,7 +1042,7 @@ psutil_net_connections(PyObject *self, PyObject *args) { #endif char buf[512]; int i, flags, getcode, num_ent, state; - char lip[200], rip[200]; + char lip[INET6_ADDRSTRLEN], rip[INET6_ADDRSTRLEN]; int lport, rport; int processed_pid; int databuf_init = 0; @@ -863,9 +1068,7 @@ psutil_net_connections(PyObject *self, PyObject *args) { goto error; } - /* - XXX - These 2 are used in ifconfig.c but they seem unnecessary - ret = ioctl(sd, I_PUSH, "tcp"); + int ret = ioctl(sd, I_PUSH, "tcp"); if (ret == -1) { PyErr_SetFromErrno(PyExc_OSError); goto error; @@ -875,8 +1078,7 @@ psutil_net_connections(PyObject *self, PyObject *args) { PyErr_SetFromErrno(PyExc_OSError); goto error; } - */ - + // // OK, this mess is basically copied and pasted from nxsensor project // which copied and pasted it from netstat source code, mibget() // function. Also see: @@ -886,9 +1088,14 @@ psutil_net_connections(PyObject *self, PyObject *args) { tor->OPT_length = sizeof (struct opthdr); tor->MGMT_flags = T_CURRENT; mibhdr = (struct opthdr *)&tor[1]; - mibhdr->level = EXPER_IP_AND_ALL_IRES; + mibhdr->level = MIB2_IP; mibhdr->name = 0; + +#ifdef NEW_MIB_COMPLIANT + mibhdr->len = 1; +#else mibhdr->len = 0; +#endif ctlbuf.buf = buf; ctlbuf.len = tor->OPT_offset + tor->OPT_length; @@ -901,7 +1108,6 @@ psutil_net_connections(PyObject *self, PyObject *args) { mibhdr = (struct opthdr *)&toa[1]; ctlbuf.maxlen = sizeof (buf); - for (;;) { flags = 0; getcode = getmsg(sd, &ctlbuf, (struct strbuf *)0, &flags); @@ -949,7 +1155,11 @@ psutil_net_connections(PyObject *self, PyObject *args) { tp = (mib2_tcpConnEntry_t *)databuf.buf; num_ent = mibhdr->len / sizeof(mib2_tcpConnEntry_t); for (i = 0; i < num_ent; i++, tp++) { +#ifdef NEW_MIB_COMPLIANT processed_pid = tp->tcpConnCreationProcess; +#else + processed_pid = 0; +#endif if (pid != -1 && processed_pid != pid) continue; // construct local/remote addresses @@ -990,7 +1200,11 @@ psutil_net_connections(PyObject *self, PyObject *args) { num_ent = mibhdr->len / sizeof(mib2_tcp6ConnEntry_t); for (i = 0; i < num_ent; i++, tp6++) { +#ifdef NEW_MIB_COMPLIANT processed_pid = tp6->tcp6ConnCreationProcess; +#else + processed_pid = 0; +#endif if (pid != -1 && processed_pid != pid) continue; // construct local/remote addresses @@ -1026,8 +1240,13 @@ psutil_net_connections(PyObject *self, PyObject *args) { else if (mibhdr->level == MIB2_UDP || mibhdr->level == MIB2_UDP_ENTRY) { ude = (mib2_udpEntry_t *)databuf.buf; num_ent = mibhdr->len / sizeof(mib2_udpEntry_t); + assert(num_ent * sizeof(mib2_udpEntry_t) == mibhdr->len); for (i = 0; i < num_ent; i++, ude++) { +#ifdef NEW_MIB_COMPLIANT processed_pid = ude->udpCreationProcess; +#else + processed_pid = 0; +#endif if (pid != -1 && processed_pid != pid) continue; // XXX Very ugly hack! It seems we get here only the first @@ -1063,7 +1282,11 @@ psutil_net_connections(PyObject *self, PyObject *args) { ude6 = (mib2_udp6Entry_t *)databuf.buf; num_ent = mibhdr->len / sizeof(mib2_udp6Entry_t); for (i = 0; i < num_ent; i++, ude6++) { +#ifdef NEW_MIB_COMPLIANT processed_pid = ude6->udp6CreationProcess; +#else + processed_pid = 0; +#endif if (pid != -1 && processed_pid != pid) continue; inet_ntop(AF_INET6, &ude6->udp6LocalAddress, lip, sizeof(lip)); @@ -1109,20 +1332,20 @@ psutil_boot_time(PyObject *self, PyObject *args) { float boot_time = 0.0; struct utmpx *ut; + setutxent(); while (NULL != (ut = getutxent())) { if (ut->ut_type == BOOT_TIME) { boot_time = (float)ut->ut_tv.tv_sec; break; } } - endutent(); - if (boot_time != 0.0) { - return Py_BuildValue("f", boot_time); - } - else { + endutxent(); + if (boot_time == 0.0) { + /* could not find BOOT_TIME in getutxent loop */ PyErr_SetString(PyExc_RuntimeError, "can't determine boot time"); return NULL; } + return Py_BuildValue("f", boot_time); } @@ -1324,12 +1547,13 @@ psutil_cpu_stats(PyObject *self, PyObject *args) { */ static PyMethodDef PsutilMethods[] = { - // --- process-related functions {"proc_basic_info", psutil_proc_basic_info, METH_VARARGS, "Return process ppid, rss, vms, ctime, nice, nthreads, status and tty"}, {"proc_name_and_args", psutil_proc_name_and_args, METH_VARARGS, "Return process name and args."}, + {"proc_environ", psutil_proc_environ, METH_VARARGS, + "Return process environment."}, {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, "Return process user and system CPU times."}, {"proc_cred", psutil_proc_cred, METH_VARARGS, @@ -1340,6 +1564,8 @@ PsutilMethods[] = { "Return process memory mappings"}, {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS, "Return the number of context switches performed by process"}, + {"proc_cpu_num", psutil_proc_cpu_num, METH_VARARGS, + "Return what CPU the process is on"}, // --- system-related functions {"swap_mem", psutil_swap_mem, METH_VARARGS, @@ -1365,6 +1591,10 @@ PsutilMethods[] = { {"cpu_stats", psutil_cpu_stats, METH_VARARGS, "Return CPU statistics"}, + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + {NULL, NULL, 0, NULL} }; @@ -1449,6 +1679,8 @@ void init_psutil_sunos(void) PyModule_AddIntConstant(module, "TCPS_BOUND", TCPS_BOUND); PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + psutil_setup(); + if (module == NULL) INITERROR; #if PY_MAJOR_VERSION >= 3 diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index 00bd22342..81d1b4a06 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include // Link with Iphlpapi.lib #pragma comment(lib, "IPHLPAPI.lib") @@ -47,9 +49,6 @@ * ============================================================================ */ - // a flag for connections without an actual status -static int PSUTIL_CONN_NONE = 128; - #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) #define LO_T ((float)1e-7) @@ -67,8 +66,11 @@ static int PSUTIL_CONN_NONE = 128; typedef BOOL (WINAPI *LPFN_GLPI) (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD); -// fix for mingw32, see +// Fix for mingw32, see: // https://github.com/giampaolo/psutil/issues/351#c2 +// This is actually a DISK_PERFORMANCE struct: +// https://msdn.microsoft.com/en-us/library/windows/desktop/ +// aa363991(v=vs.85).aspx typedef struct _DISK_PERFORMANCE_WIN_2008 { LARGE_INTEGER BytesRead; LARGE_INTEGER BytesWritten; @@ -145,6 +147,16 @@ typedef struct _MIB_UDP6TABLE_OWNER_PID { } MIB_UDP6TABLE_OWNER_PID, *PMIB_UDP6TABLE_OWNER_PID; #endif +typedef struct _PROCESSOR_POWER_INFORMATION { + ULONG Number; + ULONG MaxMhz; + ULONG CurrentMhz; + ULONG MhzLimit; + ULONG MaxIdleState; + ULONG CurrentIdleState; +} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION; + + PIP_ADAPTER_ADDRESSES psutil_get_nic_addresses() { // allocate a 15 KB buffer to start with @@ -224,8 +236,12 @@ psutil_boot_time(PyObject *self, PyObject *args) { and 01-01-1601, from time_t the divide by 1e+7 to get to the same base granularity. */ - ll = (((LONGLONG)(fileTime.dwHighDateTime)) << 32) \ - + fileTime.dwLowDateTime; +#if (_WIN32_WINNT >= 0x0600) // Windows Vista + ll = (((ULONGLONG) +#else + ll = (((LONGLONG) +#endif + (fileTime.dwHighDateTime)) << 32) + fileTime.dwLowDateTime; pt = (time_t)((ll - 116444736000000000ull) / 10000000ull); // GetTickCount64() is Windows Vista+ only. Dinamically load @@ -238,15 +254,15 @@ psutil_boot_time(PyObject *self, PyObject *args) { if (psutil_GetTickCount64 != NULL) { // Windows >= Vista uptime = psutil_GetTickCount64() / (ULONGLONG)1000.00f; + return Py_BuildValue("K", pt - uptime); } else { // Windows XP. // GetTickCount() time will wrap around to zero if the // system is run continuously for 49.7 days. - uptime = GetTickCount() / 1000.00f; + uptime = GetTickCount() / (LONGLONG)1000.00f; + return Py_BuildValue("L", pt - uptime); } - - return Py_BuildValue("d", (double)pt - (double)uptime); } @@ -313,18 +329,21 @@ psutil_pids(PyObject *self, PyObject *args) { static PyObject * psutil_proc_kill(PyObject *self, PyObject *args) { HANDLE hProcess; + DWORD err; long pid; if (! PyArg_ParseTuple(args, "l", &pid)) return NULL; if (pid == 0) - return AccessDenied(); + return AccessDenied(""); hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); if (hProcess == NULL) { if (GetLastError() == ERROR_INVALID_PARAMETER) { // see https://github.com/giampaolo/psutil/issues/24 - NoSuchProcess(); + psutil_debug("OpenProcess -> ERROR_INVALID_PARAMETER turned " + "into NoSuchProcess"); + NoSuchProcess(""); } else { PyErr_SetFromWindowsErr(0); @@ -333,10 +352,14 @@ psutil_proc_kill(PyObject *self, PyObject *args) { } // kill the process - if (! TerminateProcess(hProcess, 0)) { - PyErr_SetFromWindowsErr(0); - CloseHandle(hProcess); - return NULL; + if (! TerminateProcess(hProcess, SIGTERM)) { + err = GetLastError(); + // See: https://github.com/giampaolo/psutil/issues/1099 + if (err != ERROR_ACCESS_DENIED) { + CloseHandle(hProcess); + PyErr_SetFromWindowsErr(err); + return NULL; + } } CloseHandle(hProcess); @@ -358,7 +381,7 @@ psutil_proc_wait(PyObject *self, PyObject *args) { if (! PyArg_ParseTuple(args, "ll", &pid, &timeout)) return NULL; if (pid == 0) - return AccessDenied(); + return AccessDenied(""); hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, pid); @@ -368,10 +391,8 @@ psutil_proc_wait(PyObject *self, PyObject *args) { // return None instead. Py_RETURN_NONE; } - else { - PyErr_SetFromWindowsErr(0); - return NULL; - } + else + return PyErr_SetFromWindowsErr(0); } // wait until the process has terminated @@ -381,20 +402,20 @@ psutil_proc_wait(PyObject *self, PyObject *args) { if (retVal == WAIT_FAILED) { CloseHandle(hProcess); - return PyErr_SetFromWindowsErr(GetLastError()); + return PyErr_SetFromWindowsErr(0); } if (retVal == WAIT_TIMEOUT) { CloseHandle(hProcess); return Py_BuildValue("l", WAIT_TIMEOUT); } - // get the exit code; note: subprocess module (erroneously?) uses - // what returned by WaitForSingleObject if (GetExitCodeProcess(hProcess, &ExitCode) == 0) { CloseHandle(hProcess); return PyErr_SetFromWindowsErr(GetLastError()); } + CloseHandle(hProcess); + #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong((long) ExitCode); #else @@ -423,11 +444,10 @@ psutil_proc_cpu_times(PyObject *self, PyObject *args) { if (GetLastError() == ERROR_ACCESS_DENIED) { // usually means the process has died so we throw a NoSuchProcess // here - return NoSuchProcess(); + return NoSuchProcess(""); } else { - PyErr_SetFromWindowsErr(0); - return NULL; + return PyErr_SetFromWindowsErr(0); } } @@ -460,9 +480,7 @@ static PyObject * psutil_proc_create_time(PyObject *self, PyObject *args) { long pid; long long unix_time; - DWORD exitCode; HANDLE hProcess; - BOOL ret; FILETIME ftCreate, ftExit, ftKernel, ftUser; if (! PyArg_ParseTuple(args, "l", &pid)) @@ -480,14 +498,16 @@ psutil_proc_create_time(PyObject *self, PyObject *args) { if (GetLastError() == ERROR_ACCESS_DENIED) { // usually means the process has died so we throw a // NoSuchProcess here - return NoSuchProcess(); + return NoSuchProcess(""); } else { - PyErr_SetFromWindowsErr(0); - return NULL; + return PyErr_SetFromWindowsErr(0); } } + CloseHandle(hProcess); + + /* // Make sure the process is not gone as OpenProcess alone seems to be // unreliable in doing so (it seems a previous call to p.wait() makes // it unreliable). @@ -497,22 +517,19 @@ psutil_proc_create_time(PyObject *self, PyObject *args) { CloseHandle(hProcess); if (ret != 0) { if (exitCode != STILL_ACTIVE) - return NoSuchProcess(); + return NoSuchProcess(""); } else { // Ignore access denied as it means the process is still alive. // For all other errors, we want an exception. - if (GetLastError() != ERROR_ACCESS_DENIED) { - PyErr_SetFromWindowsErr(0); - return NULL; - } + if (GetLastError() != ERROR_ACCESS_DENIED) + return PyErr_SetFromWindowsErr(0); } - - /* - Convert the FILETIME structure to a Unix time. - It's the best I could find by googling and borrowing code here and there. - The time returned has a precision of 1 second. */ + + // Convert the FILETIME structure to a Unix time. + // It's the best I could find by googling and borrowing code here + // and there. The time returned has a precision of 1 second. unix_time = ((LONGLONG)ftCreate.dwHighDateTime) << 32; unix_time += ftCreate.dwLowDateTime - 116444736000000000LL; unix_time /= 10000000; @@ -614,7 +631,7 @@ psutil_proc_cmdline(PyObject *self, PyObject *args) { pid_return = psutil_pid_is_running(pid); if (pid_return == 0) - return NoSuchProcess(); + return NoSuchProcess(""); if (pid_return == -1) return NULL; @@ -637,7 +654,7 @@ psutil_proc_environ(PyObject *self, PyObject *args) { pid_return = psutil_pid_is_running(pid); if (pid_return == 0) - return NoSuchProcess(); + return NoSuchProcess(""); if (pid_return == -1) return NULL; @@ -661,11 +678,10 @@ psutil_proc_exe(PyObject *self, PyObject *args) { return NULL; if (GetProcessImageFileNameW(hProcess, exe, MAX_PATH) == 0) { CloseHandle(hProcess); - PyErr_SetFromWindowsErr(0); - return NULL; + return PyErr_SetFromWindowsErr(0); } CloseHandle(hProcess); - return Py_BuildValue("u", exe); + return PyUnicode_FromWideChar(exe, wcslen(exe)); } @@ -685,16 +701,13 @@ psutil_proc_name(PyObject *self, PyObject *args) { if (! PyArg_ParseTuple(args, "l", &pid)) return NULL; hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, pid); - if (hSnapShot == INVALID_HANDLE_VALUE) { - PyErr_SetFromWindowsErr(0); - return NULL; - } + if (hSnapShot == INVALID_HANDLE_VALUE) + return PyErr_SetFromWindowsErr(0); pentry.dwSize = sizeof(PROCESSENTRY32W); ok = Process32FirstW(hSnapShot, &pentry); if (! ok) { CloseHandle(hSnapShot); - PyErr_SetFromWindowsErr(0); - return NULL; + return PyErr_SetFromWindowsErr(0); } while (ok) { if (pentry.th32ProcessID == pid) { @@ -706,7 +719,7 @@ psutil_proc_name(PyObject *self, PyObject *args) { } CloseHandle(hSnapShot); - NoSuchProcess(); + NoSuchProcess(""); return NULL; } @@ -1027,7 +1040,6 @@ psutil_per_cpu_times(PyObject *self, PyObject *args) { /* * Return process current working directory as a Python string. */ - static PyObject * psutil_proc_cwd(PyObject *self, PyObject *args) { long pid; @@ -1038,7 +1050,7 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { pid_return = psutil_pid_is_running(pid); if (pid_return == 0) - return NoSuchProcess(); + return NoSuchProcess(""); if (pid_return == -1) return NULL; @@ -1053,10 +1065,11 @@ int psutil_proc_suspend_or_resume(DWORD pid, int suspend) { // a huge thanks to http://www.codeproject.com/KB/threads/pausep.aspx HANDLE hThreadSnap = NULL; + HANDLE hThread; THREADENTRY32 te32 = {0}; if (pid == 0) { - AccessDenied(); + AccessDenied(""); return FALSE; } @@ -1078,20 +1091,17 @@ psutil_proc_suspend_or_resume(DWORD pid, int suspend) { // Walk the thread snapshot to find all threads of the process. // If the thread belongs to the process, add its information // to the display list. - do - { - if (te32.th32OwnerProcessID == pid) - { - HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, - te32.th32ThreadID); + do { + if (te32.th32OwnerProcessID == pid) { + hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, + te32.th32ThreadID); if (hThread == NULL) { PyErr_SetFromWindowsErr(0); CloseHandle(hThread); CloseHandle(hThreadSnap); return FALSE; } - if (suspend == 1) - { + if (suspend == 1) { if (SuspendThread(hThread) == (DWORD) - 1) { PyErr_SetFromWindowsErr(0); CloseHandle(hThread); @@ -1099,8 +1109,7 @@ psutil_proc_suspend_or_resume(DWORD pid, int suspend) { return FALSE; } } - else - { + else { if (ResumeThread(hThread) == (DWORD) - 1) { PyErr_SetFromWindowsErr(0); CloseHandle(hThread); @@ -1162,13 +1171,13 @@ psutil_proc_threads(PyObject *self, PyObject *args) { if (pid == 0) { // raise AD instead of returning 0 as procexp is able to // retrieve useful information somehow - AccessDenied(); + AccessDenied(""); goto error; } pid_return = psutil_pid_is_running(pid); if (pid_return == 0) { - NoSuchProcess(); + NoSuchProcess(""); goto error; } if (pid_return == -1) @@ -1304,17 +1313,18 @@ psutil_win32_QueryDosDevice(PyObject *self, PyObject *args) { static PyObject * psutil_proc_username(PyObject *self, PyObject *args) { long pid; - HANDLE processHandle; - HANDLE tokenHandle; - PTOKEN_USER user; + HANDLE processHandle = NULL; + HANDLE tokenHandle = NULL; + PTOKEN_USER user = NULL; ULONG bufferSize; - PTSTR name; + WCHAR *name = NULL; + WCHAR *domainName = NULL; ULONG nameSize; - PTSTR domainName; ULONG domainNameSize; SID_NAME_USE nameUse; - PTSTR fullName; - PyObject *py_unicode; + PyObject *py_username; + PyObject *py_domain; + PyObject *py_tuple; if (! PyArg_ParseTuple(args, "l", &pid)) return NULL; @@ -1325,97 +1335,102 @@ psutil_proc_username(PyObject *self, PyObject *args) { return NULL; if (!OpenProcessToken(processHandle, TOKEN_QUERY, &tokenHandle)) { - CloseHandle(processHandle); - return PyErr_SetFromWindowsErr(0); + PyErr_SetFromWindowsErr(0); + goto error; } CloseHandle(processHandle); + processHandle = NULL; // Get the user SID. - bufferSize = 0x100; - user = malloc(bufferSize); - if (user == NULL) - return PyErr_NoMemory(); - - if (!GetTokenInformation(tokenHandle, TokenUser, user, bufferSize, - &bufferSize)) - { - free(user); + while (1) { user = malloc(bufferSize); if (user == NULL) { - CloseHandle(tokenHandle); - return PyErr_NoMemory(); + PyErr_NoMemory(); + goto error; } if (!GetTokenInformation(tokenHandle, TokenUser, user, bufferSize, &bufferSize)) { - free(user); - CloseHandle(tokenHandle); - return PyErr_SetFromWindowsErr(0); + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(user); + continue; + } + else { + PyErr_SetFromWindowsErr(0); + goto error; + } } + break; } CloseHandle(tokenHandle); + tokenHandle = NULL; // resolve the SID to a name nameSize = 0x100; domainNameSize = 0x100; - - name = malloc(nameSize * sizeof(TCHAR)); - if (name == NULL) - return PyErr_NoMemory(); - domainName = malloc(domainNameSize * sizeof(TCHAR)); - if (domainName == NULL) - return PyErr_NoMemory(); - - if (!LookupAccountSid(NULL, user->User.Sid, name, &nameSize, domainName, - &domainNameSize, &nameUse)) - { - free(name); - free(domainName); - name = malloc(nameSize * sizeof(TCHAR)); - if (name == NULL) - return PyErr_NoMemory(); - domainName = malloc(domainNameSize * sizeof(TCHAR)); - if (domainName == NULL) - return PyErr_NoMemory(); - if (!LookupAccountSid(NULL, user->User.Sid, name, &nameSize, - domainName, &domainNameSize, &nameUse)) + while (1) { + name = malloc(nameSize * sizeof(WCHAR)); + if (name == NULL) { + PyErr_NoMemory(); + goto error; + } + domainName = malloc(domainNameSize * sizeof(WCHAR)); + if (domainName == NULL) { + PyErr_NoMemory(); + goto error; + } + if (!LookupAccountSidW(NULL, user->User.Sid, name, &nameSize, + domainName, &domainNameSize, &nameUse)) { - free(name); - free(domainName); - free(user); - - return PyErr_SetFromWindowsErr(0); + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(name); + free(domainName); + continue; + } + else { + PyErr_SetFromWindowsErr(0); + goto error; + } } + break; } - nameSize = _tcslen(name); - domainNameSize = _tcslen(domainName); - - // build the full username string - fullName = malloc((domainNameSize + 1 + nameSize + 1) * sizeof(TCHAR)); - if (fullName == NULL) { - free(name); - free(domainName); - free(user); - return PyErr_NoMemory(); - } - memcpy(fullName, domainName, domainNameSize); - fullName[domainNameSize] = '\\'; - memcpy(&fullName[domainNameSize + 1], name, nameSize); - fullName[domainNameSize + 1 + nameSize] = '\0'; - - py_unicode = PyUnicode_Decode( - fullName, _tcslen(fullName), Py_FileSystemDefaultEncoding, "replace"); + py_domain = PyUnicode_FromWideChar(domainName, wcslen(domainName)); + if (! py_domain) + goto error; + py_username = PyUnicode_FromWideChar(name, wcslen(name)); + if (! py_username) + goto error; + py_tuple = Py_BuildValue("OO", py_domain, py_username); + if (! py_tuple) + goto error; + Py_DECREF(py_domain); + Py_DECREF(py_username); - free(fullName); free(name); free(domainName); free(user); - return py_unicode; + return py_tuple; + +error: + if (processHandle != NULL) + CloseHandle(processHandle); + if (tokenHandle != NULL) + CloseHandle(tokenHandle); + if (name != NULL) + free(name); + if (domainName != NULL) + free(domainName); + if (user != NULL) + free(user); + Py_XDECREF(py_domain); + Py_XDECREF(py_username); + Py_XDECREF(py_tuple); + return NULL; } @@ -1508,6 +1523,7 @@ static PyObject * psutil_net_connections(PyObject *self, PyObject *args) { static long null_address[4] = { 0, 0, 0, 0 }; unsigned long pid; + int pid_return; typedef PSTR (NTAPI * _RtlIpv4AddressToStringA)(struct in_addr *, PSTR); _RtlIpv4AddressToStringA rtlIpv4AddressToStringA; typedef PSTR (NTAPI * _RtlIpv6AddressToStringA)(struct in6_addr *, PSTR); @@ -1549,9 +1565,14 @@ psutil_net_connections(PyObject *self, PyObject *args) { } if (pid != -1) { - if (psutil_pid_is_running(pid) == 0) { + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) { _psutil_conn_decref_objs(); - return NoSuchProcess(); + return NoSuchProcess(""); + } + else if (pid_return == -1) { + _psutil_conn_decref_objs(); + return NULL; } } @@ -1962,10 +1983,8 @@ psutil_proc_priority_get(PyObject *self, PyObject *args) { return NULL; priority = GetPriorityClass(hProcess); CloseHandle(hProcess); - if (priority == 0) { - PyErr_SetFromWindowsErr(0); - return NULL; - } + if (priority == 0) + return PyErr_SetFromWindowsErr(0); return Py_BuildValue("i", priority); } @@ -1988,10 +2007,8 @@ psutil_proc_priority_set(PyObject *self, PyObject *args) { return NULL; retval = SetPriorityClass(hProcess, priority); CloseHandle(hProcess); - if (retval == 0) { - PyErr_SetFromWindowsErr(0); - return NULL; - } + if (retval == 0) + return PyErr_SetFromWindowsErr(0); Py_RETURN_NONE; } @@ -2085,11 +2102,13 @@ psutil_proc_io_counters(PyObject *self, PyObject *args) { return PyErr_SetFromWindowsErr(0); } CloseHandle(hProcess); - return Py_BuildValue("(KKKK)", + return Py_BuildValue("(KKKKKK)", IoCounters.ReadOperationCount, IoCounters.WriteOperationCount, IoCounters.ReadTransferCount, - IoCounters.WriteTransferCount); + IoCounters.WriteTransferCount, + IoCounters.OtherOperationCount, + IoCounters.OtherTransferCount); } @@ -2283,8 +2302,8 @@ psutil_net_io_counters(PyObject *self, PyObject *args) { py_nic_info = Py_BuildValue("(KKKKKKKK)", pIfRow->OutOctets, pIfRow->InOctets, - pIfRow->OutUcastPkts, - pIfRow->InUcastPkts, + (pIfRow->OutUcastPkts + pIfRow->OutNUcastPkts), + (pIfRow->InUcastPkts + pIfRow->InNUcastPkts), pIfRow->InErrors, pIfRow->OutErrors, pIfRow->InDiscards, @@ -2293,8 +2312,8 @@ psutil_net_io_counters(PyObject *self, PyObject *args) { py_nic_info = Py_BuildValue("(kkkkkkkk)", pIfRow->dwOutOctets, pIfRow->dwInOctets, - pIfRow->dwOutUcastPkts, - pIfRow->dwInUcastPkts, + (pIfRow->dwOutUcastPkts + pIfRow->dwOutNUcastPkts), + (pIfRow->dwInUcastPkts + pIfRow->dwInNUcastPkts), pIfRow->dwInErrors, pIfRow->dwOutErrors, pIfRow->dwInDiscards, @@ -2345,6 +2364,9 @@ psutil_disk_io_counters(PyObject *self, PyObject *args) { char szDevice[MAX_PATH]; char szDeviceDisplay[MAX_PATH]; int devNum; + int i; + size_t ioctrlSize; + BOOL WINAPI ret; PyObject *py_retdict = PyDict_New(); PyObject *py_tuple = NULL; @@ -2359,38 +2381,74 @@ psutil_disk_io_counters(PyObject *self, PyObject *args) { sprintf_s(szDevice, MAX_PATH, "\\\\.\\PhysicalDrive%d", devNum); hDevice = CreateFile(szDevice, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); - if (hDevice == INVALID_HANDLE_VALUE) continue; - if (DeviceIoControl(hDevice, IOCTL_DISK_PERFORMANCE, NULL, 0, - &diskPerformance, sizeof(diskPerformance), - &dwSize, NULL)) - { - sprintf_s(szDeviceDisplay, MAX_PATH, "PhysicalDrive%d", devNum); - py_tuple = Py_BuildValue( - "(IILLKK)", - diskPerformance.ReadCount, - diskPerformance.WriteCount, - diskPerformance.BytesRead, - diskPerformance.BytesWritten, - (unsigned long long)(diskPerformance.ReadTime.QuadPart * 10) / 1000, - (unsigned long long)(diskPerformance.WriteTime.QuadPart * 10) / 1000); - if (!py_tuple) - goto error; - if (PyDict_SetItemString(py_retdict, szDeviceDisplay, - py_tuple)) - { - goto error; + + // DeviceIoControl() sucks! + i = 0; + ioctrlSize = sizeof(diskPerformance); + while (1) { + i += 1; + ret = DeviceIoControl( + hDevice, IOCTL_DISK_PERFORMANCE, NULL, 0, &diskPerformance, + ioctrlSize, &dwSize, NULL); + if (ret != 0) + break; // OK! + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // Retry with a bigger buffer (+ limit for retries). + if (i <= 1024) { + ioctrlSize *= 2; + continue; + } } - Py_XDECREF(py_tuple); - } - else { - // XXX we might get here with ERROR_INSUFFICIENT_BUFFER when - // compiling with mingw32; not sure what to do. - // return PyErr_SetFromWindowsErr(0); - ;; + else if (GetLastError() == ERROR_INVALID_FUNCTION) { + // This happens on AppVeyor: + // https://ci.appveyor.com/project/giampaolo/psutil/build/ + // 1364/job/ascpdi271b06jle3 + // Assume it means we're dealing with some exotic disk + // and go on. + psutil_debug("DeviceIoControl -> ERROR_INVALID_FUNCTION; " + "ignore PhysicalDrive%i", devNum); + goto next; + } + else if (GetLastError() == ERROR_NOT_SUPPORTED) { + // Again, let's assume we're dealing with some exotic disk. + psutil_debug("DeviceIoControl -> ERROR_NOT_SUPPORTED; " + "ignore PhysicalDrive%i", devNum); + goto next; + } + // XXX: it seems we should also catch ERROR_INVALID_PARAMETER: + // https://sites.ualberta.ca/dept/aict/uts/software/openbsd/ + // ports/4.1/i386/openafs/w-openafs-1.4.14-transarc/ + // openafs-1.4.14/src/usd/usd_nt.c + + // XXX: we can also bump into ERROR_MORE_DATA in which case + // (quoting doc) we're supposed to retry with a bigger buffer + // and specify a new "starting point", whatever it means. + PyErr_SetFromWindowsErr(0); + goto error; } + sprintf_s(szDeviceDisplay, MAX_PATH, "PhysicalDrive%i", devNum); + py_tuple = Py_BuildValue( + "(IILLKK)", + diskPerformance.ReadCount, + diskPerformance.WriteCount, + diskPerformance.BytesRead, + diskPerformance.BytesWritten, + // convert to ms: + // https://github.com/giampaolo/psutil/issues/1012 + (unsigned long long) + (diskPerformance.ReadTime.QuadPart) / 10000000, + (unsigned long long) + (diskPerformance.WriteTime.QuadPart) / 10000000); + if (!py_tuple) + goto error; + if (PyDict_SetItemString(py_retdict, szDeviceDisplay, py_tuple)) + goto error; + Py_XDECREF(py_tuple); + +next: CloseHandle(hDevice); } @@ -2431,6 +2489,7 @@ static char *psutil_get_drive_type(int type) { #define _ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) #endif + /* * Return disk partitions as a list of tuples such as * (drive_letter, drive_letter, type, "") @@ -2440,10 +2499,15 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { DWORD num_bytes; char drive_strings[255]; char *drive_letter = drive_strings; + char mp_buf[MAX_PATH]; + char mp_path[MAX_PATH]; int all; int type; int ret; + unsigned int old_mode = 0; char opts[20]; + HANDLE mp_h; + BOOL mp_flag= TRUE; LPTSTR fs_type[MAX_PATH + 1] = { 0 }; DWORD pflags = 0; PyObject *py_all; @@ -2456,7 +2520,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { // avoid to visualize a message box in case something goes wrong // see https://github.com/giampaolo/psutil/issues/264 - SetErrorMode(SEM_FAILCRITICALERRORS); + old_mode = SetErrorMode(SEM_FAILCRITICALERRORS); if (! PyArg_ParseTuple(args, "O", &py_all)) goto error; @@ -2514,6 +2578,40 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { strcat_s(opts, _countof(opts), "rw"); if (pflags & FILE_VOLUME_IS_COMPRESSED) strcat_s(opts, _countof(opts), ",compressed"); + + // Check for mount points on this volume and add/get info + // (checks first to know if we can even have mount points) + if (pflags & FILE_SUPPORTS_REPARSE_POINTS) { + + mp_h = FindFirstVolumeMountPoint(drive_letter, mp_buf, MAX_PATH); + if (mp_h != INVALID_HANDLE_VALUE) { + while (mp_flag) { + + // Append full mount path with drive letter + strcpy_s(mp_path, _countof(mp_path), drive_letter); + strcat_s(mp_path, _countof(mp_path), mp_buf); + + py_tuple = Py_BuildValue( + "(ssss)", + drive_letter, + mp_path, + fs_type, // Typically NTFS + opts); + + if (!py_tuple || PyList_Append(py_retlist, py_tuple) == -1) { + FindVolumeMountPointClose(mp_h); + goto error; + } + + Py_DECREF(py_tuple); + + // Continue looking for more mount points + mp_flag = FindNextVolumeMountPoint(mp_h, mp_buf, MAX_PATH); + } + FindVolumeMountPointClose(mp_h); + } + + } } if (strlen(opts) > 0) @@ -2537,11 +2635,11 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { drive_letter = strchr(drive_letter, 0) + 1; } - SetErrorMode(0); + SetErrorMode(old_mode); return py_retlist; error: - SetErrorMode(0); + SetErrorMode(old_mode); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); return NULL; @@ -2552,8 +2650,8 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { */ static PyObject * psutil_users(PyObject *self, PyObject *args) { - HANDLE hServer = NULL; - LPTSTR buffer_user = NULL; + HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; + WCHAR *buffer_user = NULL; LPTSTR buffer_addr = NULL; PWTS_SESSION_INFO sessions = NULL; DWORD count; @@ -2572,7 +2670,7 @@ psutil_users(PyObject *self, PyObject *args) { PyObject *py_retlist = PyList_New(0); PyObject *py_tuple = NULL; PyObject *py_address = NULL; - PyObject *py_buffer_user_encoded = NULL; + PyObject *py_username = NULL; if (py_retlist == NULL) return NULL; @@ -2581,12 +2679,6 @@ psutil_users(PyObject *self, PyObject *args) { WinStationQueryInformationW = (PWINSTATIONQUERYINFORMATIONW) \ GetProcAddress(hInstWinSta, "WinStationQueryInformationW"); - hServer = WTSOpenServer('\0'); - if (hServer == NULL) { - PyErr_SetFromWindowsErr(0); - goto error; - } - if (WTSEnumerateSessions(hServer, 0, 1, &sessions, &count) == 0) { PyErr_SetFromWindowsErr(0); goto error; @@ -2606,12 +2698,12 @@ psutil_users(PyObject *self, PyObject *args) { // username bytes = 0; - if (WTSQuerySessionInformation(hServer, sessionId, WTSUserName, - &buffer_user, &bytes) == 0) { + if (WTSQuerySessionInformationW(hServer, sessionId, WTSUserName, + &buffer_user, &bytes) == 0) { PyErr_SetFromWindowsErr(0); goto error; } - if (bytes == 1) + if (bytes <= 2) continue; // address @@ -2655,23 +2747,22 @@ psutil_users(PyObject *self, PyObject *args) { station_info.ConnectTime.dwLowDateTime - 116444736000000000LL; unix_time /= 10000000; - py_buffer_user_encoded = PyUnicode_Decode( - buffer_user, _tcslen(buffer_user), Py_FileSystemDefaultEncoding, - "replace"); - if (py_buffer_user_encoded == NULL) + py_username = PyUnicode_FromWideChar(buffer_user, wcslen(buffer_user)); + if (py_username == NULL) goto error; - py_tuple = Py_BuildValue("OOd", py_buffer_user_encoded, py_address, + py_tuple = Py_BuildValue("OOd", + py_username, + py_address, (double)unix_time); if (!py_tuple) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; - Py_XDECREF(py_buffer_user_encoded); + Py_XDECREF(py_username); Py_XDECREF(py_address); Py_XDECREF(py_tuple); } - WTSCloseServer(hServer); WTSFreeMemory(sessions); WTSFreeMemory(buffer_user); WTSFreeMemory(buffer_addr); @@ -2679,15 +2770,13 @@ psutil_users(PyObject *self, PyObject *args) { return py_retlist; error: - Py_XDECREF(py_buffer_user_encoded); + Py_XDECREF(py_username); Py_XDECREF(py_tuple); Py_XDECREF(py_address); Py_DECREF(py_retlist); if (hInstWinSta != NULL) FreeLibrary(hInstWinSta); - if (hServer != NULL) - WTSCloseServer(hServer); if (sessions != NULL) WTSFreeMemory(sessions); if (buffer_user != NULL) @@ -2780,9 +2869,9 @@ psutil_proc_info(PyObject *self, PyObject *args) { py_retlist = Py_BuildValue( #if defined(_WIN64) - "kkdddiKKKK" "kKKKKKKKKK", + "kkdddiKKKKKK" "kKKKKKKKKK", #else - "kkdddiKKKK" "kIIIIIIIII", + "kkdddiKKKKKK" "kIIIIIIIII", #endif process->HandleCount, // num handles ctx_switches, // num ctx switches @@ -2790,10 +2879,13 @@ psutil_proc_info(PyObject *self, PyObject *args) { kernel_time, // cpu kernel time (double)create_time, // create time (int)process->NumberOfThreads, // num threads + // IO counters process->ReadOperationCount.QuadPart, // io rcount process->WriteOperationCount.QuadPart, // io wcount process->ReadTransferCount.QuadPart, // io rbytes process->WriteTransferCount.QuadPart, // io wbytes + process->OtherOperationCount.QuadPart, // io others count + process->OtherTransferCount.QuadPart, // io others bytes // memory process->PageFaultCount, // num page faults process->PeakWorkingSetSize, // peak wset @@ -2850,11 +2942,12 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { HANDLE hProcess = NULL; PVOID baseAddress; PVOID previousAllocationBase; - CHAR mappedFileName[MAX_PATH]; + WCHAR mappedFileName[MAX_PATH]; SYSTEM_INFO system_info; LPVOID maxAddr; PyObject *py_retlist = PyList_New(0); PyObject *py_tuple = NULL; + PyObject *py_str = NULL; if (py_retlist == NULL) return NULL; @@ -2875,20 +2968,24 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { py_tuple = NULL; if (baseAddress > maxAddr) break; - if (GetMappedFileNameA(hProcess, baseAddress, mappedFileName, + if (GetMappedFileNameW(hProcess, baseAddress, mappedFileName, sizeof(mappedFileName))) { + py_str = PyUnicode_FromWideChar(mappedFileName, + wcslen(mappedFileName)); + if (py_str == NULL) + goto error; #ifdef _WIN64 py_tuple = Py_BuildValue( - "(KssI)", + "(KsOI)", (unsigned long long)baseAddress, #else py_tuple = Py_BuildValue( - "(kssI)", + "(ksOI)", (unsigned long)baseAddress, #endif get_region_protection_string(basicInfo.Protect), - mappedFileName, + py_str, basicInfo.RegionSize); if (!py_tuple) @@ -2896,6 +2993,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { if (PyList_Append(py_retlist, py_tuple)) goto error; Py_DECREF(py_tuple); + Py_DECREF(py_str); } previousAllocationBase = basicInfo.AllocationBase; baseAddress = (PCHAR)baseAddress + basicInfo.RegionSize; @@ -2906,6 +3004,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { error: Py_XDECREF(py_tuple); + Py_XDECREF(py_str); Py_DECREF(py_retlist); if (hProcess != NULL) CloseHandle(hProcess); @@ -2972,10 +3071,9 @@ psutil_net_if_addrs(PyObject *self, PyObject *args) { PCTSTR intRet; PCTSTR netmaskIntRet; char *ptr; - char buff[100]; - DWORD bufflen = 100; - char netmask_buff[100]; - DWORD netmask_bufflen = 100; + char buff_addr[1024]; + char buff_macaddr[1024]; + char buff_netmask[1024]; DWORD dwRetVal = 0; #if (_WIN32_WINNT >= 0x0600) // Windows Vista and above ULONG converted_netmask; @@ -3014,26 +3112,22 @@ psutil_net_if_addrs(PyObject *self, PyObject *args) { // MAC address if (pCurrAddresses->PhysicalAddressLength != 0) { - ptr = buff; + ptr = buff_macaddr; *ptr = '\0'; for (i = 0; i < (int) pCurrAddresses->PhysicalAddressLength; i++) { if (i == (pCurrAddresses->PhysicalAddressLength - 1)) { - sprintf_s(ptr, _countof(buff), "%.2X\n", + sprintf_s(ptr, _countof(buff_macaddr), "%.2X\n", (int)pCurrAddresses->PhysicalAddress[i]); } else { - sprintf_s(ptr, _countof(buff), "%.2X-", + sprintf_s(ptr, _countof(buff_macaddr), "%.2X-", (int)pCurrAddresses->PhysicalAddress[i]); } ptr += 3; } *--ptr = '\0'; -#if PY_MAJOR_VERSION >= 3 - py_mac_address = PyUnicode_FromString(buff); -#else - py_mac_address = PyString_FromString(buff); -#endif + py_mac_address = Py_BuildValue("s", buff_macaddr); if (py_mac_address == NULL) goto error; @@ -3064,15 +3158,20 @@ psutil_net_if_addrs(PyObject *self, PyObject *args) { if (family == AF_INET) { struct sockaddr_in *sa_in = (struct sockaddr_in *) pUnicast->Address.lpSockaddr; - intRet = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, - bufflen); + intRet = inet_ntop(AF_INET, &(sa_in->sin_addr), buff_addr, + sizeof(buff_addr)); + if (!intRet) + goto error; #if (_WIN32_WINNT >= 0x0600) // Windows Vista and above netmask_bits = pUnicast->OnLinkPrefixLength; dwRetVal = ConvertLengthToIpv4Mask(netmask_bits, &converted_netmask); if (dwRetVal == NO_ERROR) { in_netmask.s_addr = converted_netmask; - netmaskIntRet = inet_ntop(AF_INET, &in_netmask, netmask_buff, - netmask_bufflen); + netmaskIntRet = inet_ntop( + AF_INET, &in_netmask, buff_netmask, + sizeof(buff_netmask)); + if (!netmaskIntRet) + goto error; } #endif } @@ -3080,7 +3179,9 @@ psutil_net_if_addrs(PyObject *self, PyObject *args) { struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *) pUnicast->Address.lpSockaddr; intRet = inet_ntop(AF_INET6, &(sa_in6->sin6_addr), - buff, bufflen); + buff_addr, sizeof(buff_addr)); + if (!intRet) + goto error; } else { // we should never get here @@ -3088,23 +3189,19 @@ psutil_net_if_addrs(PyObject *self, PyObject *args) { continue; } - if (intRet == NULL) { - PyErr_SetFromWindowsErr(GetLastError()); - goto error; - } #if PY_MAJOR_VERSION >= 3 - py_address = PyUnicode_FromString(buff); + py_address = PyUnicode_FromString(buff_addr); #else - py_address = PyString_FromString(buff); + py_address = PyString_FromString(buff_addr); #endif if (py_address == NULL) goto error; if (netmaskIntRet != NULL) { #if PY_MAJOR_VERSION >= 3 - py_netmask = PyUnicode_FromString(netmask_buff); + py_netmask = PyUnicode_FromString(buff_netmask); #else - py_netmask = PyString_FromString(netmask_buff); + py_netmask = PyString_FromString(buff_netmask); #endif } else { Py_INCREF(Py_None); @@ -3400,11 +3497,85 @@ psutil_cpu_stats(PyObject *self, PyObject *args) { } +/* + * Return CPU frequency. + */ +static PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + PROCESSOR_POWER_INFORMATION *ppi; + NTSTATUS ret; + size_t size; + LPBYTE pBuffer = NULL; + ULONG current; + ULONG max; + unsigned int num_cpus; + SYSTEM_INFO system_info; + system_info.dwNumberOfProcessors = 0; + + // Get the number of CPUs. + GetSystemInfo(&system_info); + if (system_info.dwNumberOfProcessors == 0) + num_cpus = 1; + else + num_cpus = system_info.dwNumberOfProcessors; + + // Allocate size. + size = num_cpus * sizeof(PROCESSOR_POWER_INFORMATION); + pBuffer = (BYTE*)LocalAlloc(LPTR, size); + if (! pBuffer) + return PyErr_SetFromWindowsErr(0); + + // Syscall. + ret = CallNtPowerInformation( + ProcessorInformation, NULL, 0, pBuffer, size); + if (ret != 0) { + PyErr_SetString(PyExc_RuntimeError, + "CallNtPowerInformation syscall failed"); + goto error; + } + + // Results. + ppi = (PROCESSOR_POWER_INFORMATION *)pBuffer; + max = ppi->MaxMhz; + current = ppi->CurrentMhz; + LocalFree(pBuffer); + + return Py_BuildValue("kk", current, max); + +error: + if (pBuffer != NULL) + LocalFree(pBuffer); + return NULL; +} + + +/* + * Return battery usage stats. + */ +static PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + SYSTEM_POWER_STATUS sps; + + if (GetSystemPowerStatus(&sps) == 0) + return PyErr_SetFromWindowsErr(0); + return Py_BuildValue( + "iiiI", + sps.ACLineStatus, // whether AC is connected: 0=no, 1=yes, 255=unknown + // status flag: + // 1, 2, 4 = high, low, critical + // 8 = charging + // 128 = no battery + sps.BatteryFlag, + sps.BatteryLifePercent, // percent + sps.BatteryLifeTime // remaining secs + ); +} + + // ------------------------ Python init --------------------------- static PyMethodDef PsutilMethods[] = { - // --- per-process functions {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS, @@ -3504,6 +3675,10 @@ PsutilMethods[] = { "Return NICs stats."}, {"cpu_stats", psutil_cpu_stats, METH_VARARGS, "Return NICs stats."}, + {"cpu_freq", psutil_cpu_freq, METH_VARARGS, + "Return CPU frequency."}, + {"sensors_battery", psutil_sensors_battery, METH_VARARGS, + "Return battery metrics usage."}, // --- windows services {"winservice_enumerate", psutil_winservice_enumerate, METH_VARARGS, @@ -3523,6 +3698,10 @@ PsutilMethods[] = { {"win32_QueryDosDevice", psutil_win32_QueryDosDevice, METH_VARARGS, "QueryDosDevice binding"}, + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + {NULL, NULL, 0, NULL} }; @@ -3667,6 +3846,7 @@ void init_psutil_windows(void) // set SeDebug for the current process psutil_set_se_debug(); + psutil_setup(); #if PY_MAJOR_VERSION >= 3 return module; diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index cb816f73a..b6c58c936 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -26,22 +26,29 @@ # but if we get here it means this this was a wheel (or exe). msg = "this Windows version is too old (< Windows Vista); " msg += "psutil 3.4.2 is the latest version which supports Windows " - msg += "2000, XP and 2003 server" + msg += "2000, XP and 2003 server; it may be possible that psutil " + msg += "will work if compiled from sources though" raise RuntimeError(msg) else: raise from ._common import conn_tmap +from ._common import ENCODING +from ._common import ENCODING_ERRS from ._common import isfile_strict +from ._common import memoize_when_activated from ._common import parse_environ_block from ._common import sockfam_to_enum from ._common import socktype_to_enum from ._common import usage_percent -from ._common import memoize_when_activated from ._compat import long from ._compat import lru_cache from ._compat import PY3 +from ._compat import unicode from ._compat import xrange +from ._exceptions import AccessDenied +from ._exceptions import NoSuchProcess +from ._exceptions import TimeoutExpired from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS from ._psutil_windows import HIGH_PRIORITY_CLASS @@ -67,14 +74,16 @@ # ===================================================================== -# --- constants +# --- globals # ===================================================================== - CONN_DELETE_TCB = "DELETE_TCB" WAIT_TIMEOUT = 0x00000102 # 258 in decimal -ACCESS_DENIED_SET = frozenset([errno.EPERM, errno.EACCES, - cext.ERROR_ACCESS_DENIED]) +ACCESS_DENIED_ERRSET = frozenset([errno.EPERM, errno.EACCES, + cext.ERROR_ACCESS_DENIED]) +NO_SUCH_SERVICE_ERRSET = frozenset([cext.ERROR_INVALID_NAME, + cext.ERROR_SERVICE_DOES_NOT_EXIST]) + if enum is None: AF_LINK = -1 @@ -120,16 +129,18 @@ class Priority(enum.IntEnum): io_wcount=7, io_rbytes=8, io_wbytes=9, - num_page_faults=10, - peak_wset=11, - wset=12, - peak_paged_pool=13, - paged_pool=14, - peak_non_paged_pool=15, - non_paged_pool=16, - pagefile=17, - peak_pagefile=18, - mem_private=19, + io_count_others=10, + io_bytes_others=11, + num_page_faults=12, + peak_wset=13, + wset=14, + peak_paged_pool=15, + paged_pool=16, + peak_non_paged_pool=17, + non_paged_pool=18, + pagefile=19, + peak_pagefile=20, + mem_private=21, ) @@ -138,33 +149,28 @@ class Priority(enum.IntEnum): # ===================================================================== +# psutil.cpu_times() scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'interrupt', 'dpc']) +# psutil.virtual_memory() svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +# psutil.Process.memory_info() pmem = namedtuple( 'pmem', ['rss', 'vms', 'num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool', 'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool', 'pagefile', 'peak_pagefile', 'private']) +# psutil.Process.memory_full_info() pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', )) +# psutil.Process.memory_maps(grouped=True) pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss']) +# psutil.Process.memory_maps(grouped=False) pmmap_ext = namedtuple( 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) -ntpinfo = namedtuple( - 'ntpinfo', ['num_handles', 'ctx_switches', 'user_time', 'kernel_time', - 'create_time', 'num_threads', 'io_rcount', 'io_wcount', - 'io_rbytes', 'io_wbytes']) - - -# ===================================================================== -# --- exceptions -# ===================================================================== - - -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -AccessDenied = None -TimeoutExpired = None +# psutil.Process.io_counters() +pio = namedtuple('pio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'other_count', 'other_bytes']) # ===================================================================== @@ -173,31 +179,28 @@ class Priority(enum.IntEnum): @lru_cache(maxsize=512) -def _win32_QueryDosDevice(s): - return cext.win32_QueryDosDevice(s) - - def convert_dos_path(s): - # convert paths using native DOS format like: - # "\Device\HarddiskVolume1\Windows\systemew\file.txt" - # into: "C:\Windows\systemew\file.txt" - if PY3 and not isinstance(s, str): - s = s.decode('utf8') + r"""Convert paths using native DOS format like: + "\Device\HarddiskVolume1\Windows\systemew\file.txt" + into: + "C:\Windows\systemew\file.txt" + """ rawdrive = '\\'.join(s.split('\\')[:3]) - driveletter = _win32_QueryDosDevice(rawdrive) + driveletter = cext.win32_QueryDosDevice(rawdrive) return os.path.join(driveletter, s[len(rawdrive):]) -def py2_strencode(s, encoding=sys.getfilesystemencoding()): - if PY3 or isinstance(s, str): +def py2_strencode(s): + """Encode a unicode string to a byte string by using the default fs + encoding + "replace" error handler. + """ + if PY3: return s else: - try: - return s.encode(encoding) - except UnicodeEncodeError: - # Filesystem codec failed, return the plain unicode - # string (this should never happen). + if isinstance(s, str): return s + else: + return s.encode(ENCODING, errors=ENCODING_ERRS) # ===================================================================== @@ -238,13 +241,11 @@ def swap_memory(): def disk_usage(path): """Return disk usage associated with path.""" - try: - total, free = cext.disk_usage(path) - except WindowsError: - if not os.path.exists(path): - msg = "No such file or directory: '%s'" % path - raise OSError(errno.ENOENT, msg) - raise + if PY3 and isinstance(path, bytes): + # XXX: do we want to use "strict"? Probably yes, in order + # to fail immediately. After all we are accepting input here... + path = path.decode(ENCODING, errors="strict") + total, free = cext.disk_usage(path) used = total - free percent = usage_percent(used, total, _round=1) return _common.sdiskusage(total, used, free, percent) @@ -299,6 +300,15 @@ def cpu_stats(): syscalls) +def cpu_freq(): + """Return CPU frequency. + On Windows per-cpu frequency is not supported. + """ + curr, max_ = cext.cpu_freq() + min_ = 0.0 + return [_common.scpufreq(float(curr), min_, float(max_))] + + # ===================================================================== # --- network # ===================================================================== @@ -316,6 +326,10 @@ def net_connections(kind, _pid=-1): ret = set() for item in rawlist: fd, fam, type, laddr, raddr, status, pid = item + if laddr: + laddr = _common.addr(*laddr) + if raddr: + raddr = _common.addr(*raddr) status = TCP_STATUSES[status] fam = sockfam_to_enum(fam) type = socktype_to_enum(type) @@ -328,9 +342,13 @@ def net_connections(kind, _pid=-1): def net_if_stats(): - ret = cext.net_if_stats() - for name, items in ret.items(): - name = py2_strencode(name) + """Get NIC stats (isup, duplex, speed, mtu).""" + ret = {} + rawdict = cext.net_if_stats() + for name, items in rawdict.items(): + if not PY3: + assert isinstance(name, unicode), type(name) + name = py2_strencode(name) isup, duplex, speed, mtu = items if hasattr(_common, 'NicDuplex'): duplex = _common.NicDuplex(duplex) @@ -339,11 +357,15 @@ def net_if_stats(): def net_io_counters(): + """Return network I/O statistics for every network interface + installed on the system as a dict of raw tuples. + """ ret = cext.net_io_counters() return dict([(py2_strencode(k), v) for k, v in ret.items()]) def net_if_addrs(): + """Return the addresses associated to each NIC.""" ret = [] for items in cext.net_if_addrs(): items = list(items) @@ -352,14 +374,51 @@ def net_if_addrs(): return ret +# ===================================================================== +# --- sensors +# ===================================================================== + + +def sensors_battery(): + """Return battery information.""" + # For constants meaning see: + # https://msdn.microsoft.com/en-us/library/windows/desktop/ + # aa373232(v=vs.85).aspx + acline_status, flags, percent, secsleft = cext.sensors_battery() + power_plugged = acline_status == 1 + no_battery = bool(flags & 128) + charging = bool(flags & 8) + + if no_battery: + return None + if power_plugged or charging: + secsleft = _common.POWER_TIME_UNLIMITED + elif secsleft == -1: + secsleft = _common.POWER_TIME_UNKNOWN + + return _common.sbattery(percent, secsleft, power_plugged) + + # ===================================================================== # --- other system functions # ===================================================================== +_last_btime = 0 + + def boot_time(): """The system boot time expressed in seconds since the epoch.""" - return cext.boot_time() + # This dirty hack is to adjust the precision of the returned + # value which may have a 1 second fluctuation, see: + # https://github.com/giampaolo/psutil/issues/1007 + global _last_btime + ret = float(cext.boot_time()) + if abs(ret - _last_btime) <= 1: + return _last_btime + else: + _last_btime = ret + return ret def users(): @@ -369,7 +428,7 @@ def users(): for item in rawlist: user, hostname, tstamp = item user = py2_strencode(user) - nt = _common.suser(user, None, hostname, tstamp) + nt = _common.suser(user, None, hostname, tstamp, None) retlist.append(nt) return retlist @@ -380,9 +439,9 @@ def users(): def win_service_iter(): - """Return a list of WindowsService instances.""" + """Yields a list of WindowsService instances.""" for name, display_name in cext.winservice_enumerate(): - yield WindowsService(name, display_name) + yield WindowsService(py2_strencode(name), py2_strencode(display_name)) def win_service_get(name): @@ -423,10 +482,10 @@ def _query_config(self): cext.winservice_query_config(self._name) # XXX - update _self.display_name? return dict( - display_name=display_name, - binpath=binpath, - username=username, - start_type=start_type) + display_name=py2_strencode(display_name), + binpath=py2_strencode(binpath), + username=py2_strencode(username), + start_type=py2_strencode(start_type)) def _query_status(self): with self._wrap_exceptions(): @@ -443,15 +502,13 @@ def _wrap_exceptions(self): try: yield except WindowsError as err: - NO_SUCH_SERVICE_SET = (cext.ERROR_INVALID_NAME, - cext.ERROR_SERVICE_DOES_NOT_EXIST) - if err.errno in ACCESS_DENIED_SET: + if err.errno in ACCESS_DENIED_ERRSET: raise AccessDenied( pid=None, name=self._name, msg="service %r is not querable (not enough privileges)" % self._name) - elif err.errno in NO_SUCH_SERVICE_SET or \ - err.winerror in NO_SUCH_SERVICE_SET: + elif err.errno in NO_SUCH_SERVICE_ERRSET or \ + err.winerror in NO_SUCH_SERVICE_ERRSET: raise NoSuchProcess( pid=None, name=self._name, msg="service %r does not exist)" % self._name) @@ -503,7 +560,7 @@ def status(self): def description(self): """Service long description.""" - return cext.winservice_query_descr(self.name()) + return py2_strencode(cext.winservice_query_descr(self.name())) # utils @@ -577,7 +634,7 @@ def wrapper(self, *args, **kwargs): try: return fun(self, *args, **kwargs) except OSError as err: - if err.errno in ACCESS_DENIED_SET: + if err.errno in ACCESS_DENIED_ERRSET: raise AccessDenied(self.pid, self._name) if err.errno == errno.ESRCH: raise NoSuchProcess(self.pid, self._name) @@ -653,7 +710,10 @@ def cmdline(self): @wrap_exceptions def environ(self): - return parse_environ_block(cext.proc_environ(self.pid)) + ustr = cext.proc_environ(self.pid) + if ustr and not PY3: + assert isinstance(ustr, unicode), type(ustr) + return parse_environ_block(py2_strencode(ustr)) def ppid(self): try: @@ -665,7 +725,7 @@ def _get_raw_meminfo(self): try: return cext.proc_memory_info(self.pid) except OSError as err: - if err.errno in ACCESS_DENIED_SET: + if err.errno in ACCESS_DENIED_ERRSET: # TODO: the C ext can probably be refactored in order # to get this from cext.proc_info() info = self.oneshot_info() @@ -705,7 +765,7 @@ def memory_maps(self): except OSError as err: # XXX - can't use wrap_exceptions decorator as we're # returning a generator; probably needs refactoring. - if err.errno in ACCESS_DENIED_SET: + if err.errno in ACCESS_DENIED_ERRSET: raise AccessDenied(self.pid, self._name) if err.errno == errno.ESRCH: raise NoSuchProcess(self.pid, self._name) @@ -713,6 +773,9 @@ def memory_maps(self): else: for addr, perm, path, rss in raw: path = convert_dos_path(path) + if not PY3: + assert isinstance(path, unicode), type(path) + path = py2_strencode(path) addr = hex(addr) yield (addr, perm, path, rss) @@ -731,16 +794,23 @@ def wait(self, timeout=None): else: # WaitForSingleObject() expects time in milliseconds cext_timeout = int(timeout * 1000) - ret = cext.proc_wait(self.pid, cext_timeout) - if ret == WAIT_TIMEOUT: - raise TimeoutExpired(timeout, self.pid, self._name) - return ret + while True: + ret = cext.proc_wait(self.pid, cext_timeout) + if ret == WAIT_TIMEOUT: + raise TimeoutExpired(timeout, self.pid, self._name) + if pid_exists(self.pid): + if timeout is None: + continue + else: + raise TimeoutExpired(timeout, self.pid, self._name) + return ret @wrap_exceptions def username(self): if self.pid in (0, 4): return 'NT AUTHORITY\\SYSTEM' - return cext.proc_username(self.pid) + domain, user = cext.proc_username(self.pid) + return py2_strencode(domain) + '\\' + py2_strencode(user) @wrap_exceptions def create_time(self): @@ -750,7 +820,7 @@ def create_time(self): try: return cext.proc_create_time(self.pid) except OSError as err: - if err.errno in ACCESS_DENIED_SET: + if err.errno in ACCESS_DENIED_ERRSET: return self.oneshot_info()[pinfo_map['create_time']] raise @@ -772,14 +842,14 @@ def cpu_times(self): try: user, system = cext.proc_cpu_times(self.pid) except OSError as err: - if err.errno in ACCESS_DENIED_SET: + if err.errno in ACCESS_DENIED_ERRSET: info = self.oneshot_info() user = info[pinfo_map['user_time']] system = info[pinfo_map['kernel_time']] else: raise # Children user/system times are not retrievable (set to 0). - return _common.pcputimes(user, system, 0, 0) + return _common.pcputimes(user, system, 0.0, 0.0) @wrap_exceptions def suspend(self): @@ -853,17 +923,19 @@ def io_counters(self): try: ret = cext.proc_io_counters(self.pid) except OSError as err: - if err.errno in ACCESS_DENIED_SET: + if err.errno in ACCESS_DENIED_ERRSET: info = self.oneshot_info() ret = ( info[pinfo_map['io_rcount']], info[pinfo_map['io_wcount']], info[pinfo_map['io_rbytes']], info[pinfo_map['io_wbytes']], + info[pinfo_map['io_count_others']], + info[pinfo_map['io_bytes_others']], ) else: raise - return _common.pio(*ret) + return pio(*ret) @wrap_exceptions def status(self): @@ -910,7 +982,7 @@ def num_handles(self): try: return cext.proc_num_handles(self.pid) except OSError as err: - if err.errno in ACCESS_DENIED_SET: + if err.errno in ACCESS_DENIED_ERRSET: return self.oneshot_info()[pinfo_map['num_handles']] raise diff --git a/psutil/arch/aix/common.c b/psutil/arch/aix/common.c new file mode 100644 index 000000000..6115a15db --- /dev/null +++ b/psutil/arch/aix/common.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include "common.h" + +/* psutil_kread() - read from kernel memory */ +int +psutil_kread( + int Kd, /* kernel memory file descriptor */ + KA_T addr, /* kernel memory address */ + char *buf, /* buffer to receive data */ + size_t len) { /* length to read */ + int br; + + if (lseek64(Kd, (off64_t)addr, L_SET) == (off64_t)-1) { + PyErr_SetFromErrno(PyExc_OSError); + return 1; + } + br = read(Kd, buf, len); + if (br == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return 1; + } + if (br != len) { + PyErr_SetString(PyExc_RuntimeError, + "size mismatch when reading kernel memory fd"); + return 1; + } + return 0; +} + +struct procentry64 * +psutil_read_process_table(int * num) { + size_t msz; + pid32_t pid = 0; + struct procentry64 *processes = (struct procentry64 *)NULL; + struct procentry64 *p; + int Np = 0; /* number of processes allocated in 'processes' */ + int np = 0; /* number of processes read into 'processes' */ + int i; /* number of processes read in current iteration */ + + msz = (size_t)(PROCSIZE * PROCINFO_INCR); + processes = (struct procentry64 *)malloc(msz); + if (!processes) { + PyErr_NoMemory(); + return NULL; + } + Np = PROCINFO_INCR; + p = processes; + while ((i = getprocs64(p, PROCSIZE, (struct fdsinfo64 *)NULL, 0, &pid, + PROCINFO_INCR)) + == PROCINFO_INCR) { + np += PROCINFO_INCR; + if (np >= Np) { + msz = (size_t)(PROCSIZE * (Np + PROCINFO_INCR)); + processes = (struct procentry64 *)realloc((char *)processes, msz); + if (!processes) { + PyErr_NoMemory(); + return NULL; + } + Np += PROCINFO_INCR; + } + p = (struct procentry64 *)((char *)processes + (np * PROCSIZE)); + } + + /* add the number of processes read in the last iteration */ + if (i > 0) + np += i; + + *num = np; + return processes; +} \ No newline at end of file diff --git a/psutil/arch/aix/common.h b/psutil/arch/aix/common.h new file mode 100644 index 000000000..b677d8c29 --- /dev/null +++ b/psutil/arch/aix/common.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __PSUTIL_AIX_COMMON_H__ +#define __PSUTIL_AIX_COMMON_H__ + +#include + +#define PROCINFO_INCR (256) +#define PROCSIZE (sizeof(struct procentry64)) +#define FDSINFOSIZE (sizeof(struct fdsinfo64)) +#define KMEM "/dev/kmem" + +typedef u_longlong_t KA_T; + +/* psutil_kread() - read from kernel memory */ +int psutil_kread(int Kd, /* kernel memory file descriptor */ + KA_T addr, /* kernel memory address */ + char *buf, /* buffer to receive data */ + size_t len); /* length to read */ + +struct procentry64 * +psutil_read_process_table( + int * num /* out - number of processes read */ +); + +#endif /* __PSUTIL_AIX_COMMON_H__ */ diff --git a/psutil/arch/aix/ifaddrs.c b/psutil/arch/aix/ifaddrs.c new file mode 100644 index 000000000..1a819365a --- /dev/null +++ b/psutil/arch/aix/ifaddrs.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/*! Based on code from + https://lists.samba.org/archive/samba-technical/2009-February/063079.html +!*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ifaddrs.h" + +#define MAX(x,y) ((x)>(y)?(x):(y)) +#define SIZE(p) MAX((p).sa_len,sizeof(p)) + + +static struct sockaddr * +sa_dup(struct sockaddr *sa1) +{ + struct sockaddr *sa2; + size_t sz = sa1->sa_len; + sa2 = (struct sockaddr *) calloc(1, sz); + if (sa2 == NULL) + return NULL; + memcpy(sa2, sa1, sz); + return sa2; +} + + +void freeifaddrs(struct ifaddrs *ifp) +{ + if (NULL == ifp) return; + free(ifp->ifa_name); + free(ifp->ifa_addr); + free(ifp->ifa_netmask); + free(ifp->ifa_dstaddr); + freeifaddrs(ifp->ifa_next); + free(ifp); +} + + +int getifaddrs(struct ifaddrs **ifap) +{ + int sd, ifsize; + char *ccp, *ecp; + struct ifconf ifc; + struct ifreq *ifr; + struct ifaddrs *cifa = NULL; /* current */ + struct ifaddrs *pifa = NULL; /* previous */ + const size_t IFREQSZ = sizeof(struct ifreq); + int fam; + + *ifap = NULL; + + sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd == -1) + goto error; + + /* find how much memory to allocate for the SIOCGIFCONF call */ + if (ioctl(sd, SIOCGSIZIFCONF, (caddr_t)&ifsize) < 0) + goto error; + + ifc.ifc_req = (struct ifreq *) calloc(1, ifsize); + if (ifc.ifc_req == NULL) + goto error; + ifc.ifc_len = ifsize; + + if (ioctl(sd, SIOCGIFCONF, &ifc) < 0) + goto error; + + ccp = (char *)ifc.ifc_req; + ecp = ccp + ifsize; + + while (ccp < ecp) { + + ifr = (struct ifreq *) ccp; + ifsize = sizeof(ifr->ifr_name) + SIZE(ifr->ifr_addr); + fam = ifr->ifr_addr.sa_family; + + if (fam == AF_INET || fam == AF_INET6) { + cifa = (struct ifaddrs *) calloc(1, sizeof(struct ifaddrs)); + if (cifa == NULL) + goto error; + cifa->ifa_next = NULL; + + if (pifa == NULL) *ifap = cifa; /* first one */ + else pifa->ifa_next = cifa; + + cifa->ifa_name = strdup(ifr->ifr_name); + if (cifa->ifa_name == NULL) + goto error; + cifa->ifa_flags = 0; + cifa->ifa_dstaddr = NULL; + + cifa->ifa_addr = sa_dup(&ifr->ifr_addr); + if (cifa->ifa_addr == NULL) + goto error; + + if (fam == AF_INET) { + if (ioctl(sd, SIOCGIFNETMASK, ifr, IFREQSZ) < 0) + goto error; + cifa->ifa_netmask = sa_dup(&ifr->ifr_addr); + if (cifa->ifa_netmask == NULL) + goto error; + } + + if (0 == ioctl(sd, SIOCGIFFLAGS, ifr)) /* optional */ + cifa->ifa_flags = ifr->ifr_flags; + + if (fam == AF_INET) { + if (ioctl(sd, SIOCGIFDSTADDR, ifr, IFREQSZ) < 0) { + if (0 == ioctl(sd, SIOCGIFBRDADDR, ifr, IFREQSZ)) { + cifa->ifa_dstaddr = sa_dup(&ifr->ifr_addr); + if (cifa->ifa_dstaddr == NULL) + goto error; + } + } + else { + cifa->ifa_dstaddr = sa_dup(&ifr->ifr_addr); + if (cifa->ifa_dstaddr == NULL) + goto error; + } + } + pifa = cifa; + } + + ccp += ifsize; + } + free(ifc.ifc_req); + close(sd); + return 0; +error: + if (ifc.ifc_req != NULL) + free(ifc.ifc_req); + if (sd != -1) + close(sd); + freeifaddrs(*ifap); + return (-1); +} \ No newline at end of file diff --git a/psutil/arch/aix/ifaddrs.h b/psutil/arch/aix/ifaddrs.h new file mode 100644 index 000000000..3920c1ccc --- /dev/null +++ b/psutil/arch/aix/ifaddrs.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/*! Based on code from + https://lists.samba.org/archive/samba-technical/2009-February/063079.html +!*/ + + +#ifndef GENERIC_AIX_IFADDRS_H +#define GENERIC_AIX_IFADDRS_H + +#include +#include + +#undef ifa_dstaddr +#undef ifa_broadaddr +#define ifa_broadaddr ifa_dstaddr + +struct ifaddrs { + struct ifaddrs *ifa_next; + char *ifa_name; + unsigned int ifa_flags; + struct sockaddr *ifa_addr; + struct sockaddr *ifa_netmask; + struct sockaddr *ifa_dstaddr; +}; + +extern int getifaddrs(struct ifaddrs **); +extern void freeifaddrs(struct ifaddrs *); + +#endif \ No newline at end of file diff --git a/psutil/arch/aix/net_connections.c b/psutil/arch/aix/net_connections.c new file mode 100644 index 000000000..69b438920 --- /dev/null +++ b/psutil/arch/aix/net_connections.c @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Baded on code from lsof: + * http://www.ibm.com/developerworks/aix/library/au-lsof.html + * - dialects/aix/dproc.c:gather_proc_info + * - lib/prfp.c:process_file + * - dialects/aix/dsock.c:process_socket + * - dialects/aix/dproc.c:get_kernel_access +*/ + +#include +#include +#include +#define _KERNEL +#include +#undef _KERNEL +#include +#include +#include +#include +#include +#include + +#include "../../_psutil_common.h" +#include "net_kernel_structs.h" +#include "net_connections.h" +#include "common.h" + +#define NO_SOCKET (PyObject *)(-1) + +static int +read_unp_addr( + int Kd, + KA_T unp_addr, + char *buf, + size_t buflen +) { + struct sockaddr_un *ua = (struct sockaddr_un *)NULL; + struct sockaddr_un un; + struct mbuf64 mb; + int uo; + + if (psutil_kread(Kd, unp_addr, (char *)&mb, sizeof(mb))) { + return 1; + } + + uo = (int)(mb.m_hdr.mh_data - unp_addr); + if ((uo + sizeof(struct sockaddr)) <= sizeof(mb)) + ua = (struct sockaddr_un *)((char *)&mb + uo); + else { + if (psutil_kread(Kd, (KA_T)mb.m_hdr.mh_data, + (char *)&un, sizeof(un))) { + return 1; + } + ua = &un; + } + if (ua && ua->sun_path[0]) { + if (mb.m_len > sizeof(struct sockaddr_un)) + mb.m_len = sizeof(struct sockaddr_un); + *((char *)ua + mb.m_len - 1) = '\0'; + snprintf(buf, buflen, "%s", ua->sun_path); + } + return 0; +} + +static PyObject * +process_file(int Kd, pid32_t pid, int fd, KA_T fp) { + struct file64 f; + struct socket64 s; + struct protosw64 p; + struct domain d; + struct inpcb64 inp; + int fam; + struct tcpcb64 t; + int state = PSUTIL_CONN_NONE; + unsigned char *laddr = (unsigned char *)NULL; + unsigned char *raddr = (unsigned char *)NULL; + int rport, lport; + char laddr_str[INET6_ADDRSTRLEN]; + char raddr_str[INET6_ADDRSTRLEN]; + struct unpcb64 unp; + char unix_laddr_str[PATH_MAX] = { 0 }; + char unix_raddr_str[PATH_MAX] = { 0 }; + + /* Read file structure */ + if (psutil_kread(Kd, fp, (char *)&f, sizeof(f))) { + return NULL; + } + if (!f.f_count || f.f_type != DTYPE_SOCKET) { + return NO_SOCKET; + } + + if (psutil_kread(Kd, (KA_T) f.f_data, (char *) &s, sizeof(s))) { + return NULL; + } + + if (!s.so_type) { + return NO_SOCKET; + } + + if (!s.so_proto) { + PyErr_SetString(PyExc_RuntimeError, "invalid socket protocol handle"); + return NULL; + } + if (psutil_kread(Kd, (KA_T)s.so_proto, (char *)&p, sizeof(p))) { + return NULL; + } + + if (!p.pr_domain) { + PyErr_SetString(PyExc_RuntimeError, "invalid socket protocol domain"); + return NULL; + } + if (psutil_kread(Kd, (KA_T)p.pr_domain, (char *)&d, sizeof(d))) { + return NULL; + } + + fam = d.dom_family; + if (fam == AF_INET || fam == AF_INET6) { + /* Read protocol control block */ + if (!s.so_pcb) { + PyErr_SetString(PyExc_RuntimeError, "invalid socket PCB"); + return NULL; + } + if (psutil_kread(Kd, (KA_T) s.so_pcb, (char *) &inp, sizeof(inp))) { + return NULL; + } + + if (p.pr_protocol == IPPROTO_TCP) { + /* If this is a TCP socket, read its control block */ + if (inp.inp_ppcb + && !psutil_kread(Kd, (KA_T)inp.inp_ppcb, + (char *)&t, sizeof(t))) + state = t.t_state; + } + + if (fam == AF_INET6) { + laddr = (unsigned char *)&inp.inp_laddr6; + if (!IN6_IS_ADDR_UNSPECIFIED(&inp.inp_faddr6)) { + raddr = (unsigned char *)&inp.inp_faddr6; + rport = (int)ntohs(inp.inp_fport); + } + } + if (fam == AF_INET) { + laddr = (unsigned char *)&inp.inp_laddr; + if (inp.inp_faddr.s_addr != INADDR_ANY || inp.inp_fport != 0) { + raddr = (unsigned char *)&inp.inp_faddr; + rport = (int)ntohs(inp.inp_fport); + } + } + lport = (int)ntohs(inp.inp_lport); + + inet_ntop(fam, laddr, laddr_str, sizeof(laddr_str)); + + if (raddr != NULL) { + inet_ntop(fam, raddr, raddr_str, sizeof(raddr_str)); + return Py_BuildValue("(iii(si)(si)ii)", fd, fam, + s.so_type, laddr_str, lport, raddr_str, + rport, state, pid); + } + else { + return Py_BuildValue("(iii(si)()ii)", fd, fam, + s.so_type, laddr_str, lport, state, + pid); + } + } + + + if (fam == AF_UNIX) { + if (psutil_kread(Kd, (KA_T) s.so_pcb, (char *)&unp, sizeof(unp))) { + return NULL; + } + if ((KA_T) f.f_data != (KA_T) unp.unp_socket) { + PyErr_SetString(PyExc_RuntimeError, "unp_socket mismatch"); + return NULL; + } + + if (unp.unp_addr) { + if (read_unp_addr(Kd, unp.unp_addr, unix_laddr_str, + sizeof(unix_laddr_str))) { + return NULL; + } + } + + if (unp.unp_conn) { + if (psutil_kread(Kd, (KA_T) unp.unp_conn, (char *)&unp, + sizeof(unp))) { + return NULL; + } + if (read_unp_addr(Kd, unp.unp_addr, unix_raddr_str, + sizeof(unix_raddr_str))) { + return NULL; + } + } + + return Py_BuildValue("(iiissii)", fd, d.dom_family, + s.so_type, unix_laddr_str, unix_raddr_str, PSUTIL_CONN_NONE, + pid); + } + return NO_SOCKET; +} + +PyObject * +psutil_net_connections(PyObject *self, PyObject *args) { + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + KA_T fp; + int Kd = -1; + int i, np; + struct procentry64 *p; + struct fdsinfo64 *fds = (struct fdsinfo64 *)NULL; + pid32_t requested_pid; + pid32_t pid; + struct procentry64 *processes = (struct procentry64 *)NULL; + /* the process table */ + + if (py_retlist == NULL) + goto error; + if (! PyArg_ParseTuple(args, "i", &requested_pid)) + goto error; + + Kd = open(KMEM, O_RDONLY, 0); + if (Kd < 0) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, KMEM); + goto error; + } + + processes = psutil_read_process_table(&np); + if (!processes) + goto error; + + /* Loop through processes */ + for (p = processes; np > 0; np--, p++) { + pid = p->pi_pid; + if (requested_pid != -1 && requested_pid != pid) + continue; + if (p->pi_state == 0 || p->pi_state == SZOMB) + continue; + + if (!fds) { + fds = (struct fdsinfo64 *)malloc((size_t)FDSINFOSIZE); + if (!fds) { + PyErr_NoMemory(); + goto error; + } + } + if (getprocs64((struct procentry64 *)NULL, PROCSIZE, fds, FDSINFOSIZE, + &pid, 1) + != 1) + continue; + + /* loop over file descriptors */ + for (i = 0; i < p->pi_maxofile; i++) { + fp = (KA_T)fds->pi_ufd[i].fp; + if (fp) { + py_tuple = process_file(Kd, p->pi_pid, i, fp); + if (py_tuple == NULL) + goto error; + if (py_tuple != NO_SOCKET) { + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + } + } + } + close(Kd); + free(processes); + if (fds != NULL) + free(fds); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (Kd > 0) + close(Kd); + if (processes != NULL) + free(processes); + if (fds != NULL) + free(fds); + return NULL; +} diff --git a/psutil/arch/aix/net_connections.h b/psutil/arch/aix/net_connections.h new file mode 100644 index 000000000..222bcaf35 --- /dev/null +++ b/psutil/arch/aix/net_connections.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __NET_CONNECTIONS_H__ +#define __NET_CONNECTIONS_H__ + +#include + +PyObject* psutil_net_connections(PyObject *self, PyObject *args); + +#endif /* __NET_CONNECTIONS_H__ */ \ No newline at end of file diff --git a/psutil/arch/aix/net_kernel_structs.h b/psutil/arch/aix/net_kernel_structs.h new file mode 100644 index 000000000..09f320ff5 --- /dev/null +++ b/psutil/arch/aix/net_kernel_structs.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* The kernel is always 64 bit but Python is usually compiled as a 32 bit + * process. We're reading the kernel memory to get the network connections, + * so we need the structs we read to be defined with 64 bit "pointers". + * Here are the partial definitions of the structs we use, taken from the + * header files, with data type sizes converted to their 64 bit counterparts, + * and unused data truncated. */ + +#ifdef __64BIT__ +/* In case we're in a 64 bit process after all */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define file64 file +#define socket64 socket +#define protosw64 protosw +#define inpcb64 inpcb +#define tcpcb64 tcpcb +#define unpcb64 unpcb +#define mbuf64 mbuf +#else + struct file64 { + int f_flag; + int f_count; + int f_options; + int f_type; + u_longlong_t f_data; + }; + + struct socket64 { + short so_type; /* generic type, see socket.h */ + short so_options; /* from socket call, see socket.h */ + ushort so_linger; /* time to linger while closing */ + short so_state; /* internal state flags SS_*, below */ + u_longlong_t so_pcb; /* protocol control block */ + u_longlong_t so_proto; /* protocol handle */ + }; + + struct protosw64 { + short pr_type; /* socket type used for */ + u_longlong_t pr_domain; /* domain protocol a member of */ + short pr_protocol; /* protocol number */ + short pr_flags; /* see below */ + }; + + struct inpcb64 { + u_longlong_t inp_next,inp_prev; + /* pointers to other pcb's */ + u_longlong_t inp_head; /* pointer back to chain of inpcb's + for this protocol */ + u_int32_t inp_iflowinfo; /* input flow label */ + u_short inp_fport; /* foreign port */ + u_int16_t inp_fatype; /* foreign address type */ + union in_addr_6 inp_faddr_6; /* foreign host table entry */ + u_int32_t inp_oflowinfo; /* output flow label */ + u_short inp_lport; /* local port */ + u_int16_t inp_latype; /* local address type */ + union in_addr_6 inp_laddr_6; /* local host table entry */ + u_longlong_t inp_socket; /* back pointer to socket */ + u_longlong_t inp_ppcb; /* pointer to per-protocol pcb */ + u_longlong_t space_rt; + struct sockaddr_in6 spare_dst; + u_longlong_t inp_ifa; /* interface address to use */ + int inp_flags; /* generic IP/datagram flags */ +}; + +struct tcpcb64 { + u_longlong_t seg__next; + u_longlong_t seg__prev; + short t_state; /* state of this connection */ +}; + +struct unpcb64 { + u_longlong_t unp_socket; /* pointer back to socket */ + u_longlong_t unp_vnode; /* if associated with file */ + ino_t unp_vno; /* fake vnode number */ + u_longlong_t unp_conn; /* control block of connected socket */ + u_longlong_t unp_refs; /* referencing socket linked list */ + u_longlong_t unp_nextref; /* link in unp_refs list */ + u_longlong_t unp_addr; /* bound address of socket */ +}; + +struct m_hdr64 +{ + u_longlong_t mh_next; /* next buffer in chain */ + u_longlong_t mh_nextpkt; /* next chain in queue/record */ + long mh_len; /* amount of data in this mbuf */ + u_longlong_t mh_data; /* location of data */ +}; + +struct mbuf64 +{ + struct m_hdr64 m_hdr; +}; + +#define m_len m_hdr.mh_len + +#endif \ No newline at end of file diff --git a/psutil/arch/bsd/freebsd_socks.c b/psutil/arch/bsd/freebsd_socks.c deleted file mode 100644 index 826b27f77..000000000 --- a/psutil/arch/bsd/freebsd_socks.c +++ /dev/null @@ -1,631 +0,0 @@ -/* - * Copyright (c) 2009, Giampaolo Rodola'. - * All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include -#include -#include -#include // for struct xsocket -#include -#include -#include -#include -#include // for xinpcb struct -#include -#include -#include -#include -#include -#include // for struct xtcpcb -#include // for TCP connection states -#include // for inet_ntop() -#include -#include - -#include "../../_psutil_common.h" - - -#define HASHSIZE 1009 -// a signaler for connections without an actual status -static int PSUTIL_CONN_NONE = 128; -static struct xfile *psutil_xfiles; -static int psutil_nxfiles; - - -// The tcplist fetching and walking is borrowed from netstat/inet.c. -static char * -psutil_fetch_tcplist(void) { - char *buf; - size_t len; - - for (;;) { - if (sysctlbyname("net.inet.tcp.pcblist", NULL, &len, NULL, 0) < 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - buf = malloc(len); - if (buf == NULL) { - PyErr_NoMemory(); - return NULL; - } - if (sysctlbyname("net.inet.tcp.pcblist", buf, &len, NULL, 0) < 0) { - free(buf); - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - return buf; - } -} - - -static int -psutil_sockaddr_port(int family, struct sockaddr_storage *ss) { - struct sockaddr_in6 *sin6; - struct sockaddr_in *sin; - - if (family == AF_INET) { - sin = (struct sockaddr_in *)ss; - return (sin->sin_port); - } - else { - sin6 = (struct sockaddr_in6 *)ss; - return (sin6->sin6_port); - } -} - - -static void * -psutil_sockaddr_addr(int family, struct sockaddr_storage *ss) { - struct sockaddr_in6 *sin6; - struct sockaddr_in *sin; - - if (family == AF_INET) { - sin = (struct sockaddr_in *)ss; - return (&sin->sin_addr); - } - else { - sin6 = (struct sockaddr_in6 *)ss; - return (&sin6->sin6_addr); - } -} - - -static socklen_t -psutil_sockaddr_addrlen(int family) { - if (family == AF_INET) - return (sizeof(struct in_addr)); - else - return (sizeof(struct in6_addr)); -} - - -static int -psutil_sockaddr_matches(int family, int port, void *pcb_addr, - struct sockaddr_storage *ss) { - if (psutil_sockaddr_port(family, ss) != port) - return (0); - return (memcmp(psutil_sockaddr_addr(family, ss), pcb_addr, - psutil_sockaddr_addrlen(family)) == 0); -} - - -static struct tcpcb * -psutil_search_tcplist(char *buf, struct kinfo_file *kif) { - struct tcpcb *tp; - struct inpcb *inp; - struct xinpgen *xig, *oxig; - struct xsocket *so; - - oxig = xig = (struct xinpgen *)buf; - for (xig = (struct xinpgen *)((char *)xig + xig->xig_len); - xig->xig_len > sizeof(struct xinpgen); - xig = (struct xinpgen *)((char *)xig + xig->xig_len)) { - tp = &((struct xtcpcb *)xig)->xt_tp; - inp = &((struct xtcpcb *)xig)->xt_inp; - so = &((struct xtcpcb *)xig)->xt_socket; - - if (so->so_type != kif->kf_sock_type || - so->xso_family != kif->kf_sock_domain || - so->xso_protocol != kif->kf_sock_protocol) - continue; - - if (kif->kf_sock_domain == AF_INET) { - if (!psutil_sockaddr_matches( - AF_INET, inp->inp_lport, &inp->inp_laddr, - &kif->kf_sa_local)) - continue; - if (!psutil_sockaddr_matches( - AF_INET, inp->inp_fport, &inp->inp_faddr, - &kif->kf_sa_peer)) - continue; - } else { - if (!psutil_sockaddr_matches( - AF_INET6, inp->inp_lport, &inp->in6p_laddr, - &kif->kf_sa_local)) - continue; - if (!psutil_sockaddr_matches( - AF_INET6, inp->inp_fport, &inp->in6p_faddr, - &kif->kf_sa_peer)) - continue; - } - - return (tp); - } - return NULL; -} - - -int -psutil_populate_xfiles() { - size_t len; - - if ((psutil_xfiles = malloc(len = sizeof *psutil_xfiles)) == NULL) { - PyErr_NoMemory(); - return 0; - } - while (sysctlbyname("kern.file", psutil_xfiles, &len, 0, 0) == -1) { - if (errno != ENOMEM) { - PyErr_SetFromErrno(0); - return 0; - } - len *= 2; - if ((psutil_xfiles = realloc(psutil_xfiles, len)) == NULL) { - PyErr_NoMemory(); - return 0; - } - } - if (len > 0 && psutil_xfiles->xf_size != sizeof *psutil_xfiles) { - PyErr_Format(PyExc_RuntimeError, "struct xfile size mismatch"); - return 0; - } - psutil_nxfiles = len / sizeof *psutil_xfiles; - return 1; -} - - -int -psutil_get_pid_from_sock(int sock_hash) { - struct xfile *xf; - int hash, n; - for (xf = psutil_xfiles, n = 0; n < psutil_nxfiles; ++n, ++xf) { - if (xf->xf_data == NULL) - continue; - hash = (int)((uintptr_t)xf->xf_data % HASHSIZE); - if (sock_hash == hash) - return xf->xf_pid; - } - return -1; -} - - -// Reference: -// https://gitorious.org/freebsd/freebsd/source/ -// f1d6f4778d2044502209708bc167c05f9aa48615:usr.bin/sockstat/sockstat.c -int psutil_gather_inet(int proto, PyObject *py_retlist) { - struct xinpgen *xig, *exig; - struct xinpcb *xip; - struct xtcpcb *xtp; - struct inpcb *inp; - struct xsocket *so; - const char *varname = NULL; - size_t len, bufsize; - void *buf; - int hash; - int retry; - int type; - - PyObject *py_tuple = NULL; - PyObject *py_laddr = NULL; - PyObject *py_raddr = NULL; - - switch (proto) { - case IPPROTO_TCP: - varname = "net.inet.tcp.pcblist"; - type = SOCK_STREAM; - break; - case IPPROTO_UDP: - varname = "net.inet.udp.pcblist"; - type = SOCK_DGRAM; - break; - } - - buf = NULL; - bufsize = 8192; - retry = 5; - do { - for (;;) { - buf = realloc(buf, bufsize); - if (buf == NULL) - continue; // XXX - len = bufsize; - if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) - break; - if (errno != ENOMEM) { - PyErr_SetFromErrno(0); - goto error; - } - bufsize *= 2; - } - xig = (struct xinpgen *)buf; - exig = (struct xinpgen *)(void *)((char *)buf + len - sizeof *exig); - if (xig->xig_len != sizeof *xig || exig->xig_len != sizeof *exig) { - PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); - goto error; - } - } while (xig->xig_gen != exig->xig_gen && retry--); - - for (;;) { - int lport, rport, pid, status, family; - - xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len); - if (xig >= exig) - break; - - switch (proto) { - case IPPROTO_TCP: - xtp = (struct xtcpcb *)xig; - if (xtp->xt_len != sizeof *xtp) { - PyErr_Format(PyExc_RuntimeError, - "struct xtcpcb size mismatch"); - goto error; - } - inp = &xtp->xt_inp; - so = &xtp->xt_socket; - status = xtp->xt_tp.t_state; - break; - case IPPROTO_UDP: - xip = (struct xinpcb *)xig; - if (xip->xi_len != sizeof *xip) { - PyErr_Format(PyExc_RuntimeError, - "struct xinpcb size mismatch"); - goto error; - } - inp = &xip->xi_inp; - so = &xip->xi_socket; - status = PSUTIL_CONN_NONE; - break; - default: - PyErr_Format(PyExc_RuntimeError, "invalid proto"); - goto error; - } - - char lip[200], rip[200]; - - hash = (int)((uintptr_t)so->xso_so % HASHSIZE); - pid = psutil_get_pid_from_sock(hash); - if (pid < 0) - continue; - lport = ntohs(inp->inp_lport); - rport = ntohs(inp->inp_fport); - - if (inp->inp_vflag & INP_IPV4) { - family = AF_INET; - inet_ntop(AF_INET, &inp->inp_laddr.s_addr, lip, sizeof(lip)); - inet_ntop(AF_INET, &inp->inp_faddr.s_addr, rip, sizeof(rip)); - } - else if (inp->inp_vflag & INP_IPV6) { - family = AF_INET6; - inet_ntop(AF_INET6, &inp->in6p_laddr.s6_addr, lip, sizeof(lip)); - inet_ntop(AF_INET6, &inp->in6p_faddr.s6_addr, rip, sizeof(rip)); - } - - // construct python tuple/list - py_laddr = Py_BuildValue("(si)", lip, lport); - if (!py_laddr) - goto error; - if (rport != 0) - py_raddr = Py_BuildValue("(si)", rip, rport); - else - py_raddr = Py_BuildValue("()"); - if (!py_raddr) - goto error; - py_tuple = Py_BuildValue("(iiiNNii)", -1, family, type, py_laddr, - py_raddr, status, pid); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_DECREF(py_tuple); - } - - free(buf); - return 1; - -error: - Py_XDECREF(py_tuple); - Py_XDECREF(py_laddr); - Py_XDECREF(py_raddr); - free(buf); - return 0; -} - - -int psutil_gather_unix(int proto, PyObject *py_retlist) { - struct xunpgen *xug, *exug; - struct xunpcb *xup; - const char *varname = NULL; - const char *protoname = NULL; - size_t len; - size_t bufsize; - void *buf; - int hash; - int retry; - int pid; - struct sockaddr_un *sun; - char path[PATH_MAX]; - - PyObject *py_tuple = NULL; - PyObject *py_laddr = NULL; - PyObject *py_raddr = NULL; - - switch (proto) { - case SOCK_STREAM: - varname = "net.local.stream.pcblist"; - protoname = "stream"; - break; - case SOCK_DGRAM: - varname = "net.local.dgram.pcblist"; - protoname = "dgram"; - break; - } - - buf = NULL; - bufsize = 8192; - retry = 5; - - do { - for (;;) { - buf = realloc(buf, bufsize); - if (buf == NULL) { - PyErr_NoMemory(); - goto error; - } - len = bufsize; - if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) - break; - if (errno != ENOMEM) { - PyErr_SetFromErrno(0); - goto error; - } - bufsize *= 2; - } - xug = (struct xunpgen *)buf; - exug = (struct xunpgen *)(void *) - ((char *)buf + len - sizeof *exug); - if (xug->xug_len != sizeof *xug || exug->xug_len != sizeof *exug) { - PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); - goto error; - } - } while (xug->xug_gen != exug->xug_gen && retry--); - - for (;;) { - xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len); - if (xug >= exug) - break; - xup = (struct xunpcb *)xug; - if (xup->xu_len != sizeof *xup) - goto error; - - hash = (int)((uintptr_t) xup->xu_socket.xso_so % HASHSIZE); - pid = psutil_get_pid_from_sock(hash); - if (pid < 0) - continue; - - sun = (struct sockaddr_un *)&xup->xu_addr; - snprintf(path, sizeof(path), "%.*s", - (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), - sun->sun_path); - - py_tuple = Py_BuildValue("(iiisOii)", -1, AF_UNIX, proto, path, - Py_None, PSUTIL_CONN_NONE, pid); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_DECREF(py_tuple); - Py_INCREF(Py_None); - } - - free(buf); - return 1; - -error: - Py_XDECREF(py_tuple); - Py_XDECREF(py_laddr); - Py_XDECREF(py_raddr); - free(buf); - return 0; -} - - -PyObject* -psutil_net_connections(PyObject* self, PyObject* args) { - // Return system-wide open connections. - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; - if (psutil_populate_xfiles() != 1) - goto error; - if (psutil_gather_inet(IPPROTO_TCP, py_retlist) == 0) - goto error; - if (psutil_gather_inet(IPPROTO_UDP, py_retlist) == 0) - goto error; - if (psutil_gather_unix(SOCK_STREAM, py_retlist) == 0) - goto error; - if (psutil_gather_unix(SOCK_DGRAM, py_retlist) == 0) - goto error; - - free(psutil_xfiles); - return py_retlist; - -error: - Py_DECREF(py_retlist); - free(psutil_xfiles); - return NULL; -} - - -PyObject * -psutil_proc_connections(PyObject *self, PyObject *args) { - // Return connections opened by process. - long pid; - int i, cnt; - struct kinfo_file *freep = NULL; - struct kinfo_file *kif; - char *tcplist = NULL; - struct tcpcb *tcp; - - PyObject *py_retlist = PyList_New(0); - PyObject *py_tuple = NULL; - PyObject *py_laddr = NULL; - PyObject *py_raddr = NULL; - PyObject *py_af_filter = NULL; - PyObject *py_type_filter = NULL; - PyObject *py_family = NULL; - PyObject *py_type = NULL; - - if (py_retlist == NULL) - return NULL; - if (! PyArg_ParseTuple(args, "lOO", &pid, &py_af_filter, &py_type_filter)) - goto error; - if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { - PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); - goto error; - } - - errno = 0; - freep = kinfo_getfile(pid, &cnt); - if (freep == NULL) { - psutil_raise_for_pid(pid, "kinfo_getfile() failed"); - goto error; - } - - tcplist = psutil_fetch_tcplist(); - if (tcplist == NULL) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - for (i = 0; i < cnt; i++) { - int lport, rport, state; - char lip[200], rip[200]; - char path[PATH_MAX]; - int inseq; - py_tuple = NULL; - py_laddr = NULL; - py_raddr = NULL; - - kif = &freep[i]; - if (kif->kf_type == KF_TYPE_SOCKET) { - // apply filters - py_family = PyLong_FromLong((long)kif->kf_sock_domain); - inseq = PySequence_Contains(py_af_filter, py_family); - Py_DECREF(py_family); - if (inseq == 0) - continue; - py_type = PyLong_FromLong((long)kif->kf_sock_type); - inseq = PySequence_Contains(py_type_filter, py_type); - Py_DECREF(py_type); - if (inseq == 0) - continue; - // IPv4 / IPv6 socket - if ((kif->kf_sock_domain == AF_INET) || - (kif->kf_sock_domain == AF_INET6)) { - // fill status - state = PSUTIL_CONN_NONE; - if (kif->kf_sock_type == SOCK_STREAM) { - tcp = psutil_search_tcplist(tcplist, kif); - if (tcp != NULL) - state = (int)tcp->t_state; - } - - // build addr and port - inet_ntop( - kif->kf_sock_domain, - psutil_sockaddr_addr(kif->kf_sock_domain, - &kif->kf_sa_local), - lip, - sizeof(lip)); - inet_ntop( - kif->kf_sock_domain, - psutil_sockaddr_addr(kif->kf_sock_domain, - &kif->kf_sa_peer), - rip, - sizeof(rip)); - lport = htons(psutil_sockaddr_port(kif->kf_sock_domain, - &kif->kf_sa_local)); - rport = htons(psutil_sockaddr_port(kif->kf_sock_domain, - &kif->kf_sa_peer)); - - // construct python tuple/list - py_laddr = Py_BuildValue("(si)", lip, lport); - if (!py_laddr) - goto error; - if (rport != 0) - py_raddr = Py_BuildValue("(si)", rip, rport); - else - py_raddr = Py_BuildValue("()"); - if (!py_raddr) - goto error; - py_tuple = Py_BuildValue( - "(iiiNNi)", - kif->kf_fd, - kif->kf_sock_domain, - kif->kf_sock_type, - py_laddr, - py_raddr, - state - ); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_DECREF(py_tuple); - } - // UNIX socket - else if (kif->kf_sock_domain == AF_UNIX) { - struct sockaddr_un *sun; - - sun = (struct sockaddr_un *)&kif->kf_sa_local; - snprintf( - path, sizeof(path), "%.*s", - (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), - sun->sun_path); - - py_tuple = Py_BuildValue( - "(iiisOi)", - kif->kf_fd, - kif->kf_sock_domain, - kif->kf_sock_type, - path, - Py_None, - PSUTIL_CONN_NONE - ); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_DECREF(py_tuple); - Py_INCREF(Py_None); - } - } - } - free(freep); - free(tcplist); - return py_retlist; - -error: - Py_XDECREF(py_tuple); - Py_XDECREF(py_laddr); - Py_XDECREF(py_raddr); - Py_DECREF(py_retlist); - if (freep != NULL) - free(freep); - if (tcplist != NULL) - free(tcplist); - return NULL; -} diff --git a/psutil/arch/freebsd/proc_socks.c b/psutil/arch/freebsd/proc_socks.c new file mode 100644 index 000000000..a458a01e5 --- /dev/null +++ b/psutil/arch/freebsd/proc_socks.c @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Retrieves per-process open socket connections. + */ + +#include +#include +#include // for struct xsocket +#include +#include +#include // for xinpcb struct +#include +#include // for struct xtcpcb +#include // for inet_ntop() +#include + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + + +// The tcplist fetching and walking is borrowed from netstat/inet.c. +static char * +psutil_fetch_tcplist(void) { + char *buf; + size_t len; + + for (;;) { + if (sysctlbyname("net.inet.tcp.pcblist", NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + buf = malloc(len); + if (buf == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (sysctlbyname("net.inet.tcp.pcblist", buf, &len, NULL, 0) < 0) { + free(buf); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return buf; + } +} + + +static int +psutil_sockaddr_port(int family, struct sockaddr_storage *ss) { + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (family == AF_INET) { + sin = (struct sockaddr_in *)ss; + return (sin->sin_port); + } + else { + sin6 = (struct sockaddr_in6 *)ss; + return (sin6->sin6_port); + } +} + + +static void * +psutil_sockaddr_addr(int family, struct sockaddr_storage *ss) { + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (family == AF_INET) { + sin = (struct sockaddr_in *)ss; + return (&sin->sin_addr); + } + else { + sin6 = (struct sockaddr_in6 *)ss; + return (&sin6->sin6_addr); + } +} + + +static socklen_t +psutil_sockaddr_addrlen(int family) { + if (family == AF_INET) + return (sizeof(struct in_addr)); + else + return (sizeof(struct in6_addr)); +} + + +static int +psutil_sockaddr_matches(int family, int port, void *pcb_addr, + struct sockaddr_storage *ss) { + if (psutil_sockaddr_port(family, ss) != port) + return (0); + return (memcmp(psutil_sockaddr_addr(family, ss), pcb_addr, + psutil_sockaddr_addrlen(family)) == 0); +} + + +#if __FreeBSD_version >= 1200026 +static struct xtcpcb * +psutil_search_tcplist(char *buf, struct kinfo_file *kif) { + struct xtcpcb *tp; + struct xinpcb *inp; +#else +static struct tcpcb * +psutil_search_tcplist(char *buf, struct kinfo_file *kif) { + struct tcpcb *tp; + struct inpcb *inp; +#endif + struct xinpgen *xig, *oxig; + struct xsocket *so; + + oxig = xig = (struct xinpgen *)buf; + for (xig = (struct xinpgen *)((char *)xig + xig->xig_len); + xig->xig_len > sizeof(struct xinpgen); + xig = (struct xinpgen *)((char *)xig + xig->xig_len)) { + +#if __FreeBSD_version >= 1200026 + tp = (struct xtcpcb *)xig; + inp = &tp->xt_inp; + so = &inp->xi_socket; +#else + tp = &((struct xtcpcb *)xig)->xt_tp; + inp = &((struct xtcpcb *)xig)->xt_inp; + so = &((struct xtcpcb *)xig)->xt_socket; +#endif + + if (so->so_type != kif->kf_sock_type || + so->xso_family != kif->kf_sock_domain || + so->xso_protocol != kif->kf_sock_protocol) + continue; + + if (kif->kf_sock_domain == AF_INET) { + if (!psutil_sockaddr_matches( + AF_INET, inp->inp_lport, &inp->inp_laddr, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_local)) +#else + &kif->kf_un.kf_sock.kf_sa_local)) +#endif + continue; + if (!psutil_sockaddr_matches( + AF_INET, inp->inp_fport, &inp->inp_faddr, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_peer)) +#else + &kif->kf_un.kf_sock.kf_sa_peer)) +#endif + continue; + } else { + if (!psutil_sockaddr_matches( + AF_INET6, inp->inp_lport, &inp->in6p_laddr, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_local)) +#else + &kif->kf_un.kf_sock.kf_sa_local)) +#endif + continue; + if (!psutil_sockaddr_matches( + AF_INET6, inp->inp_fport, &inp->in6p_faddr, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_peer)) +#else + &kif->kf_un.kf_sock.kf_sa_peer)) +#endif + continue; + } + + return (tp); + } + return NULL; +} + + + +PyObject * +psutil_proc_connections(PyObject *self, PyObject *args) { + // Return connections opened by process. + long pid; + int i; + int cnt; + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + char *tcplist = NULL; +#if __FreeBSD_version >= 1200026 + struct xtcpcb *tcp; +#else + struct tcpcb *tcp; +#endif + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + PyObject *py_af_filter = NULL; + PyObject *py_type_filter = NULL; + PyObject *py_family = NULL; + PyObject *py_type = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "lOO", &pid, &py_af_filter, &py_type_filter)) + goto error; + if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + goto error; + } + + errno = 0; + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_for_pid(pid, "kinfo_getfile()"); + goto error; + } + + tcplist = psutil_fetch_tcplist(); + if (tcplist == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < cnt; i++) { + int lport, rport, state; + char lip[200], rip[200]; + char path[PATH_MAX]; + int inseq; + py_tuple = NULL; + py_laddr = NULL; + py_raddr = NULL; + + kif = &freep[i]; + if (kif->kf_type == KF_TYPE_SOCKET) { + // apply filters + py_family = PyLong_FromLong((long)kif->kf_sock_domain); + inseq = PySequence_Contains(py_af_filter, py_family); + Py_DECREF(py_family); + if (inseq == 0) + continue; + py_type = PyLong_FromLong((long)kif->kf_sock_type); + inseq = PySequence_Contains(py_type_filter, py_type); + Py_DECREF(py_type); + if (inseq == 0) + continue; + // IPv4 / IPv6 socket + if ((kif->kf_sock_domain == AF_INET) || + (kif->kf_sock_domain == AF_INET6)) { + // fill status + state = PSUTIL_CONN_NONE; + if (kif->kf_sock_type == SOCK_STREAM) { + tcp = psutil_search_tcplist(tcplist, kif); + if (tcp != NULL) + state = (int)tcp->t_state; + } + + // build addr and port + inet_ntop( + kif->kf_sock_domain, + psutil_sockaddr_addr(kif->kf_sock_domain, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_local), +#else + &kif->kf_un.kf_sock.kf_sa_local), +#endif + lip, + sizeof(lip)); + inet_ntop( + kif->kf_sock_domain, + psutil_sockaddr_addr(kif->kf_sock_domain, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_peer), +#else + &kif->kf_un.kf_sock.kf_sa_peer), +#endif + rip, + sizeof(rip)); + lport = htons(psutil_sockaddr_port(kif->kf_sock_domain, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_local)); +#else + &kif->kf_un.kf_sock.kf_sa_local)); +#endif + rport = htons(psutil_sockaddr_port(kif->kf_sock_domain, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_peer)); +#else + &kif->kf_un.kf_sock.kf_sa_peer)); +#endif + + // construct python tuple/list + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue( + "(iiiNNi)", + kif->kf_fd, + kif->kf_sock_domain, + kif->kf_sock_type, + py_laddr, + py_raddr, + state + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + // UNIX socket. + // Note: remote path cannot be determined. + else if (kif->kf_sock_domain == AF_UNIX) { + struct sockaddr_un *sun; + +#if __FreeBSD_version < 1200031 + sun = (struct sockaddr_un *)&kif->kf_sa_local; +#else + sun = (struct sockaddr_un *)&kif->kf_un.kf_sock.kf_sa_local; +#endif + snprintf( + path, sizeof(path), "%.*s", + (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), + sun->sun_path); + + py_laddr = PyUnicode_DecodeFSDefault(path); + if (! py_laddr) + goto error; + + py_tuple = Py_BuildValue( + "(iiiOsi)", + kif->kf_fd, + kif->kf_sock_domain, + kif->kf_sock_type, + py_laddr, + "", // raddr can't be determined + PSUTIL_CONN_NONE + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_DECREF(py_laddr); + } + } + } + free(freep); + free(tcplist); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + Py_DECREF(py_retlist); + if (freep != NULL) + free(freep); + if (tcplist != NULL) + free(tcplist); + return NULL; +} diff --git a/psutil/arch/freebsd/proc_socks.h b/psutil/arch/freebsd/proc_socks.h new file mode 100644 index 000000000..a7996b107 --- /dev/null +++ b/psutil/arch/freebsd/proc_socks.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +PyObject* psutil_proc_connections(PyObject* self, PyObject* args); diff --git a/psutil/arch/bsd/freebsd.c b/psutil/arch/freebsd/specific.c similarity index 92% rename from psutil/arch/bsd/freebsd.c rename to psutil/arch/freebsd/specific.c index 456a50aa4..2c8944ddd 100644 --- a/psutil/arch/bsd/freebsd.c +++ b/psutil/arch/freebsd/specific.c @@ -3,7 +3,7 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * - * Helper functions related to fetching process information. + * Helper functions specific to FreeBSD. * Used by _psutil_bsd module methods. */ @@ -26,7 +26,7 @@ #include #include "../../_psutil_common.h" - +#include "../../_psutil_posix.h" #define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) #define PSUTIL_BT2MSEC(bt) (bt.sec * 1000 + (((uint64_t) 1000000000 * (uint32_t) \ @@ -59,7 +59,7 @@ psutil_kinfo_proc(const pid_t pid, struct kinfo_proc *proc) { // sysctl stores 0 in the size if we can't find the process information. if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); return -1; } return 0; @@ -179,7 +179,8 @@ psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount) { */ static char *psutil_get_cmd_args(long pid, size_t *argsize) { - int mib[4], argmax; + int mib[4]; + int argmax; size_t size = sizeof(argmax); char *procargs = NULL; @@ -198,9 +199,7 @@ static char return NULL; } - /* - * Make a sysctl() call to get the raw argument space of the process. - */ + // Make a sysctl() call to get the raw argument space of the process. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_ARGS; @@ -209,7 +208,8 @@ static char size = argmax; if (sysctl(mib, 4, procargs, &size, NULL, 0) == -1) { free(procargs); - return NULL; // Insufficient privileges + PyErr_SetFromErrno(PyExc_OSError); + return NULL; } // return string and set the length of arguments @@ -222,7 +222,7 @@ static char PyObject * psutil_get_cmdline(long pid) { char *argstr = NULL; - int pos = 0; + size_t pos = 0; size_t argsize = 0; PyObject *py_retlist = Py_BuildValue("[]"); PyObject *py_arg = NULL; @@ -238,11 +238,7 @@ psutil_get_cmdline(long pid) { // separator if (argsize > 0) { while (pos < argsize) { -#if PY_MAJOR_VERSION >= 3 py_arg = PyUnicode_DecodeFSDefault(&argstr[pos]); -#else - py_arg = Py_BuildValue("s", &argstr[pos]); -#endif if (!py_arg) goto error; if (PyList_Append(py_retlist, py_arg)) @@ -290,31 +286,23 @@ psutil_proc_exe(PyObject *self, PyObject *args) { size = sizeof(pathname); error = sysctl(mib, 4, pathname, &size, NULL, 0); if (error == -1) { - if (errno == ENOENT) { - // see: https://github.com/giampaolo/psutil/issues/907 - return Py_BuildValue("s", ""); - } - else { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } + // see: https://github.com/giampaolo/psutil/issues/907 + if (errno == ENOENT) + return PyUnicode_DecodeFSDefault(""); + else + return PyErr_SetFromErrno(PyExc_OSError); } if (size == 0 || strlen(pathname) == 0) { ret = psutil_pid_exists(pid); if (ret == -1) return NULL; else if (ret == 0) - return NoSuchProcess(); + return NoSuchProcess(""); else strcpy(pathname, ""); } -#if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeFSDefault(pathname); -#else - return Py_BuildValue("s", pathname); -#endif - } @@ -336,8 +324,8 @@ psutil_proc_threads(PyObject *self, PyObject *args) { // Retrieves all threads used by process returning a list of tuples // including thread id, user time and system time. // Thanks to Robert N. M. Watson: - // http://fxr.googlebit.com/source/usr.bin/procstat/ - // procstat_threads.c?v=8-CURRENT + // http://code.metager.de/source/xref/freebsd/usr.bin/procstat/ + // procstat_threads.c long pid; int mib[4]; struct kinfo_proc *kip = NULL; @@ -366,7 +354,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { goto error; } if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); goto error; } @@ -382,7 +370,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { goto error; } if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); goto error; } @@ -468,8 +456,9 @@ psutil_virtual_mem(PyObject *self, PyObject *args) { goto error; if (sysctlbyname("vm.stats.vm.v_wire_count", &wired, &size, NULL, 0)) goto error; + // https://github.com/giampaolo/psutil/issues/997 if (sysctlbyname("vm.stats.vm.v_cache_count", &cached, &size, NULL, 0)) - goto error; + cached = 0; if (sysctlbyname("vm.stats.vm.v_free_count", &free, &size, NULL, 0)) goto error; if (sysctlbyname("vfs.bufspace", &buffers, &buffers_size, NULL, 0)) @@ -491,8 +480,7 @@ psutil_virtual_mem(PyObject *self, PyObject *args) { ); error: - PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return PyErr_SetFromErrno(PyExc_OSError); } @@ -520,13 +508,13 @@ psutil_swap_mem(PyObject *self, PyObject *args) { kvm_close(kd); if (sysctlbyname("vm.stats.vm.v_swapin", &swapin, &size, NULL, 0) == -1) - goto sbn_error; + goto error; if (sysctlbyname("vm.stats.vm.v_swapout", &swapout, &size, NULL, 0) == -1) - goto sbn_error; + goto error; if (sysctlbyname("vm.stats.vm.v_vnodein", &nodein, &size, NULL, 0) == -1) - goto sbn_error; + goto error; if (sysctlbyname("vm.stats.vm.v_vnodeout", &nodeout, &size, NULL, 0) == -1) - goto sbn_error; + goto error; return Py_BuildValue("(iiiII)", kvmsw[0].ksw_total, // total @@ -535,9 +523,8 @@ psutil_swap_mem(PyObject *self, PyObject *args) { swapin + swapout, // swap in nodein + nodeout); // swap out -sbn_error: - PyErr_SetFromErrno(PyExc_OSError); - return NULL; +error: + return PyErr_SetFromErrno(PyExc_OSError); } @@ -561,18 +548,14 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { errno = 0; freep = kinfo_getfile(pid, &cnt); if (freep == NULL) { - psutil_raise_for_pid(pid, "kinfo_getfile() failed"); + psutil_raise_for_pid(pid, "kinfo_getfile()"); goto error; } for (i = 0; i < cnt; i++) { kif = &freep[i]; if (kif->kf_fd == KF_FD_TYPE_CWD) { -#if PY_MAJOR_VERSION >= 3 py_path = PyUnicode_DecodeFSDefault(kif->kf_path); -#else - py_path = Py_BuildValue("s", kif->kf_path); -#endif if (!py_path) goto error; break; @@ -584,7 +567,7 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { * as root we return an empty string instead of AccessDenied. */ if (py_path == NULL) - py_path = Py_BuildValue("s", ""); + py_path = PyUnicode_DecodeFSDefault(""); free(freep); return py_path; @@ -614,7 +597,7 @@ psutil_proc_num_fds(PyObject *self, PyObject *args) { errno = 0; freep = kinfo_getfile(pid, &cnt); if (freep == NULL) { - psutil_raise_for_pid(pid, "kinfo_getfile() failed"); + psutil_raise_for_pid(pid, "kinfo_getfile()"); return NULL; } free(freep); @@ -642,8 +625,7 @@ psutil_per_cpu_times(PyObject *self, PyObject *args) { size = sizeof(maxcpus); if (sysctlbyname("kern.smp.maxcpus", &maxcpus, &size, NULL, 0) < 0) { Py_DECREF(py_retlist); - PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return PyErr_SetFromErrno(PyExc_OSError); } long cpu_time[maxcpus][CPUSTATES]; @@ -764,12 +746,13 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { int i, cnt; char addr[1000]; char perms[4]; - const char *path; + char *path; struct kinfo_proc kp; struct kinfo_vmentry *freep = NULL; struct kinfo_vmentry *kve; ptrwidth = 2 * sizeof(void *); PyObject *py_tuple = NULL; + PyObject *py_path = NULL; PyObject *py_retlist = PyList_New(0); if (py_retlist == NULL) @@ -782,7 +765,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { errno = 0; freep = kinfo_getvmmap(pid, &cnt); if (freep == NULL) { - psutil_raise_for_pid(pid, "kinfo_getvmmap() failed"); + psutil_raise_for_pid(pid, "kinfo_getvmmap()"); goto error; } for (i = 0; i < cnt; i++) { @@ -838,10 +821,13 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { path = kve->kve_path; } - py_tuple = Py_BuildValue("sssiiii", + py_path = PyUnicode_DecodeFSDefault(path); + if (! py_path) + goto error; + py_tuple = Py_BuildValue("ssOiiii", addr, // "start-end" address perms, // "rwx" permissions - path, // path + py_path, // path kve->kve_resident, // rss kve->kve_private_resident, // private kve->kve_ref_count, // ref count @@ -850,6 +836,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_path); Py_DECREF(py_tuple); } free(freep); @@ -857,6 +844,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { error: Py_XDECREF(py_tuple); + Py_XDECREF(py_path); Py_DECREF(py_retlist); if (freep != NULL) free(freep); @@ -880,10 +868,8 @@ psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args) { return NULL; ret = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid, sizeof(mask), &mask); - if (ret != 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } + if (ret != 0) + return PyErr_SetFromErrno(PyExc_OSError); py_retlist = PyList_New(0); if (py_retlist == NULL) @@ -991,6 +977,33 @@ psutil_cpu_stats(PyObject *self, PyObject *args) { ); error: - PyErr_SetFromErrno(PyExc_OSError); + return PyErr_SetFromErrno(PyExc_OSError); +} + + +/* + * Return battery information. + */ +PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + int percent; + int minsleft; + int power_plugged; + size_t size = sizeof(percent); + + if (sysctlbyname("hw.acpi.battery.life", &percent, &size, NULL, 0)) + goto error; + if (sysctlbyname("hw.acpi.battery.time", &minsleft, &size, NULL, 0)) + goto error; + if (sysctlbyname("hw.acpi.acline", &power_plugged, &size, NULL, 0)) + goto error; + return Py_BuildValue("iii", percent, minsleft, power_plugged); + +error: + // see: https://github.com/giampaolo/psutil/issues/1074 + if (errno == ENOENT) + PyErr_SetString(PyExc_NotImplementedError, "no battery"); + else + PyErr_SetFromErrno(PyExc_OSError); return NULL; } diff --git a/psutil/arch/bsd/freebsd.h b/psutil/arch/freebsd/specific.h similarity index 92% rename from psutil/arch/bsd/freebsd.h rename to psutil/arch/freebsd/specific.h index e15706c66..0df66eccb 100644 --- a/psutil/arch/bsd/freebsd.h +++ b/psutil/arch/freebsd/specific.h @@ -27,3 +27,6 @@ PyObject* psutil_proc_threads(PyObject* self, PyObject* args); PyObject* psutil_swap_mem(PyObject* self, PyObject* args); PyObject* psutil_virtual_mem(PyObject* self, PyObject* args); PyObject* psutil_cpu_stats(PyObject* self, PyObject* args); +#if defined(PSUTIL_FREEBSD) +PyObject* psutil_sensors_battery(PyObject* self, PyObject* args); +#endif diff --git a/psutil/arch/freebsd/sys_socks.c b/psutil/arch/freebsd/sys_socks.c new file mode 100644 index 000000000..e0e2046be --- /dev/null +++ b/psutil/arch/freebsd/sys_socks.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Retrieves system-wide open socket connections. This is based off of + * sockstat utility source code: + * https://github.com/freebsd/freebsd/blob/master/usr.bin/sockstat/sockstat.c + */ + +#include +#include +#include +#include // for struct xsocket +#include +#include +#include +#include // for xinpcb struct +#include +#include +#include // for struct xtcpcb +#include // for inet_ntop() + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + +static struct xfile *psutil_xfiles; +static int psutil_nxfiles; + + +int +psutil_populate_xfiles() { + size_t len; + + if ((psutil_xfiles = malloc(len = sizeof *psutil_xfiles)) == NULL) { + PyErr_NoMemory(); + return 0; + } + while (sysctlbyname("kern.file", psutil_xfiles, &len, 0, 0) == -1) { + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + return 0; + } + len *= 2; + if ((psutil_xfiles = realloc(psutil_xfiles, len)) == NULL) { + PyErr_NoMemory(); + return 0; + } + } + if (len > 0 && psutil_xfiles->xf_size != sizeof *psutil_xfiles) { + PyErr_Format(PyExc_RuntimeError, "struct xfile size mismatch"); + return 0; + } + psutil_nxfiles = len / sizeof *psutil_xfiles; + return 1; +} + + +struct xfile * +psutil_get_file_from_sock(void *sock) { + struct xfile *xf; + int n; + + for (xf = psutil_xfiles, n = 0; n < psutil_nxfiles; ++n, ++xf) { + if (xf->xf_data == sock) + return xf; + } + return NULL; +} + + +// Reference: +// https://github.com/freebsd/freebsd/blob/master/usr.bin/sockstat/sockstat.c +int psutil_gather_inet(int proto, PyObject *py_retlist) { + struct xinpgen *xig, *exig; + struct xinpcb *xip; + struct xtcpcb *xtp; +#if __FreeBSD_version >= 1200026 + struct xinpcb *inp; +#else + struct inpcb *inp; +#endif + struct xsocket *so; + const char *varname = NULL; + size_t len, bufsize; + void *buf; + int retry; + int type; + + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + + switch (proto) { + case IPPROTO_TCP: + varname = "net.inet.tcp.pcblist"; + type = SOCK_STREAM; + break; + case IPPROTO_UDP: + varname = "net.inet.udp.pcblist"; + type = SOCK_DGRAM; + break; + } + + buf = NULL; + bufsize = 8192; + retry = 5; + do { + for (;;) { + buf = realloc(buf, bufsize); + if (buf == NULL) + continue; // XXX + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + goto error; + } + bufsize *= 2; + } + xig = (struct xinpgen *)buf; + exig = (struct xinpgen *)(void *)((char *)buf + len - sizeof *exig); + if (xig->xig_len != sizeof *xig || exig->xig_len != sizeof *exig) { + PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); + goto error; + } + } while (xig->xig_gen != exig->xig_gen && retry--); + + for (;;) { + struct xfile *xf; + int lport, rport, status, family; + + xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len); + if (xig >= exig) + break; + + switch (proto) { + case IPPROTO_TCP: + xtp = (struct xtcpcb *)xig; + if (xtp->xt_len != sizeof *xtp) { + PyErr_Format(PyExc_RuntimeError, + "struct xtcpcb size mismatch"); + goto error; + } + inp = &xtp->xt_inp; +#if __FreeBSD_version >= 1200026 + so = &inp->xi_socket; + status = xtp->t_state; +#else + so = &xtp->xt_socket; + status = xtp->xt_tp.t_state; +#endif + break; + case IPPROTO_UDP: + xip = (struct xinpcb *)xig; + if (xip->xi_len != sizeof *xip) { + PyErr_Format(PyExc_RuntimeError, + "struct xinpcb size mismatch"); + goto error; + } +#if __FreeBSD_version >= 1200026 + inp = xip; +#else + inp = &xip->xi_inp; +#endif + so = &xip->xi_socket; + status = PSUTIL_CONN_NONE; + break; + default: + PyErr_Format(PyExc_RuntimeError, "invalid proto"); + goto error; + } + + char lip[200], rip[200]; + + xf = psutil_get_file_from_sock(so->xso_so); + if (xf == NULL) + continue; + lport = ntohs(inp->inp_lport); + rport = ntohs(inp->inp_fport); + + if (inp->inp_vflag & INP_IPV4) { + family = AF_INET; + inet_ntop(AF_INET, &inp->inp_laddr.s_addr, lip, sizeof(lip)); + inet_ntop(AF_INET, &inp->inp_faddr.s_addr, rip, sizeof(rip)); + } + else if (inp->inp_vflag & INP_IPV6) { + family = AF_INET6; + inet_ntop(AF_INET6, &inp->in6p_laddr.s6_addr, lip, sizeof(lip)); + inet_ntop(AF_INET6, &inp->in6p_faddr.s6_addr, rip, sizeof(rip)); + } + + // construct python tuple/list + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue( + "(iiiNNii)", + xf->xf_fd, // fd + family, // family + type, // type + py_laddr, // laddr + py_raddr, // raddr + status, // status + xf->xf_pid); // pid + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + + free(buf); + return 1; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + free(buf); + return 0; +} + + +int psutil_gather_unix(int proto, PyObject *py_retlist) { + struct xunpgen *xug, *exug; + struct xunpcb *xup; + const char *varname = NULL; + const char *protoname = NULL; + size_t len; + size_t bufsize; + void *buf; + int retry; + struct sockaddr_un *sun; + char path[PATH_MAX]; + + PyObject *py_tuple = NULL; + PyObject *py_lpath = NULL; + + switch (proto) { + case SOCK_STREAM: + varname = "net.local.stream.pcblist"; + protoname = "stream"; + break; + case SOCK_DGRAM: + varname = "net.local.dgram.pcblist"; + protoname = "dgram"; + break; + } + + buf = NULL; + bufsize = 8192; + retry = 5; + + do { + for (;;) { + buf = realloc(buf, bufsize); + if (buf == NULL) { + PyErr_NoMemory(); + goto error; + } + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + goto error; + } + bufsize *= 2; + } + xug = (struct xunpgen *)buf; + exug = (struct xunpgen *)(void *) + ((char *)buf + len - sizeof *exug); + if (xug->xug_len != sizeof *xug || exug->xug_len != sizeof *exug) { + PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); + goto error; + } + } while (xug->xug_gen != exug->xug_gen && retry--); + + for (;;) { + struct xfile *xf; + + xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len); + if (xug >= exug) + break; + xup = (struct xunpcb *)xug; + if (xup->xu_len != sizeof *xup) + goto error; + + xf = psutil_get_file_from_sock(xup->xu_socket.xso_so); + if (xf == NULL) + continue; + + sun = (struct sockaddr_un *)&xup->xu_addr; + snprintf(path, sizeof(path), "%.*s", + (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), + sun->sun_path); + py_lpath = PyUnicode_DecodeFSDefault(path); + if (! py_lpath) + goto error; + + py_tuple = Py_BuildValue("(iiiOsii)", + xf->xf_fd, // fd + AF_UNIX, // family + proto, // type + py_lpath, // lpath + "", // rath + PSUTIL_CONN_NONE, // status + xf->xf_pid); // pid + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_lpath); + Py_DECREF(py_tuple); + } + + free(buf); + return 1; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_lpath); + free(buf); + return 0; +} + + +PyObject* +psutil_net_connections(PyObject* self, PyObject* args) { + // Return system-wide open connections. + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (psutil_populate_xfiles() != 1) + goto error; + if (psutil_gather_inet(IPPROTO_TCP, py_retlist) == 0) + goto error; + if (psutil_gather_inet(IPPROTO_UDP, py_retlist) == 0) + goto error; + if (psutil_gather_unix(SOCK_STREAM, py_retlist) == 0) + goto error; + if (psutil_gather_unix(SOCK_DGRAM, py_retlist) == 0) + goto error; + + free(psutil_xfiles); + return py_retlist; + +error: + Py_DECREF(py_retlist); + free(psutil_xfiles); + return NULL; +} diff --git a/psutil/arch/bsd/freebsd_socks.h b/psutil/arch/freebsd/sys_socks.h similarity index 79% rename from psutil/arch/bsd/freebsd_socks.h rename to psutil/arch/freebsd/sys_socks.h index 15ccb0b3b..752479265 100644 --- a/psutil/arch/bsd/freebsd_socks.h +++ b/psutil/arch/freebsd/sys_socks.h @@ -7,5 +7,4 @@ #include -PyObject* psutil_proc_connections(PyObject* self, PyObject* args); PyObject* psutil_net_connections(PyObject* self, PyObject* args); diff --git a/psutil/arch/bsd/netbsd_socks.c b/psutil/arch/netbsd/socks.c similarity index 95% rename from psutil/arch/bsd/netbsd_socks.c rename to psutil/arch/netbsd/socks.c index c782a4436..f370f0946 100644 --- a/psutil/arch/bsd/netbsd_socks.c +++ b/psutil/arch/netbsd/socks.c @@ -21,8 +21,9 @@ #include #include -// a signaler for connections without an actual status -int PSUTIL_CONN_NONE = 128; +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + // address family filter enum af_filter { @@ -109,10 +110,10 @@ psutil_kpcblist_clear(void) { static int psutil_get_files(void) { size_t len; + size_t j; int mib[6]; char *buf; off_t offset; - int j; mib[0] = CTL_KERN; mib[1] = KERN_FILE2; @@ -166,9 +167,9 @@ static int psutil_get_sockets(const char *name) { size_t namelen; int mib[8]; - int ret, j; struct kinfo_pcb *pcb; size_t len; + size_t j; memset(mib, 0, sizeof(mib)); @@ -309,11 +310,16 @@ psutil_get_info(int aff) { */ PyObject * psutil_net_connections(PyObject *self, PyObject *args) { - PyObject *py_retlist = PyList_New(0); + char laddr[PATH_MAX]; + char raddr[PATH_MAX]; + int32_t lport; + int32_t rport; + int32_t status; + pid_t pid; PyObject *py_tuple = NULL; PyObject *py_laddr = NULL; PyObject *py_raddr = NULL; - pid_t pid; + PyObject *py_retlist = PyList_New(0); if (py_retlist == NULL) return NULL; @@ -331,16 +337,11 @@ psutil_net_connections(PyObject *self, PyObject *args) { struct kif *k; SLIST_FOREACH(k, &kihead, kifs) { struct kpcb *kp; - if ((pid != -1) && (k->kif->ki_pid != pid)) + if ((pid != -1) && (k->kif->ki_pid != (unsigned int)pid)) continue; SLIST_FOREACH(kp, &kpcbhead, kpcbs) { if (k->kif->ki_fdata != kp->kpcb->ki_sockaddr) continue; - char laddr[PATH_MAX]; - char raddr[PATH_MAX]; - int32_t lport; - int32_t rport; - int32_t status; // IPv4 or IPv6 if ((kp->kpcb->ki_family == AF_INET) || @@ -403,19 +404,20 @@ psutil_net_connections(PyObject *self, PyObject *args) { strcpy(laddr, sun_src->sun_path); strcpy(raddr, sun_dst->sun_path); status = PSUTIL_CONN_NONE; - // TODO: handle unicode - py_laddr = Py_BuildValue("s", laddr); + py_laddr = PyUnicode_DecodeFSDefault(laddr); if (! py_laddr) goto error; - // TODO: handle unicode - py_raddr = Py_BuildValue("s", raddr); + py_raddr = PyUnicode_DecodeFSDefault(raddr); if (! py_raddr) goto error; } + else { + continue; + } // append tuple to list py_tuple = Py_BuildValue( - "(iiiNNii)", + "(iiiOOii)", k->kif->ki_fd, kp->kpcb->ki_family, kp->kpcb->ki_type, @@ -427,6 +429,8 @@ psutil_net_connections(PyObject *self, PyObject *args) { goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; + Py_DECREF(py_laddr); + Py_DECREF(py_raddr); Py_DECREF(py_tuple); } } diff --git a/psutil/arch/bsd/netbsd_socks.h b/psutil/arch/netbsd/socks.h similarity index 100% rename from psutil/arch/bsd/netbsd_socks.h rename to psutil/arch/netbsd/socks.h diff --git a/psutil/arch/bsd/netbsd.c b/psutil/arch/netbsd/specific.c similarity index 95% rename from psutil/arch/bsd/netbsd.c rename to psutil/arch/netbsd/specific.c index 2cf2ef224..cab60d608 100644 --- a/psutil/arch/bsd/netbsd.c +++ b/psutil/arch/netbsd/specific.c @@ -7,8 +7,8 @@ * Platform-specific module methods for NetBSD. */ -#if defined(__NetBSD__) -#define _KMEMUSER +#if defined(PSUTIL_NETBSD) + #define _KMEMUSER #endif #include @@ -38,10 +38,9 @@ #include #include - -#include "netbsd_socks.h" -#include "netbsd.h" +#include "specific.h" #include "../../_psutil_common.h" +#include "../../_psutil_posix.h" #define PSUTIL_KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0) #define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) @@ -73,7 +72,7 @@ psutil_kinfo_proc(pid_t pid, kinfo_proc *proc) { } // sysctl stores 0 in the size if we can't find the process information. if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); return -1; } return 0; @@ -118,6 +117,7 @@ kinfo_getfile(pid_t pid, int* cnt) { // https://github.com/giampaolo/psutil/pull/557#issuecomment-171912820 // Current implementation uses /proc instead. // Left here just in case. +/* PyObject * psutil_proc_exe(PyObject *self, PyObject *args) { #if __NetBSD_Version__ >= 799000000 @@ -157,21 +157,17 @@ psutil_proc_exe(PyObject *self, PyObject *args) { if (ret == -1) return NULL; else if (ret == 0) - return NoSuchProcess(); + return NoSuchProcess(""); else strcpy(pathname, ""); } -#if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeFSDefault(pathname); -#else - return Py_BuildValue("s", pathname); -#endif - #else return Py_BuildValue("s", ""); #endif } +*/ PyObject * psutil_proc_num_threads(PyObject *self, PyObject *args) { @@ -213,7 +209,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { goto error; } if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); goto error; } @@ -230,7 +226,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { goto error; } if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); goto error; } @@ -271,13 +267,9 @@ psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) { // On success, the function returns 0. // On error, the function returns a BSD errno value. kinfo_proc *result; - int done; - static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC, 0 }; // Declaring name as const requires us to cast it when passing it to // sysctl because the prototype doesn't include the const modifier. - size_t length; char errbuf[_POSIX2_LINE_MAX]; - kinfo_proc *x; int cnt; kvm_t *kd; @@ -332,7 +324,6 @@ psutil_get_cmd_args(pid_t pid, size_t *argsize) { size = sizeof(argmax); st = sysctl(mib, 2, &argmax, &size, NULL, 0); if (st == -1) { - warn("failed to get kern.argmax"); PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -350,7 +341,7 @@ psutil_get_cmd_args(pid_t pid, size_t *argsize) { st = sysctl(mib, 4, procargs, &argmax, NULL, 0); if (st == -1) { - warn("failed to get kern.procargs"); + free(procargs); PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -359,6 +350,7 @@ psutil_get_cmd_args(pid_t pid, size_t *argsize) { return procargs; } + // Return the command line as a python list object. // XXX - most of the times sysctl() returns a truncated string. // Also /proc/pid/cmdline behaves the same so it looks like this @@ -366,7 +358,7 @@ psutil_get_cmd_args(pid_t pid, size_t *argsize) { PyObject * psutil_get_cmdline(pid_t pid) { char *argstr = NULL; - int pos = 0; + size_t pos = 0; size_t argsize = 0; PyObject *py_arg = NULL; PyObject *py_retlist = PyList_New(0); @@ -385,11 +377,7 @@ psutil_get_cmdline(pid_t pid) { // separator if (argsize > 0) { while (pos < argsize) { -#if PY_MAJOR_VERSION >= 3 py_arg = PyUnicode_DecodeFSDefault(&argstr[pos]); -#else - py_arg = Py_BuildValue("s", &argstr[pos]); -#endif if (!py_arg) goto error; if (PyList_Append(py_retlist, py_arg)) @@ -497,6 +485,7 @@ psutil_swap_mem(PyObject *self, PyObject *args) { error: free(swdev); + return NULL; } @@ -513,7 +502,7 @@ psutil_proc_num_fds(PyObject *self, PyObject *args) { errno = 0; freep = kinfo_getfile(pid, &cnt); if (freep == NULL) { - psutil_raise_for_pid(pid, "kinfo_getfile() failed"); + psutil_raise_for_pid(pid, "kinfo_getfile()"); return NULL; } free(freep); @@ -525,7 +514,6 @@ psutil_proc_num_fds(PyObject *self, PyObject *args) { PyObject * psutil_per_cpu_times(PyObject *self, PyObject *args) { // XXX: why static? - static int maxcpus; int mib[3]; int ncpu; size_t len; @@ -585,7 +573,7 @@ PyObject * psutil_disk_io_counters(PyObject *self, PyObject *args) { int i, dk_ndrive, mib[3]; size_t len; - struct io_sysctl *stats; + struct io_sysctl *stats = NULL; PyObject *py_disk_info = NULL; PyObject *py_retdict = PyDict_New(); diff --git a/psutil/arch/bsd/netbsd.h b/psutil/arch/netbsd/specific.h similarity index 100% rename from psutil/arch/bsd/netbsd.h rename to psutil/arch/netbsd/specific.h diff --git a/psutil/arch/bsd/openbsd.c b/psutil/arch/openbsd/specific.c similarity index 93% rename from psutil/arch/bsd/openbsd.c rename to psutil/arch/openbsd/specific.c index dfa8999bd..33ebdeecb 100644 --- a/psutil/arch/bsd/openbsd.c +++ b/psutil/arch/openbsd/specific.c @@ -35,15 +35,12 @@ #include // for inet_ntoa() #include // for warn() & err() - #include "../../_psutil_common.h" +#include "../../_psutil_posix.h" #define PSUTIL_KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0) // #define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) -// a signaler for connections without an actual status -int PSUTIL_CONN_NONE = 128; - // ============================================================================ // Utility functions @@ -70,7 +67,7 @@ psutil_kinfo_proc(pid_t pid, struct kinfo_proc *proc) { } // sysctl stores 0 in the size if we can't find the process information. if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); return -1; } return 0; @@ -102,6 +99,7 @@ kinfo_getfile(long pid, int* cnt) { } mib[5] = (int)(len / sizeof(struct kinfo_file)); if (sysctl(mib, 6, kf, &len, NULL, 0) < 0) { + free(kf); PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -170,18 +168,21 @@ _psutil_get_argv(long pid) { static char **argv; int argv_mib[] = {CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV}; size_t argv_size = 128; - /* Loop and reallocate until we have enough space to fit argv. */ + // Loop and reallocate until we have enough space to fit argv. for (;; argv_size *= 2) { + if (argv_size >= 8192) { + PyErr_SetString(PyExc_RuntimeError, + "can't allocate enough space for KERN_PROC_ARGV"); + return NULL; + } if ((argv = realloc(argv, argv_size)) == NULL) - err(1, NULL); + continue; if (sysctl(argv_mib, 4, argv, &argv_size, NULL, 0) == 0) return argv; - if (errno == ESRCH) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - if (errno != ENOMEM) - err(1, NULL); + if (errno == ENOMEM) + continue; + PyErr_SetFromErrno(PyExc_OSError); + return NULL; } } @@ -203,11 +204,7 @@ psutil_get_cmdline(long pid) { goto error; for (p = argv; *p != NULL; p++) { -#if PY_MAJOR_VERSION >= 3 py_arg = PyUnicode_DecodeFSDefault(*p); -#else - py_arg = Py_BuildValue("s", *p); -#endif if (!py_arg) goto error; if (PyList_Append(py_retlist, py_arg)) @@ -245,7 +242,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { kd = kvm_openfiles(0, 0, 0, O_RDONLY, errbuf); if (! kd) { if (strstr(errbuf, "Permission denied") != NULL) - AccessDenied(); + AccessDenied(""); else PyErr_Format(PyExc_RuntimeError, "kvm_openfiles() syscall failed"); goto error; @@ -256,7 +253,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { sizeof(*kp), &nentries); if (! kp) { if (strstr(errbuf, "Permission denied") != NULL) - AccessDenied(); + AccessDenied(""); else PyErr_Format(PyExc_RuntimeError, "kvm_getprocs() syscall failed"); goto error; @@ -407,7 +404,7 @@ psutil_proc_num_fds(PyObject *self, PyObject *args) { errno = 0; freep = kinfo_getfile(pid, &cnt); if (freep == NULL) { - psutil_raise_for_pid(pid, "kinfo_getfile() failed"); + psutil_raise_for_pid(pid, "kinfo_getfile()"); return NULL; } free(freep); @@ -419,7 +416,8 @@ psutil_proc_num_fds(PyObject *self, PyObject *args) { PyObject * psutil_proc_cwd(PyObject *self, PyObject *args) { // Reference: - // http://anoncvs.spacehopper.org/openbsd-src/tree/bin/ps/print.c#n179 + // https://github.com/openbsd/src/blob/ + // 588f7f8c69786211f2d16865c552afb91b1c7cba/bin/ps/print.c#L191 long pid; struct kinfo_proc kp; char path[MAXPATHLEN]; @@ -435,11 +433,7 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } -#if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeFSDefault(path); -#else - return Py_BuildValue("s", path); -#endif } @@ -479,15 +473,21 @@ psutil_inet6_addrstr(struct in6_addr *p) } +/* + * List process connections. + * Note: there is no net_connections() on OpenBSD. The Python + * implementation will iterate over all processes and use this + * function. + * Note: local and remote paths cannot be determined for UNIX sockets. + */ PyObject * psutil_proc_connections(PyObject *self, PyObject *args) { long pid; - int i, cnt; - + int i; + int cnt; struct kinfo_file *freep = NULL; struct kinfo_file *kif; char *tcplist = NULL; - PyObject *py_retlist = PyList_New(0); PyObject *py_tuple = NULL; PyObject *py_laddr = NULL; @@ -509,7 +509,7 @@ psutil_proc_connections(PyObject *self, PyObject *args) { errno = 0; freep = kinfo_getfile(pid, &cnt); if (freep == NULL) { - psutil_raise_for_pid(pid, "kinfo_getfile() failed"); + psutil_raise_for_pid(pid, "kinfo_getfile()"); goto error; } @@ -608,15 +608,18 @@ psutil_proc_connections(PyObject *self, PyObject *args) { goto error; Py_DECREF(py_tuple); } - // UNIX socket + // UNIX socket. + // XXX: local addr is supposed to be in "unp_path" but it + // always empty; also "fstat" command is not able to show + // UNIX socket paths. else if (kif->so_family == AF_UNIX) { py_tuple = Py_BuildValue( - "(iiisOi)", + "(iiissi)", kif->fd_fd, kif->so_family, kif->so_type, - kif->unp_path, - Py_None, + "", // laddr (kif->unp_path is empty) + "", // raddr PSUTIL_CONN_NONE); if (!py_tuple) goto error; @@ -707,7 +710,7 @@ PyObject * psutil_disk_io_counters(PyObject *self, PyObject *args) { int i, dk_ndrive, mib[3]; size_t len; - struct diskstats *stats; + struct diskstats *stats = NULL; PyObject *py_retdict = PyDict_New(); PyObject *py_disk_info = NULL; diff --git a/psutil/arch/bsd/openbsd.h b/psutil/arch/openbsd/specific.h similarity index 100% rename from psutil/arch/bsd/openbsd.h rename to psutil/arch/openbsd/specific.h diff --git a/psutil/arch/osx/process_info.c b/psutil/arch/osx/process_info.c index 4b0b458ba..40c79a2cd 100644 --- a/psutil/arch/osx/process_info.c +++ b/psutil/arch/osx/process_info.c @@ -21,7 +21,7 @@ #include "process_info.h" #include "../../_psutil_common.h" - +#include "../../_psutil_posix.h" /* * Returns a list of all BSD processes on the system. This routine @@ -141,13 +141,12 @@ psutil_get_cmdline(long pid) { mib[1] = KERN_PROCARGS2; mib[2] = (pid_t)pid; if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { - if (EINVAL == errno) { - // EINVAL == access denied OR nonexistent PID - if (psutil_pid_exists(pid)) - AccessDenied(); - else - NoSuchProcess(); - } + // In case of zombie process we'll get EINVAL. We translate it + // to NSP and _psosx.py will translate it to ZP. + if ((errno == EINVAL) && (psutil_pid_exists(pid))) + NoSuchProcess(""); + else + PyErr_SetFromErrno(PyExc_OSError); goto error; } @@ -177,12 +176,8 @@ psutil_get_cmdline(long pid) { goto error; while (arg_ptr < arg_end && nargs > 0) { if (*arg_ptr++ == '\0') { -#if PY_MAJOR_VERSION >= 3 py_arg = PyUnicode_DecodeFSDefault(curr_arg); -#else - py_arg = Py_BuildValue("s", curr_arg); -#endif - if (!py_arg) + if (! py_arg) goto error; if (PyList_Append(py_retlist, py_arg)) goto error; @@ -240,13 +235,12 @@ psutil_get_environ(long pid) { mib[1] = KERN_PROCARGS2; mib[2] = (pid_t)pid; if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { - if (EINVAL == errno) { - // EINVAL == access denied OR nonexistent PID - if (psutil_pid_exists(pid)) - AccessDenied(); - else - NoSuchProcess(); - } + // In case of zombie process we'll get EINVAL. We translate it + // to NSP and _psosx.py will translate it to ZP. + if ((errno == EINVAL) && (psutil_pid_exists(pid))) + NoSuchProcess(""); + else + PyErr_SetFromErrno(PyExc_OSError); goto error; } @@ -293,13 +287,8 @@ psutil_get_environ(long pid) { arg_ptr = s + 1; } -#if PY_MAJOR_VERSION >= 3 py_ret = PyUnicode_DecodeFSDefaultAndSize( procenv, arg_ptr - env_start + 1); -#else - py_ret = PyString_FromStringAndSize(procenv, arg_ptr - env_start + 1); -#endif - if (!py_ret) { // XXX: don't want to free() this as per: // https://github.com/giampaolo/psutil/issues/926 @@ -349,7 +338,7 @@ psutil_get_kinfo_proc(long pid, struct kinfo_proc *kp) { // sysctl succeeds but len is zero, happens when process has gone away if (len == 0) { - NoSuchProcess(); + NoSuchProcess(""); return -1; } return 0; @@ -365,7 +354,7 @@ psutil_proc_pidinfo(long pid, int flavor, uint64_t arg, void *pti, int size) { errno = 0; int ret = proc_pidinfo((int)pid, flavor, arg, pti, size); if ((ret <= 0) || ((unsigned long)ret < sizeof(pti))) { - psutil_raise_for_pid(pid, "proc_pidinfo() syscall failed"); + psutil_raise_for_pid(pid, "proc_pidinfo()"); return 0; } return ret; diff --git a/psutil/arch/solaris/environ.c b/psutil/arch/solaris/environ.c new file mode 100644 index 000000000..1af4c1293 --- /dev/null +++ b/psutil/arch/solaris/environ.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Oleksii Shevchuk. + * All rights reserved. Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * + * Functions specific for Process.environ(). + */ + +#define NEW_MIB_COMPLIANT 1 +#define _STRUCTURED_PROC 1 + +#include + +#if !defined(_LP64) && _FILE_OFFSET_BITS == 64 +# undef _FILE_OFFSET_BITS +# undef _LARGEFILE64_SOURCE +#endif + +#include +#include +#include +#include + +#include "environ.h" + + +#define STRING_SEARCH_BUF_SIZE 512 + + +/* + * Open address space of specified process and return file descriptor. + * @param pid a pid of process. + * @param procfs_path a path to mounted procfs filesystem. + * @return file descriptor or -1 in case of error. + */ +static int +open_address_space(pid_t pid, const char *procfs_path) { + int fd; + char proc_path[PATH_MAX]; + + snprintf(proc_path, PATH_MAX, "%s/%i/as", procfs_path, pid); + fd = open(proc_path, O_RDONLY); + if (fd < 0) + PyErr_SetFromErrno(PyExc_OSError); + + return fd; +} + + +/* + * Read chunk of data by offset to specified buffer of the same size. + * @param fd a file descriptor. + * @param offset an required offset in file. + * @param buf a buffer where to store result. + * @param buf_size a size of buffer where data will be stored. + * @return amount of bytes stored to the buffer or -1 in case of + * error. + */ +static int +read_offt(int fd, off_t offset, char *buf, size_t buf_size) { + size_t to_read = buf_size; + size_t stored = 0; + int r; + + while (to_read) { + r = pread(fd, buf + stored, to_read, offset + stored); + if (r < 0) + goto error; + else if (r == 0) + break; + + to_read -= r; + stored += r; + } + + return stored; + + error: + PyErr_SetFromErrno(PyExc_OSError); + return -1; +} + + +/* + * Read null-terminated string from file descriptor starting from + * specified offset. + * @param fd a file descriptor of opened address space. + * @param offset an offset in specified file descriptor. + * @return allocated null-terminated string or NULL in case of error. +*/ +static char * +read_cstring_offt(int fd, off_t offset) { + int r; + int i = 0; + off_t end = offset; + size_t len; + char buf[STRING_SEARCH_BUF_SIZE]; + char *result = NULL; + + if (lseek(fd, offset, SEEK_SET) == (off_t)-1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // Search end of string + for (;;) { + r = read(fd, buf, sizeof(buf)); + if (r == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + else if (r == 0) { + break; + } + else { + for (i=0; i= 0 && count) + *count = env_count; + + if (env_count > 0) + result = read_cstrings_block( + as, info.pr_envp, ptr_size, env_count); + + close(as); + return result; +} + + +/* + * Free array of cstrings. + * @param array an array of cstrings returned by psutil_read_raw_env, + * psutil_read_raw_args or any other function. + * @param count a count of strings in the passed array + */ +void +psutil_free_cstrings_array(char **array, size_t count) { + int i; + + if (!array) + return; + for (i=0; i @@ -21,10 +21,10 @@ static struct sockaddr * -sa_dup (struct sockaddr *sa1) +sa_dup (struct sockaddr_storage *sa1) { struct sockaddr *sa2; - size_t sz = sizeof(sa1); + size_t sz = sizeof(struct sockaddr_storage); sa2 = (struct sockaddr *) calloc(1,sz); memcpy(sa2,sa1,sz); return(sa2); @@ -91,11 +91,11 @@ int getifaddrs (struct ifaddrs **ifap) if (ioctl(sd, SIOCGLIFADDR, ifr, IFREQSZ) < 0) goto error; - cifa->ifa_addr = sa_dup((struct sockaddr*)&ifr->lifr_addr); + cifa->ifa_addr = sa_dup(&ifr->lifr_addr); if (ioctl(sd, SIOCGLIFNETMASK, ifr, IFREQSZ) < 0) goto error; - cifa->ifa_netmask = sa_dup((struct sockaddr*)&ifr->lifr_addr); + cifa->ifa_netmask = sa_dup(&ifr->lifr_addr); cifa->ifa_flags = 0; cifa->ifa_dstaddr = NULL; @@ -105,9 +105,9 @@ int getifaddrs (struct ifaddrs **ifap) if (ioctl(sd, SIOCGLIFDSTADDR, ifr, IFREQSZ) < 0) { if (0 == ioctl(sd, SIOCGLIFBRDADDR, ifr, IFREQSZ)) - cifa->ifa_dstaddr = sa_dup((struct sockaddr*)&ifr->lifr_addr); + cifa->ifa_dstaddr = sa_dup(&ifr->lifr_addr); } - else cifa->ifa_dstaddr = sa_dup((struct sockaddr*)&ifr->lifr_addr); + else cifa->ifa_dstaddr = sa_dup(&ifr->lifr_addr); pifa = cifa; ccp += IFREQSZ; diff --git a/psutil/arch/solaris/v10/ifaddrs.h b/psutil/arch/solaris/v10/ifaddrs.h index e1d885963..0953a9b99 100644 --- a/psutil/arch/solaris/v10/ifaddrs.h +++ b/psutil/arch/solaris/v10/ifaddrs.h @@ -1,8 +1,8 @@ /* Reference: https://lists.samba.org/archive/samba-technical/2009-February/063079.html */ -#ifndef __IFADDRS_H___ -#define __IFADDRS_H___ +#ifndef __IFADDRS_H__ +#define __IFADDRS_H__ #include #include @@ -23,4 +23,4 @@ struct ifaddrs { extern int getifaddrs(struct ifaddrs **); extern void freeifaddrs(struct ifaddrs *); -#endif \ No newline at end of file +#endif diff --git a/psutil/arch/windows/inet_ntop.c b/psutil/arch/windows/inet_ntop.c index 50dfb6aed..4b6c1dfec 100644 --- a/psutil/arch/windows/inet_ntop.c +++ b/psutil/arch/windows/inet_ntop.c @@ -1,9 +1,15 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Jeff Tang. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include #include "inet_ntop.h" // From: https://memset.wordpress.com/2010/10/09/inet_ntop-for-win32/ -PCSTR -WSAAPI -inet_ntop(__in INT Family, +PCSTR WSAAPI +inet_ntop(__in INT family, __in PVOID pAddr, __out_ecount(StringBufSize) PSTR pStringBuf, __in size_t StringBufSize) { @@ -13,17 +19,18 @@ inet_ntop(__in INT Family, struct sockaddr_in6 *srcaddr6 = (struct sockaddr_in6*) &srcaddr; memset(&srcaddr, 0, sizeof(struct sockaddr_storage)); - srcaddr.ss_family = Family; + srcaddr.ss_family = family; - if (Family == AF_INET) - { + if (family == AF_INET) { dwAddressLength = sizeof(struct sockaddr_in); memcpy(&(srcaddr4->sin_addr), pAddr, sizeof(struct in_addr)); - } else if (Family == AF_INET6) - { + } + else if (family == AF_INET6) { dwAddressLength = sizeof(struct sockaddr_in6); memcpy(&(srcaddr6->sin6_addr), pAddr, sizeof(struct in6_addr)); - } else { + } + else { + PyErr_SetString(PyExc_ValueError, "invalid family"); return NULL; } @@ -32,6 +39,7 @@ inet_ntop(__in INT Family, 0, pStringBuf, (LPDWORD) &StringBufSize) != 0) { + PyErr_SetExcFromWindowsErr(PyExc_OSError, WSAGetLastError()); return NULL; } return pStringBuf; diff --git a/psutil/arch/windows/inet_ntop.h b/psutil/arch/windows/inet_ntop.h index 0d97e28c8..70573a368 100644 --- a/psutil/arch/windows/inet_ntop.h +++ b/psutil/arch/windows/inet_ntop.h @@ -1,10 +1,15 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Jeff Tang. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + #include -PCSTR -WSAAPI +PCSTR WSAAPI inet_ntop( __in INT Family, __in PVOID pAddr, __out_ecount(StringBufSize) PSTR pStringBuf, __in size_t StringBufSize -); \ No newline at end of file +); diff --git a/psutil/arch/windows/ntextapi.h b/psutil/arch/windows/ntextapi.h index 1bbbf2ac0..ea23ddb72 100644 --- a/psutil/arch/windows/ntextapi.h +++ b/psutil/arch/windows/ntextapi.h @@ -163,13 +163,15 @@ typedef enum _KWAIT_REASON { } KWAIT_REASON, *PKWAIT_REASON; -typedef struct _CLIENT_ID { +typedef struct _CLIENT_ID2 { HANDLE UniqueProcess; HANDLE UniqueThread; -} CLIENT_ID, *PCLIENT_ID; +} CLIENT_ID2, *PCLIENT_ID2; +#define CLIENT_ID CLIENT_ID2 +#define PCLIENT_ID PCLIENT_ID2 -typedef struct _SYSTEM_THREAD_INFORMATION { +typedef struct _SYSTEM_THREAD_INFORMATION2 { LARGE_INTEGER KernelTime; LARGE_INTEGER UserTime; LARGE_INTEGER CreateTime; @@ -181,8 +183,10 @@ typedef struct _SYSTEM_THREAD_INFORMATION { ULONG ContextSwitches; ULONG ThreadState; KWAIT_REASON WaitReason; -} SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION; +} SYSTEM_THREAD_INFORMATION2, *PSYSTEM_THREAD_INFORMATION2; +#define SYSTEM_THREAD_INFORMATION SYSTEM_THREAD_INFORMATION2 +#define PSYSTEM_THREAD_INFORMATION PSYSTEM_THREAD_INFORMATION2 typedef struct _TEB *PTEB; diff --git a/psutil/arch/windows/process_handles.c b/psutil/arch/windows/process_handles.c index b260450e5..356e23686 100644 --- a/psutil/arch/windows/process_handles.c +++ b/psutil/arch/windows/process_handles.c @@ -5,6 +5,7 @@ * */ #include "process_handles.h" +#include "../../_psutil_common.h" static _NtQuerySystemInformation __NtQuerySystemInformation = NULL; static _NtQueryObject __NtQueryObject = NULL; @@ -19,7 +20,6 @@ HANDLE g_hThread = NULL; PUNICODE_STRING g_pNameBuffer = NULL; ULONG g_dwSize = 0; ULONG g_dwLength = 0; -PVOID g_fiber = NULL; PVOID @@ -280,7 +280,7 @@ psutil_NtQueryObject() { g_hThread = CreateThread( NULL, 0, - (LPTHREAD_START_ROUTINE)psutil_NtQueryObjectThread, + psutil_NtQueryObjectThread, NULL, 0, NULL); @@ -300,11 +300,6 @@ psutil_NtQueryObject() { WaitForSingleObject(g_hThread, INFINITE); CloseHandle(g_hThread); - // Cleanup Fiber - if (g_fiber != NULL) - DeleteFiber(g_fiber); - g_fiber = NULL; - g_hThread = NULL; } @@ -312,12 +307,8 @@ psutil_NtQueryObject() { } -void -psutil_NtQueryObjectThread() { - // Prevent the thread stack from leaking when this - // thread gets terminated due to NTQueryObject hanging - g_fiber = ConvertThreadToFiber(NULL); - +DWORD WINAPI +psutil_NtQueryObjectThread(LPVOID lpvParam) { // Loop infinitely waiting for work while (TRUE) { WaitForSingleObject(g_hEvtStart, INFINITE); diff --git a/psutil/arch/windows/process_handles.h b/psutil/arch/windows/process_handles.h index ea5fbdbeb..4a022c1c1 100644 --- a/psutil/arch/windows/process_handles.h +++ b/psutil/arch/windows/process_handles.h @@ -106,6 +106,6 @@ PyObject* psutil_get_open_files(long pid, HANDLE processHandle); PyObject* psutil_get_open_files_ntqueryobject(long dwPid, HANDLE hProcess); PyObject* psutil_get_open_files_getmappedfilename(long dwPid, HANDLE hProcess); DWORD psutil_NtQueryObject(void); -void psutil_NtQueryObjectThread(void); +DWORD WINAPI psutil_NtQueryObjectThread(LPVOID lpvParam); #endif // __PROCESS_HANDLES_H__ diff --git a/psutil/arch/windows/process_info.c b/psutil/arch/windows/process_info.c index e29f2161d..ffd3c80ef 100644 --- a/psutil/arch/windows/process_info.c +++ b/psutil/arch/windows/process_info.c @@ -18,196 +18,11 @@ #include "../../_psutil_common.h" -/* - * A wrapper around OpenProcess setting NSP exception if process - * no longer exists. - * "pid" is the process pid, "dwDesiredAccess" is the first argument - * exptected by OpenProcess. - * Return a process handle or NULL. - */ -HANDLE -psutil_handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess) { - HANDLE hProcess; - DWORD processExitCode = 0; - - if (pid == 0) { - // otherwise we'd get NoSuchProcess - return AccessDenied(); - } - - hProcess = OpenProcess(dwDesiredAccess, FALSE, pid); - if (hProcess == NULL) { - if (GetLastError() == ERROR_INVALID_PARAMETER) - NoSuchProcess(); - else - PyErr_SetFromWindowsErr(0); - return NULL; - } - - // make sure the process is running - GetExitCodeProcess(hProcess, &processExitCode); - if (processExitCode == 0) { - NoSuchProcess(); - CloseHandle(hProcess); - return NULL; - } - return hProcess; -} - - -/* - * Same as psutil_handle_from_pid_waccess but implicitly uses - * PROCESS_QUERY_INFORMATION | PROCESS_VM_READ as dwDesiredAccess - * parameter for OpenProcess. - */ -HANDLE -psutil_handle_from_pid(DWORD pid) { - DWORD dwDesiredAccess = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; - return psutil_handle_from_pid_waccess(pid, dwDesiredAccess); -} - - -/* - * Given a Python int referencing a process handle close the process handle. - */ -PyObject * -psutil_win32_CloseHandle(PyObject *self, PyObject *args) { - unsigned long handle; - - if (! PyArg_ParseTuple(args, "k", &handle)) - return NULL; - // TODO: may want to check return value; - CloseHandle((HANDLE)handle); - Py_RETURN_NONE; -} - - -DWORD * -psutil_get_pids(DWORD *numberOfReturnedPIDs) { - // Win32 SDK says the only way to know if our process array - // wasn't large enough is to check the returned size and make - // sure that it doesn't match the size of the array. - // If it does we allocate a larger array and try again - - // Stores the actual array - DWORD *procArray = NULL; - DWORD procArrayByteSz; - int procArraySz = 0; - - // Stores the byte size of the returned array from enumprocesses - DWORD enumReturnSz = 0; - - do { - procArraySz += 1024; - free(procArray); - procArrayByteSz = procArraySz * sizeof(DWORD); - procArray = malloc(procArrayByteSz); - if (procArray == NULL) { - PyErr_NoMemory(); - return NULL; - } - if (! EnumProcesses(procArray, procArrayByteSz, &enumReturnSz)) { - free(procArray); - PyErr_SetFromWindowsErr(0); - return NULL; - } - } while (enumReturnSz == procArraySz * sizeof(DWORD)); - - // The number of elements is the returned size / size of each element - *numberOfReturnedPIDs = enumReturnSz / sizeof(DWORD); - - return procArray; -} - - -int -psutil_pid_is_running(DWORD pid) { - HANDLE hProcess; - DWORD exitCode; - - // Special case for PID 0 System Idle Process - if (pid == 0) - return 1; - if (pid < 0) - return 0; - - hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - FALSE, pid); - if (NULL == hProcess) { - // invalid parameter is no such process - if (GetLastError() == ERROR_INVALID_PARAMETER) { - CloseHandle(hProcess); - return 0; - } - - // access denied obviously means there's a process to deny access to... - if (GetLastError() == ERROR_ACCESS_DENIED) { - CloseHandle(hProcess); - return 1; - } - - CloseHandle(hProcess); - PyErr_SetFromWindowsErr(0); - return -1; - } - - if (GetExitCodeProcess(hProcess, &exitCode)) { - CloseHandle(hProcess); - return (exitCode == STILL_ACTIVE); - } - - // access denied means there's a process there so we'll assume - // it's running - if (GetLastError() == ERROR_ACCESS_DENIED) { - CloseHandle(hProcess); - return 1; - } - - PyErr_SetFromWindowsErr(0); - CloseHandle(hProcess); - return -1; -} - - -int -psutil_pid_in_proclist(DWORD pid) { - DWORD *proclist = NULL; - DWORD numberOfReturnedPIDs; - DWORD i; - - proclist = psutil_get_pids(&numberOfReturnedPIDs); - if (proclist == NULL) - return -1; - for (i = 0; i < numberOfReturnedPIDs; i++) { - if (pid == proclist[i]) { - free(proclist); - return 1; - } - } - - free(proclist); - return 0; -} - - -// Check exit code from a process handle. Return FALSE on an error also -// XXX - not used anymore -int -handlep_is_running(HANDLE hProcess) { - DWORD dwCode; - - if (NULL == hProcess) - return 0; - if (GetExitCodeProcess(hProcess, &dwCode)) { - if (dwCode == STILL_ACTIVE) - return 1; - } - return 0; -} - -// Helper structures to access the memory correctly. Some of these might also -// be defined in the winternl.h header file but unfortunately not in a usable -// way. +// ==================================================================== +// Helper structures to access the memory correctly. +// Some of these might also be defined in the winternl.h header file +// but unfortunately not in a usable way. +// ==================================================================== // see http://msdn2.microsoft.com/en-us/library/aa489609.aspx #ifndef NT_SUCCESS @@ -335,6 +150,297 @@ typedef struct { } PEB64; #endif + +#define PSUTIL_FIRST_PROCESS(Processes) ( \ + (PSYSTEM_PROCESS_INFORMATION)(Processes)) +#define PSUTIL_NEXT_PROCESS(Process) ( \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset ? \ + (PSYSTEM_PROCESS_INFORMATION)((PCHAR)(Process) + \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset) : NULL) + +const int STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; +const int STATUS_BUFFER_TOO_SMALL = 0xC0000023L; + + +// ==================================================================== +// Process and PIDs utiilties. +// ==================================================================== + + +/* + * Return 1 if PID exists, 0 if not, -1 on error. + */ +int +psutil_pid_in_pids(DWORD pid) { + DWORD *proclist = NULL; + DWORD numberOfReturnedPIDs; + DWORD i; + + proclist = psutil_get_pids(&numberOfReturnedPIDs); + if (proclist == NULL) + return -1; + for (i = 0; i < numberOfReturnedPIDs; i++) { + if (proclist[i] == pid) { + free(proclist); + return 1; + } + } + free(proclist); + return 0; +} + + +/* + * Given a process HANDLE checks whether it's actually running. + * Returns: + * - 1: running + * - 0: not running + * - -1: WindowsError + * - -2: AssertionError + */ +int +psutil_is_phandle_running(HANDLE hProcess, DWORD pid) { + DWORD processExitCode = 0; + + if (hProcess == NULL) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + // Yeah, this is the actual error code in case of + // "no such process". + if (! psutil_assert_pid_not_exists( + pid, "iphr: OpenProcess() -> ERROR_INVALID_PARAMETER")) { + return -2; + } + return 0; + } + return -1; + } + + if (GetExitCodeProcess(hProcess, &processExitCode)) { + // XXX - maybe STILL_ACTIVE is not fully reliable as per: + // http://stackoverflow.com/questions/1591342/#comment47830782_1591379 + if (processExitCode == STILL_ACTIVE) { + if (! psutil_assert_pid_exists( + pid, "iphr: GetExitCodeProcess() -> STILL_ACTIVE")) { + return -2; + } + return 1; + } + else { + // We can't be sure so we look into pids. + if (psutil_pid_in_pids(pid) == 1) { + return 1; + } + else { + CloseHandle(hProcess); + return 0; + } + } + } + + CloseHandle(hProcess); + if (! psutil_assert_pid_not_exists( pid, "iphr: exit fun")) { + return -2; + } + return -1; +} + + +/* + * Given a process HANDLE checks whether it's actually running and if + * it does return it, else return NULL with the proper Python exception + * set. + */ +HANDLE +psutil_check_phandle(HANDLE hProcess, DWORD pid) { + int ret = psutil_is_phandle_running(hProcess, pid); + if (ret == 1) + return hProcess; + else if (ret == 0) + return NoSuchProcess(""); + else if (ret == -1) + return PyErr_SetFromWindowsErr(0); + else // -2 + return NULL; +} + + +/* + * A wrapper around OpenProcess setting NSP exception if process + * no longer exists. + * "pid" is the process pid, "dwDesiredAccess" is the first argument + * exptected by OpenProcess. + * Return a process handle or NULL. + */ +HANDLE +psutil_handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess) { + HANDLE hProcess; + + if (pid == 0) { + // otherwise we'd get NoSuchProcess + return AccessDenied(""); + } + + hProcess = OpenProcess(dwDesiredAccess, FALSE, pid); + return psutil_check_phandle(hProcess, pid); +} + + +/* + * Same as psutil_handle_from_pid_waccess but implicitly uses + * PROCESS_QUERY_INFORMATION | PROCESS_VM_READ as dwDesiredAccess + * parameter for OpenProcess. + */ +HANDLE +psutil_handle_from_pid(DWORD pid) { + DWORD dwDesiredAccess = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + return psutil_handle_from_pid_waccess(pid, dwDesiredAccess); +} + + +DWORD * +psutil_get_pids(DWORD *numberOfReturnedPIDs) { + // Win32 SDK says the only way to know if our process array + // wasn't large enough is to check the returned size and make + // sure that it doesn't match the size of the array. + // If it does we allocate a larger array and try again + + // Stores the actual array + DWORD *procArray = NULL; + DWORD procArrayByteSz; + int procArraySz = 0; + + // Stores the byte size of the returned array from enumprocesses + DWORD enumReturnSz = 0; + + do { + procArraySz += 1024; + free(procArray); + procArrayByteSz = procArraySz * sizeof(DWORD); + procArray = malloc(procArrayByteSz); + if (procArray == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (! EnumProcesses(procArray, procArrayByteSz, &enumReturnSz)) { + free(procArray); + PyErr_SetFromWindowsErr(0); + return NULL; + } + } while (enumReturnSz == procArraySz * sizeof(DWORD)); + + // The number of elements is the returned size / size of each element + *numberOfReturnedPIDs = enumReturnSz / sizeof(DWORD); + + return procArray; +} + + +int +psutil_assert_pid_exists(DWORD pid, char *err) { + if (PSUTIL_TESTING) { + if (psutil_pid_in_pids(pid) == 0) { + PyErr_SetString(PyExc_AssertionError, err); + return 0; + } + } + return 1; +} + + +int +psutil_assert_pid_not_exists(DWORD pid, char *err) { + if (PSUTIL_TESTING) { + if (psutil_pid_in_pids(pid) == 1) { + PyErr_SetString(PyExc_AssertionError, err); + return 0; + } + } + return 1; +} + + +/* +/* Check for PID existance by using OpenProcess() + GetExitCodeProcess. +/* Returns: + * 1: pid exists + * 0: it doesn't + * -1: error + */ +int +psutil_pid_is_running(DWORD pid) { + HANDLE hProcess; + DWORD exitCode; + DWORD err; + + // Special case for PID 0 System Idle Process + if (pid == 0) + return 1; + if (pid < 0) + return 0; + hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + FALSE, pid); + if (NULL == hProcess) { + err = GetLastError(); + // Yeah, this is the actual error code in case of "no such process". + if (err == ERROR_INVALID_PARAMETER) { + if (! psutil_assert_pid_not_exists( + pid, "pir: OpenProcess() -> INVALID_PARAMETER")) { + return -1; + } + return 0; + } + // Access denied obviously means there's a process to deny access to. + else if (err == ERROR_ACCESS_DENIED) { + if (! psutil_assert_pid_exists( + pid, "pir: OpenProcess() ACCESS_DENIED")) { + return -1; + } + return 1; + } + // Be strict and raise an exception; the caller is supposed + // to take -1 into account. + else { + PyErr_SetFromWindowsErr(err); + return -1; + } + } + + if (GetExitCodeProcess(hProcess, &exitCode)) { + CloseHandle(hProcess); + // XXX - maybe STILL_ACTIVE is not fully reliable as per: + // http://stackoverflow.com/questions/1591342/#comment47830782_1591379 + if (exitCode == STILL_ACTIVE) { + if (! psutil_assert_pid_exists( + pid, "pir: GetExitCodeProcess() -> STILL_ACTIVE")) { + return -1; + } + return 1; + } + // We can't be sure so we look into pids. + else { + return psutil_pid_in_pids(pid); + } + } + else { + err = GetLastError(); + CloseHandle(hProcess); + // Same as for OpenProcess, assume access denied means there's + // a process to deny access to. + if (err == ERROR_ACCESS_DENIED) { + if (! psutil_assert_pid_exists( + pid, "pir: GetExitCodeProcess() -> ERROR_ACCESS_DENIED")) { + return -1; + } + return 1; + } + else { + PyErr_SetFromWindowsErr(0); + return -1; + } + } +} + + /* Given a pointer into a process's memory, figure out how much data can be * read from it. */ static int psutil_get_process_region_size(HANDLE hProcess, @@ -783,15 +889,6 @@ PyObject *psutil_get_environ(long pid) { return ret; } -#define PH_FIRST_PROCESS(Processes) ((PSYSTEM_PROCESS_INFORMATION)(Processes)) -#define PH_NEXT_PROCESS(Process) ( \ - ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset ? \ - (PSYSTEM_PROCESS_INFORMATION)((PCHAR)(Process) + \ - ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset) : \ - NULL) - -const int STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; -const int STATUS_BUFFER_TOO_SMALL = 0xC0000023L; /* * Given a process PID and a PSYSTEM_PROCESS_INFORMATION structure @@ -853,16 +950,16 @@ psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, if (bufferSize <= 0x20000) initialBufferSize = bufferSize; - process = PH_FIRST_PROCESS(buffer); + process = PSUTIL_FIRST_PROCESS(buffer); do { if (process->UniqueProcessId == (HANDLE)pid) { *retProcess = process; *retBuffer = buffer; return 1; } - } while ( (process = PH_NEXT_PROCESS(process)) ); + } while ( (process = PSUTIL_NEXT_PROCESS(process)) ); - NoSuchProcess(); + NoSuchProcess(""); goto error; error: diff --git a/psutil/arch/windows/process_info.h b/psutil/arch/windows/process_info.h index 7c2c9c2be..a2f70c2b9 100644 --- a/psutil/arch/windows/process_info.h +++ b/psutil/arch/windows/process_info.h @@ -19,16 +19,16 @@ DWORD* psutil_get_pids(DWORD *numberOfReturnedPIDs); HANDLE psutil_handle_from_pid(DWORD pid); HANDLE psutil_handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess); -PyObject* psutil_win32_OpenProcess(PyObject *self, PyObject *args); -PyObject* psutil_win32_CloseHandle(PyObject *self, PyObject *args); - -int psutil_handlep_is_running(HANDLE hProcess); -int psutil_pid_in_proclist(DWORD pid); int psutil_pid_is_running(DWORD pid); +int psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, + PVOID *retBuffer); + +int psutil_assert_pid_exists(DWORD pid, char *err); +int psutil_assert_pid_not_exists(DWORD pid, char *err); + + PyObject* psutil_get_cmdline(long pid); PyObject* psutil_get_cwd(long pid); PyObject* psutil_get_environ(long pid); -int psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, - PVOID *retBuffer); #endif diff --git a/psutil/arch/windows/services.c b/psutil/arch/windows/services.c index 7923ddc27..62a12861f 100644 --- a/psutil/arch/windows/services.c +++ b/psutil/arch/windows/services.c @@ -9,7 +9,7 @@ #include #include "services.h" - +#include "../../_psutil_common.h" // ================================================================== // utils @@ -18,10 +18,9 @@ SC_HANDLE psutil_get_service_handler(char *service_name, DWORD scm_access, DWORD access) { - ENUM_SERVICE_STATUS_PROCESS *lpService = NULL; + ENUM_SERVICE_STATUS_PROCESSW *lpService = NULL; SC_HANDLE sc = NULL; SC_HANDLE hService = NULL; - SERVICE_DESCRIPTION *scd = NULL; sc = OpenSCManager(NULL, NULL, scm_access); if (sc == NULL) { @@ -96,7 +95,7 @@ get_state_string(DWORD state) { */ PyObject * psutil_winservice_enumerate(PyObject *self, PyObject *args) { - ENUM_SERVICE_STATUS_PROCESS *lpService = NULL; + ENUM_SERVICE_STATUS_PROCESSW *lpService = NULL; BOOL ok; SC_HANDLE sc = NULL; DWORD bytesNeeded = 0; @@ -106,7 +105,8 @@ psutil_winservice_enumerate(PyObject *self, PyObject *args) { DWORD i; PyObject *py_retlist = PyList_New(0); PyObject *py_tuple = NULL; - PyObject *py_unicode_display_name = NULL; + PyObject *py_name = NULL; + PyObject *py_display_name = NULL; if (py_retlist == NULL) return NULL; @@ -118,7 +118,7 @@ psutil_winservice_enumerate(PyObject *self, PyObject *args) { } for (;;) { - ok = EnumServicesStatusEx( + ok = EnumServicesStatusExW( sc, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, // XXX - extend this to include drivers etc.? @@ -134,31 +134,31 @@ psutil_winservice_enumerate(PyObject *self, PyObject *args) { if (lpService) free(lpService); dwBytes = bytesNeeded; - lpService = (ENUM_SERVICE_STATUS_PROCESS*)malloc(dwBytes); + lpService = (ENUM_SERVICE_STATUS_PROCESSW*)malloc(dwBytes); } for (i = 0; i < srvCount; i++) { - // Get unicode display name. - py_unicode_display_name = NULL; - py_unicode_display_name = PyUnicode_Decode( - lpService[i].lpDisplayName, - _tcslen(lpService[i].lpDisplayName), - Py_FileSystemDefaultEncoding, - "replace"); - if (py_unicode_display_name == NULL) + // Get unicode name / display name. + py_name = NULL; + py_name = PyUnicode_FromWideChar( + lpService[i].lpServiceName, wcslen(lpService[i].lpServiceName)); + if (py_name == NULL) + goto error; + + py_display_name = NULL; + py_display_name = PyUnicode_FromWideChar( + lpService[i].lpDisplayName, wcslen(lpService[i].lpDisplayName)); + if (py_display_name == NULL) goto error; // Construct the result. - py_tuple = Py_BuildValue( - "(sO)", - lpService[i].lpServiceName, // name - py_unicode_display_name // display_name - ); + py_tuple = Py_BuildValue("(OO)", py_name, py_display_name); if (py_tuple == NULL) goto error; if (PyList_Append(py_retlist, py_tuple)) goto error; - Py_DECREF(py_unicode_display_name); + Py_DECREF(py_display_name); + Py_DECREF(py_name); Py_DECREF(py_tuple); } @@ -168,7 +168,8 @@ psutil_winservice_enumerate(PyObject *self, PyObject *args) { return py_retlist; error: - Py_XDECREF(py_unicode_display_name); + Py_DECREF(py_name); + Py_XDECREF(py_display_name); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); if (sc != NULL) @@ -194,7 +195,7 @@ psutil_winservice_query_config(PyObject *self, PyObject *args) { DWORD bytesNeeded = 0; DWORD resumeHandle = 0; DWORD dwBytes = 0; - QUERY_SERVICE_CONFIG *qsc = NULL; + QUERY_SERVICE_CONFIGW *qsc = NULL; PyObject *py_tuple = NULL; PyObject *py_unicode_display_name = NULL; PyObject *py_unicode_binpath = NULL; @@ -207,45 +208,36 @@ psutil_winservice_query_config(PyObject *self, PyObject *args) { if (hService == NULL) goto error; - // First call to QueryServiceConfig() is necessary to get the + // First call to QueryServiceConfigW() is necessary to get the // right size. bytesNeeded = 0; - QueryServiceConfig(hService, NULL, 0, &bytesNeeded); + QueryServiceConfigW(hService, NULL, 0, &bytesNeeded); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { PyErr_SetFromWindowsErr(0); goto error; } - qsc = (QUERY_SERVICE_CONFIG *)malloc(bytesNeeded); - ok = QueryServiceConfig(hService, qsc, bytesNeeded, &bytesNeeded); + qsc = (QUERY_SERVICE_CONFIGW *)malloc(bytesNeeded); + ok = QueryServiceConfigW(hService, qsc, bytesNeeded, &bytesNeeded); if (ok == 0) { PyErr_SetFromWindowsErr(0); goto error; } // Get unicode display name. - py_unicode_display_name = PyUnicode_Decode( - qsc->lpDisplayName, - _tcslen(qsc->lpDisplayName), - Py_FileSystemDefaultEncoding, - "replace"); + py_unicode_display_name = PyUnicode_FromWideChar( + qsc->lpDisplayName, wcslen(qsc->lpDisplayName)); if (py_unicode_display_name == NULL) goto error; // Get unicode bin path. - py_unicode_binpath = PyUnicode_Decode( - qsc->lpBinaryPathName, - _tcslen(qsc->lpBinaryPathName), - Py_FileSystemDefaultEncoding, - "replace"); + py_unicode_binpath = PyUnicode_FromWideChar( + qsc->lpBinaryPathName, wcslen(qsc->lpBinaryPathName)); if (py_unicode_binpath == NULL) goto error; // Get unicode username. - py_unicode_username = PyUnicode_Decode( - qsc->lpServiceStartName, - _tcslen(qsc->lpServiceStartName), - Py_FileSystemDefaultEncoding, - "replace"); + py_unicode_username = PyUnicode_FromWideChar( + qsc->lpServiceStartName, wcslen(qsc->lpServiceStartName)); if (py_unicode_username == NULL) goto error; @@ -308,6 +300,12 @@ psutil_winservice_query_status(PyObject *self, PyObject *args) { // right size. QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded); + if (GetLastError() == ERROR_MUI_FILE_NOT_FOUND) { + // Also services.msc fails in the same manner, so we return an + // empty string. + CloseServiceHandle(hService); + return Py_BuildValue("s", ""); + } if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { PyErr_SetFromWindowsErr(0); goto error; @@ -354,13 +352,13 @@ psutil_winservice_query_status(PyObject *self, PyObject *args) { */ PyObject * psutil_winservice_query_descr(PyObject *self, PyObject *args) { - ENUM_SERVICE_STATUS_PROCESS *lpService = NULL; + ENUM_SERVICE_STATUS_PROCESSW *lpService = NULL; BOOL ok; DWORD bytesNeeded = 0; DWORD resumeHandle = 0; DWORD dwBytes = 0; SC_HANDLE hService = NULL; - SERVICE_DESCRIPTION *scd = NULL; + SERVICE_DESCRIPTIONW *scd = NULL; char *service_name; PyObject *py_retstr = NULL; @@ -371,19 +369,25 @@ psutil_winservice_query_descr(PyObject *self, PyObject *args) { if (hService == NULL) goto error; - // This first call to QueryServiceConfig2() is necessary in order + // This first call to QueryServiceConfig2W() is necessary in order // to get the right size. bytesNeeded = 0; - QueryServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, NULL, 0, - &bytesNeeded); + QueryServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, NULL, 0, + &bytesNeeded); + if (GetLastError() == ERROR_MUI_FILE_NOT_FOUND) { + // Also services.msc fails in the same manner, so we return an + // empty string. + CloseServiceHandle(hService); + return Py_BuildValue("s", ""); + } if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { PyErr_SetFromWindowsErr(0); goto error; } - scd = (SERVICE_DESCRIPTION *)malloc(bytesNeeded); - ok = QueryServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, - (LPBYTE)scd, bytesNeeded, &bytesNeeded); + scd = (SERVICE_DESCRIPTIONW *)malloc(bytesNeeded); + ok = QueryServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, + (LPBYTE)scd, bytesNeeded, &bytesNeeded); if (ok == 0) { PyErr_SetFromWindowsErr(0); goto error; @@ -393,11 +397,8 @@ psutil_winservice_query_descr(PyObject *self, PyObject *args) { py_retstr = Py_BuildValue("s", ""); } else { - py_retstr = PyUnicode_Decode( - scd->lpDescription, - _tcslen(scd->lpDescription), - Py_FileSystemDefaultEncoding, - "replace"); + py_retstr = PyUnicode_FromWideChar( + scd->lpDescription, wcslen(scd->lpDescription)); } if (!py_retstr) goto error; diff --git a/psutil/tests/README.rst b/psutil/tests/README.rst index 2ad91c143..515abf772 100644 --- a/psutil/tests/README.rst +++ b/psutil/tests/README.rst @@ -1,19 +1,23 @@ Instructions for running tests ============================== -- The recommended way to run tests (also on Windows) is to cd into psutil root - directory and run ``make test``. +* There are two ways of running tests. As a "user", if psutil is already + installed and you just want to test it works:: -- Depending on the Python version, dependencies for running tests include - ``ipaddress``, ``mock`` and ``unittest2`` modules. - On Windows also ``pywin32`` and ``wmi`` modules are recommended - (although optional). - Run ``make setup-dev-env`` to install all deps (also on Windows). + python -m psutil.tests --install-deps # install test deps + python -m psutil.tests -- To run tests on all supported Python versions install tox - (``pip install tox``) then run ``tox`` from psutil root directory. + As a "developer", if you have a copy of the source code and you whish to hack + on psutil:: -- Every time a commit is pushed tests are automatically run on Travis + make setup-dev-env # install test deps (+ other things) + make test + +* To run tests on all supported Python versions install tox + (``pip install tox``) then run ``tox`` from within psutil root directory. + +* Every time a commit is pushed tests are automatically run on Travis (Linux, OSX) and appveyor (Windows): - - https://travis-ci.org/giampaolo/psutil/ - - https://ci.appveyor.com/project/giampaolo/psutil + + * Travis builds: https://travis-ci.org/giampaolo/psutil + * AppVeyor builds: https://ci.appveyor.com/project/giampaolo/psutil diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 883d92850..9e8d8596b 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -9,13 +9,16 @@ """ from __future__ import print_function + import atexit import contextlib +import ctypes import errno import functools -import ipaddress # python >= 3.3 / requires "pip install ipaddress" import os +import random import re +import select import shutil import socket import stat @@ -25,21 +28,21 @@ import textwrap import threading import time +import traceback import warnings from socket import AF_INET +from socket import AF_INET6 from socket import SOCK_DGRAM from socket import SOCK_STREAM -try: - from unittest import mock # py3 -except ImportError: - import mock # NOQA - requires "pip install mock" - import psutil -from psutil import LINUX +from psutil import OSX from psutil import POSIX +from psutil import SUNOS from psutil import WINDOWS +from psutil._common import supports_ipv6 from psutil._compat import PY3 +from psutil._compat import u from psutil._compat import unicode from psutil._compat import which @@ -47,42 +50,54 @@ import unittest2 as unittest # requires "pip install unittest2" else: import unittest + +try: + from unittest import mock # py3 +except ImportError: + import mock # NOQA - requires "pip install mock" + if sys.version_info >= (3, 4): import enum else: enum = None -if PY3: - import importlib - # python <=3.3 - if not hasattr(importlib, 'reload'): - import imp as importlib -else: - import imp as importlib - __all__ = [ # constants 'APPVEYOR', 'DEVNULL', 'GLOBAL_TIMEOUT', 'MEMORY_TOLERANCE', 'NO_RETRIES', - 'PYPY', 'PYTHON', 'RLIMIT_SUPPORT', 'ROOT_DIR', 'SCRIPTS_DIR', - 'TESTFILE_PREFIX', 'TESTFN', 'TESTFN_UNICODE', 'TOX', 'TRAVIS', - 'VALID_PROC_STATUSES', 'VERBOSITY', - # classes + 'PYPY', 'PYTHON_EXE', 'ROOT_DIR', 'SCRIPTS_DIR', 'TESTFILE_PREFIX', + 'TESTFN', 'TESTFN_UNICODE', 'TOX', 'TRAVIS', 'VALID_PROC_STATUSES', + 'VERBOSITY', + "HAS_CPU_AFFINITY", "HAS_CPU_FREQ", "HAS_ENVIRON", "HAS_PROC_IO_COUNTERS", + "HAS_IONICE", "HAS_MEMORY_MAPS", "HAS_PROC_CPU_NUM", "HAS_RLIMIT", + "HAS_SENSORS_BATTERY", "HAS_BATTERY", "HAS_SENSORS_FANS", + "HAS_SENSORS_TEMPERATURES", "HAS_MEMORY_FULL_INFO", + # subprocesses + 'pyrun', 'reap_children', 'get_test_subprocess', 'create_zombie_proc', + 'create_proc_children_pair', + # threads 'ThreadTask' # test utils - 'check_connection_ntuple', 'check_net_address', 'unittest', 'cleanup', - 'skip_on_access_denied', 'skip_on_not_implemented', 'retry_before_failing', - 'run_test_module_by_name', + 'unittest', 'skip_on_access_denied', 'skip_on_not_implemented', + 'retry_before_failing', 'run_test_module_by_name', 'get_suite', + 'run_suite', + # install utils + 'install_pip', 'install_test_deps', # fs utils - 'chdir', 'safe_rmpath', 'create_exe', - # subprocesses - 'pyrun', 'reap_children', 'get_test_subprocess', + 'chdir', 'safe_rmpath', 'create_exe', 'decode_path', 'encode_path', + 'unique_filename', # os 'get_winver', 'get_kernel_version', # sync primitives 'call_until', 'wait_for_pid', 'wait_for_file', + # network + 'check_connection_ntuple', 'check_net_address', + 'get_free_port', 'unix_socket_path', 'bind_socket', 'bind_unix_socket', + 'tcp_socketpair', 'unix_socketpair', 'create_sockets', + # compat + 'reload_module', 'import_module_by_path', # others - 'warn', 'decode_path', 'encode_path', + 'warn', 'copyload_shared_lib', 'is_namedtuple', ] @@ -90,80 +105,162 @@ # --- constants # =================================================================== +# --- platforms + +TOX = os.getenv('TOX') or '' in ('1', 'true') +PYPY = '__pypy__' in sys.builtin_module_names +WIN_VISTA = (6, 0, 0) if WINDOWS else None +# whether we're running this test suite on Travis (https://travis-ci.org/) +TRAVIS = bool(os.environ.get('TRAVIS')) +# whether we're running this test suite on Appveyor for Windows +# (http://www.appveyor.com/) +APPVEYOR = bool(os.environ.get('APPVEYOR')) -# conf for retry_before_failing() decorator +# --- configurable defaults + +# how many times retry_before_failing() decorator will retry NO_RETRIES = 10 -# bytes tolerance for OS memory related tests +# bytes tolerance for system-wide memory related tests MEMORY_TOLERANCE = 500 * 1024 # 500KB # the timeout used in functions which have to wait GLOBAL_TIMEOUT = 3 +# test output verbosity +VERBOSITY = 1 if os.getenv('SILENT') or TOX else 2 +# be more tolerant if we're on travis / appveyor in order to avoid +# false positives +if TRAVIS or APPVEYOR: + NO_RETRIES *= 3 + GLOBAL_TIMEOUT *= 3 -AF_INET6 = getattr(socket, "AF_INET6") -AF_UNIX = getattr(socket, "AF_UNIX", None) -PYTHON = os.path.realpath(sys.executable) -DEVNULL = open(os.devnull, 'r+') +# --- files TESTFILE_PREFIX = '$testfn' TESTFN = os.path.join(os.path.realpath(os.getcwd()), TESTFILE_PREFIX) _TESTFN = TESTFN + '-internal' -TESTFN_UNICODE = TESTFN + "-ƒőő" -if not PY3: - try: - TESTFN_UNICODE = unicode(TESTFN, sys.getfilesystemencoding()) - except UnicodeDecodeError: - TESTFN_UNICODE = TESTFN + "-???" +TESTFN_UNICODE = TESTFN + u("-ƒőő") +ASCII_FS = sys.getfilesystemencoding().lower() in ('ascii', 'us-ascii') -TOX = os.getenv('TOX') or '' in ('1', 'true') -PYPY = '__pypy__' in sys.builtin_module_names +# --- paths -ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), - '..', '..')) +ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) SCRIPTS_DIR = os.path.join(ROOT_DIR, 'scripts') +HERE = os.path.abspath(os.path.dirname(__file__)) + +# --- support + +HAS_CPU_AFFINITY = hasattr(psutil.Process, "cpu_affinity") +HAS_CPU_FREQ = hasattr(psutil, "cpu_freq") +HAS_CONNECTIONS_UNIX = POSIX and not SUNOS +HAS_ENVIRON = hasattr(psutil.Process, "environ") +HAS_PROC_IO_COUNTERS = hasattr(psutil.Process, "io_counters") +HAS_IONICE = hasattr(psutil.Process, "ionice") +HAS_MEMORY_FULL_INFO = 'uss' in psutil.Process().memory_full_info()._fields +HAS_MEMORY_MAPS = hasattr(psutil.Process, "memory_maps") +HAS_PROC_CPU_NUM = hasattr(psutil.Process, "cpu_num") +HAS_RLIMIT = hasattr(psutil.Process, "rlimit") +HAS_THREADS = hasattr(psutil.Process, "threads") +HAS_SENSORS_BATTERY = hasattr(psutil, "sensors_battery") +HAS_BATTERY = HAS_SENSORS_BATTERY and psutil.sensors_battery() +HAS_SENSORS_FANS = hasattr(psutil, "sensors_fans") +HAS_SENSORS_TEMPERATURES = hasattr(psutil, "sensors_temperatures") + +# --- misc + + +def _get_py_exe(): + def attempt(exe): + try: + subprocess.check_call( + [exe, "-V"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except Exception: + return None + else: + return exe + + if OSX: + exe = \ + attempt(sys.executable) or \ + attempt(os.path.realpath(sys.executable)) or \ + attempt(which("python%s.%s" % sys.version_info[:2])) or \ + attempt(psutil.Process().exe()) + if not exe: + raise ValueError("can't find python exe real abspath") + return exe + else: + exe = os.path.realpath(sys.executable) + assert os.path.exists(exe), exe + return exe -WIN_VISTA = (6, 0, 0) if WINDOWS else None + +PYTHON_EXE = _get_py_exe() +DEVNULL = open(os.devnull, 'r+') VALID_PROC_STATUSES = [getattr(psutil, x) for x in dir(psutil) if x.startswith('STATUS_')] -# whether we're running this test suite on Travis (https://travis-ci.org/) -TRAVIS = bool(os.environ.get('TRAVIS')) -# whether we're running this test suite on Appveyor for Windows -# (http://www.appveyor.com/) -APPVEYOR = bool(os.environ.get('APPVEYOR')) +AF_UNIX = getattr(socket, "AF_UNIX", object()) +SOCK_SEQPACKET = getattr(socket, "SOCK_SEQPACKET", object()) + +_subprocesses_started = set() +_pids_started = set() +_testfiles_created = set() + + +@atexit.register +def _cleanup_files(): + DEVNULL.close() + for name in os.listdir(u('.')): + if isinstance(name, unicode): + prefix = u(TESTFILE_PREFIX) + else: + prefix = TESTFILE_PREFIX + if name.startswith(prefix): + try: + safe_rmpath(name) + except Exception: + traceback.print_exc() + for path in _testfiles_created: + try: + safe_rmpath(path) + except Exception: + traceback.print_exc() -if TRAVIS or APPVEYOR: - GLOBAL_TIMEOUT = GLOBAL_TIMEOUT * 4 -VERBOSITY = 1 if os.getenv('SILENT') or TOX else 2 -# assertRaisesRegexp renamed to assertRaisesRegex in 3.3; add support -# for the new name -if not hasattr(unittest.TestCase, 'assertRaisesRegex'): - unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp +# this is executed first +@atexit.register +def _cleanup_procs(): + reap_children(recursive=True) # =================================================================== -# --- classes +# --- threads # =================================================================== class ThreadTask(threading.Thread): - """A thread object used for running process thread tests.""" + """A thread task which does nothing expect staying alive.""" def __init__(self): threading.Thread.__init__(self) self._running = False - self._interval = None + self._interval = 0.001 self._flag = threading.Event() def __repr__(self): name = self.__class__.__name__ return '<%s running=%s at %#x>' % (name, self._running, id(self)) - def start(self, interval=0.001): + def __enter__(self): + self.start() + return self + + def __exit__(self, *args, **kwargs): + self.stop() + + def start(self): """Start thread and keep it running until an explicit stop() request. Polls for shutdown every 'timeout' seconds. """ if self._running: raise ValueError("already started") - self._interval = interval threading.Thread.start(self) self._flag.wait() @@ -186,70 +283,162 @@ def stop(self): # =================================================================== -_subprocesses_started = set() +def _cleanup_on_err(fun): + @functools.wraps(fun) + def wrapper(*args, **kwargs): + try: + return fun(*args, **kwargs) + except Exception: + reap_children() + raise + return wrapper +@_cleanup_on_err def get_test_subprocess(cmd=None, **kwds): - """Return a subprocess.Popen object to use in tests. - By default stdout and stderr are redirected to /dev/null and the - python interpreter is used as test process. + """Creates a python subprocess which does nothing for 60 secs and + return it as subprocess.Popen instance. + If "cmd" is specified that is used instead of python. + By default stdin and stdout are redirected to /dev/null. It also attemps to make sure the process is in a reasonably initialized state. + The process is registered for cleanup on reap_children(). """ kwds.setdefault("stdin", DEVNULL) kwds.setdefault("stdout", DEVNULL) + kwds.setdefault("cwd", os.getcwd()) + kwds.setdefault("env", os.environ) + if WINDOWS: + # Prevents the subprocess to open error dialogs. + kwds.setdefault("creationflags", 0x8000000) # CREATE_NO_WINDOW if cmd is None: - assert not os.path.exists(_TESTFN) - pyline = "from time import sleep;" - pyline += "open(r'%s', 'w').close();" % _TESTFN - pyline += "sleep(60)" - cmd = [PYTHON, "-c", pyline] + safe_rmpath(_TESTFN) + pyline = "from time import sleep;" \ + "open(r'%s', 'w').close();" \ + "sleep(60);" % _TESTFN + cmd = [PYTHON_EXE, "-c", pyline] sproc = subprocess.Popen(cmd, **kwds) - wait_for_file(_TESTFN, delete_file=True, empty=True) + _subprocesses_started.add(sproc) + wait_for_file(_TESTFN, delete=True, empty=True) else: sproc = subprocess.Popen(cmd, **kwds) + _subprocesses_started.add(sproc) wait_for_pid(sproc.pid) - _subprocesses_started.add(sproc) return sproc -_testfiles = [] +@_cleanup_on_err +def create_proc_children_pair(): + """Create a subprocess which creates another one as in: + A (us) -> B (child) -> C (grandchild). + Return a (child, grandchild) tuple. + The 2 processes are fully initialized and will live for 60 secs + and are registered for cleanup on reap_children(). + """ + _TESTFN2 = os.path.basename(_TESTFN) + '2' # need to be relative + s = textwrap.dedent("""\ + import subprocess, os, sys, time + s = "import os, time;" + s += "f = open('%s', 'w');" + s += "f.write(str(os.getpid()));" + s += "f.close();" + s += "time.sleep(60);" + subprocess.Popen(['%s', '-c', s]) + time.sleep(60) + """ % (_TESTFN2, PYTHON_EXE)) + # On Windows if we create a subprocess with CREATE_NO_WINDOW flag + # set (which is the default) a "conhost.exe" extra process will be + # spawned as a child. We don't want that. + if WINDOWS: + subp = pyrun(s, creationflags=0) + else: + subp = pyrun(s) + child1 = psutil.Process(subp.pid) + data = wait_for_file(_TESTFN2, delete=False, empty=False) + os.remove(_TESTFN2) + child2_pid = int(data) + _pids_started.add(child2_pid) + child2 = psutil.Process(child2_pid) + return (child1, child2) + + +def create_zombie_proc(): + """Create a zombie process and return its PID.""" + assert psutil.POSIX + unix_file = tempfile.mktemp(prefix=TESTFILE_PREFIX) if OSX else TESTFN + src = textwrap.dedent("""\ + import os, sys, time, socket, contextlib + child_pid = os.fork() + if child_pid > 0: + time.sleep(3000) + else: + # this is the zombie process + s = socket.socket(socket.AF_UNIX) + with contextlib.closing(s): + s.connect('%s') + if sys.version_info < (3, ): + pid = str(os.getpid()) + else: + pid = bytes(str(os.getpid()), 'ascii') + s.sendall(pid) + """ % unix_file) + with contextlib.closing(socket.socket(socket.AF_UNIX)) as sock: + sock.settimeout(GLOBAL_TIMEOUT) + sock.bind(unix_file) + sock.listen(1) + pyrun(src) + conn, _ = sock.accept() + try: + select.select([conn.fileno()], [], [], GLOBAL_TIMEOUT) + zpid = int(conn.recv(1024)) + _pids_started.add(zpid) + zproc = psutil.Process(zpid) + call_until(lambda: zproc.status(), "ret == psutil.STATUS_ZOMBIE") + return zpid + finally: + conn.close() -def pyrun(src): - """Run python 'src' code in a separate interpreter. - Return interpreter subprocess. +@_cleanup_on_err +def pyrun(src, **kwds): + """Run python 'src' code string in a separate interpreter. + Returns a subprocess.Popen instance. """ - if PY3: - src = bytes(src, 'ascii') + kwds.setdefault("stdout", None) + kwds.setdefault("stderr", None) with tempfile.NamedTemporaryFile( - prefix=TESTFILE_PREFIX, delete=False) as f: - _testfiles.append(f.name) + prefix=TESTFILE_PREFIX, mode="wt", delete=False) as f: + _testfiles_created.add(f.name) f.write(src) f.flush() - subp = get_test_subprocess([PYTHON, f.name], stdout=None, - stderr=None) + subp = get_test_subprocess([PYTHON_EXE, f.name], **kwds) wait_for_pid(subp.pid) - return subp + return subp -def sh(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE): +@_cleanup_on_err +def sh(cmd, **kwds): """run cmd in a subprocess and return its output. raises RuntimeError on error. """ - p = subprocess.Popen(cmdline, shell=True, stdout=stdout, stderr=stderr) + shell = True if isinstance(cmd, (str, unicode)) else False + # Prevents subprocess to open error dialogs in case of error. + flags = 0x8000000 if WINDOWS and shell else 0 + kwds.setdefault("shell", shell) + kwds.setdefault("stdout", subprocess.PIPE) + kwds.setdefault("stderr", subprocess.PIPE) + kwds.setdefault("universal_newlines", True) + kwds.setdefault("creationflags", flags) + p = subprocess.Popen(cmd, **kwds) + _subprocesses_started.add(p) stdout, stderr = p.communicate() if p.returncode != 0: raise RuntimeError(stderr) if stderr: - if PY3: - stderr = str(stderr, sys.stderr.encoding or - sys.getfilesystemencoding()) warn(stderr) - if PY3: - stdout = str(stdout, sys.stdout.encoding or - sys.getfilesystemencoding()) - return stdout.strip() + if stdout.endswith('\n'): + stdout = stdout[:-1] + return stdout def reap_children(recursive=False): @@ -260,19 +449,34 @@ def reap_children(recursive=False): If resursive is True it also tries to terminate and wait() all grandchildren started by this process. """ + # This is here to make sure wait_procs() behaves properly and + # investigate: + # https://ci.appveyor.com/project/giampaolo/psutil/build/job/ + # jiq2cgd6stsbtn60 + def assert_gone(pid): + assert not psutil.pid_exists(pid), pid + assert pid not in psutil.pids(), pid + try: + p = psutil.Process(pid) + assert not p.is_running(), pid + except psutil.NoSuchProcess: + pass + else: + assert 0, "pid %s is not gone" % pid + # Get the children here, before terminating the children sub # processes as we don't want to lose the intermediate reference # in case of grandchildren. if recursive: - children = psutil.Process().children(recursive=True) + children = set(psutil.Process().children(recursive=True)) else: - children = [] + children = set() # Terminate subprocess.Popen instances "cleanly" by closing their # fds and wiat()ing for them in order to avoid zombies. - subprocs = _subprocesses_started.copy() - _subprocesses_started.clear() - for subp in subprocs: + while _subprocesses_started: + subp = _subprocesses_started.pop() + _pids_started.add(subp.pid) try: subp.terminate() except OSError as err: @@ -294,7 +498,17 @@ def reap_children(recursive=False): if err.errno != errno.ECHILD: raise - # Terminates grandchildren. + # Terminate started pids. + while _pids_started: + pid = _pids_started.pop() + try: + p = psutil.Process(pid) + except psutil.NoSuchProcess: + assert_gone(pid) + else: + children.add(p) + + # Terminate children. if children: for p in children: try: @@ -308,64 +522,57 @@ def reap_children(recursive=False): p.kill() except psutil.NoSuchProcess: pass - _, alive = psutil.wait_procs(alive, timeout=GLOBAL_TIMEOUT) + gone, alive = psutil.wait_procs(alive, timeout=GLOBAL_TIMEOUT) if alive: for p in alive: warn("process %r survived kill()" % p) + for p in children: + assert_gone(p.pid) + # =================================================================== # --- OS # =================================================================== -if not POSIX: - def get_kernel_version(): - return () -else: - def get_kernel_version(): - """Return a tuple such as (2, 6, 36).""" - s = "" - uname = os.uname()[2] - for c in uname: - if c.isdigit() or c == '.': - s += c - else: - break - if not s: - raise ValueError("can't parse %r" % uname) - minor = 0 - micro = 0 - nums = s.split('.') - major = int(nums[0]) - if len(nums) >= 2: - minor = int(nums[1]) - if len(nums) >= 3: - micro = int(nums[2]) - return (major, minor, micro) - - -if LINUX: - RLIMIT_SUPPORT = get_kernel_version() >= (2, 6, 36) -else: - RLIMIT_SUPPORT = False - - -if not WINDOWS: - def get_winver(): - raise NotImplementedError("not a Windows OS") -else: - def get_winver(): - wv = sys.getwindowsversion() - if hasattr(wv, 'service_pack_major'): # python >= 2.7 - sp = wv.service_pack_major or 0 +def get_kernel_version(): + """Return a tuple such as (2, 6, 36).""" + if not POSIX: + raise NotImplementedError("not POSIX") + s = "" + uname = os.uname()[2] + for c in uname: + if c.isdigit() or c == '.': + s += c else: - r = re.search("\s\d$", wv[4]) - if r: - sp = int(r.group(0)) - else: - sp = 0 - return (wv[0], wv[1], sp) + break + if not s: + raise ValueError("can't parse %r" % uname) + minor = 0 + micro = 0 + nums = s.split('.') + major = int(nums[0]) + if len(nums) >= 2: + minor = int(nums[1]) + if len(nums) >= 3: + micro = int(nums[2]) + return (major, minor, micro) + + +def get_winver(): + if not WINDOWS: + raise NotImplementedError("not WINDOWS") + wv = sys.getwindowsversion() + if hasattr(wv, 'service_pack_major'): # python >= 2.7 + sp = wv.service_pack_major or 0 + else: + r = re.search(r"\s\d$", wv[4]) + if r: + sp = int(r.group(0)) + else: + sp = 0 + return (wv[0], wv[1], sp) # =================================================================== @@ -419,11 +626,11 @@ def wrapper(*args, **kwargs): if self.logfun is not None: self.logfun(exc) self.sleep() + continue + if PY3: + raise exc else: - if PY3: - raise exc - else: - raise + raise # This way the user of the decorated function can change config # parameters. @@ -445,13 +652,13 @@ def wait_for_pid(pid): @retry(exception=(EnvironmentError, AssertionError), logfun=None, timeout=GLOBAL_TIMEOUT, interval=0.001) -def wait_for_file(fname, delete_file=True, empty=False): +def wait_for_file(fname, delete=True, empty=False): """Wait for a file to be written on disk with some content.""" with open(fname, "rb") as f: data = f.read() if not empty: assert data - if delete_file: + if delete: os.remove(fname) return data @@ -506,9 +713,12 @@ def chdir(dirname): def create_exe(outpath, c_code=None): + """Creates an executable file in the given location.""" assert not os.path.exists(outpath), outpath - if which("gcc"): - if c_code is None: + if c_code: + if not which("gcc"): + raise ValueError("gcc is not installed") + if isinstance(c_code, bool): # c_code is True c_code = textwrap.dedent( """ #include @@ -517,6 +727,7 @@ def create_exe(outpath, c_code=None): return 1; } """) + assert isinstance(c_code, str), c_code with tempfile.NamedTemporaryFile( suffix='.c', delete=False, mode='wt') as f: f.write(c_code) @@ -525,13 +736,17 @@ def create_exe(outpath, c_code=None): finally: safe_rmpath(f.name) else: - # fallback - use python's executable - shutil.copyfile(sys.executable, outpath) + # copy python executable + shutil.copyfile(PYTHON_EXE, outpath) if POSIX: st = os.stat(outpath) os.chmod(outpath, st.st_mode | stat.S_IEXEC) +def unique_filename(prefix=TESTFILE_PREFIX, suffix=""): + return tempfile.mktemp(prefix=prefix, suffix=suffix) + + # =================================================================== # --- testing # =================================================================== @@ -539,27 +754,56 @@ def create_exe(outpath, c_code=None): class TestCase(unittest.TestCase): + # Print a full path representation of the single unit tests + # being run. def __str__(self): return "%s.%s.%s" % ( self.__class__.__module__, self.__class__.__name__, self._testMethodName) + # assertRaisesRegexp renamed to assertRaisesRegex in 3.3; + # add support for the new name. + if not hasattr(unittest.TestCase, 'assertRaisesRegex'): + assertRaisesRegex = unittest.TestCase.assertRaisesRegexp -# Hack that overrides default unittest.TestCase in order to print -# a full path representation of the single unit tests being run. + +# override default unittest.TestCase unittest.TestCase = TestCase -def retry_before_failing(retries=NO_RETRIES): - """Decorator which runs a test function and retries N times before - actually failing. - """ - return retry(exception=AssertionError, timeout=None, retries=retries) +def _setup_tests(): + if 'PSUTIL_TESTING' not in os.environ: + # This won't work on Windows but set_testing() below will do it. + os.environ['PSUTIL_TESTING'] = '1' + psutil._psplatform.cext.set_testing() + + +def get_suite(): + testmods = [os.path.splitext(x)[0] for x in os.listdir(HERE) + if x.endswith('.py') and x.startswith('test_') and not + x.startswith('test_memory_leaks')] + if "WHEELHOUSE_UPLOADER_USERNAME" in os.environ: + testmods = [x for x in testmods if not x.endswith(( + "osx", "posix", "linux"))] + suite = unittest.TestSuite() + for tm in testmods: + # ...so that the full test paths are printed on screen + tm = "psutil.tests.%s" % tm + suite.addTest(unittest.defaultTestLoader.loadTestsFromName(tm)) + return suite + + +def run_suite(): + _setup_tests() + result = unittest.TextTestRunner(verbosity=VERBOSITY).run(get_suite()) + success = result.wasSuccessful() + sys.exit(0 if success else 1) def run_test_module_by_name(name): # testmodules = [os.path.splitext(x)[0] for x in os.listdir(HERE) # if x.endswith('.py') and x.startswith('test_')] + _setup_tests() name = os.path.splitext(os.path.basename(name))[0] suite = unittest.TestSuite() suite.addTest(unittest.defaultTestLoader.loadTestsFromName(name)) @@ -568,6 +812,13 @@ def run_test_module_by_name(name): sys.exit(0 if success else 1) +def retry_before_failing(retries=NO_RETRIES): + """Decorator which runs a test function and retries N times before + actually failing. + """ + return retry(exception=AssertionError, timeout=None, retries=retries) + + def skip_on_access_denied(only_if=None): """Decorator to Ignore AccessDenied exceptions.""" def decorator(fun): @@ -579,9 +830,7 @@ def wrapper(*args, **kwargs): if only_if is not None: if not only_if: raise - msg = "%r was skipped because it raised AccessDenied" \ - % fun.__name__ - raise unittest.SkipTest(msg) + raise unittest.SkipTest("raises AccessDenied") return wrapper return decorator @@ -604,13 +853,150 @@ def wrapper(*args, **kwargs): return decorator +# =================================================================== +# --- network +# =================================================================== + + +def get_free_port(host='127.0.0.1'): + """Return an unused TCP port.""" + with contextlib.closing(socket.socket()) as sock: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((host, 0)) + return sock.getsockname()[1] + + +@contextlib.contextmanager +def unix_socket_path(suffix=""): + """A context manager which returns a non-existent file name + and tries to delete it on exit. + """ + assert psutil.POSIX + path = unique_filename(suffix=suffix) + try: + yield path + finally: + try: + os.unlink(path) + except OSError: + pass + + +def bind_socket(family=AF_INET, type=SOCK_STREAM, addr=None): + """Binds a generic socket.""" + if addr is None and family in (AF_INET, AF_INET6): + addr = ("", 0) + sock = socket.socket(family, type) + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(addr) + if type == socket.SOCK_STREAM: + sock.listen(10) + return sock + except Exception: + sock.close() + raise + + +def bind_unix_socket(name, type=socket.SOCK_STREAM): + """Bind a UNIX socket.""" + assert psutil.POSIX + assert not os.path.exists(name), name + sock = socket.socket(socket.AF_UNIX, type) + try: + sock.bind(name) + if type == socket.SOCK_STREAM: + sock.listen(10) + except Exception: + sock.close() + raise + return sock + + +def tcp_socketpair(family, addr=("", 0)): + """Build a pair of TCP sockets connected to each other. + Return a (server, client) tuple. + """ + with contextlib.closing(socket.socket(family, SOCK_STREAM)) as ll: + ll.bind(addr) + ll.listen(10) + addr = ll.getsockname() + c = socket.socket(family, SOCK_STREAM) + try: + c.connect(addr) + caddr = c.getsockname() + while True: + a, addr = ll.accept() + # check that we've got the correct client + if addr == caddr: + return (a, c) + a.close() + except OSError: + c.close() + raise + + +def unix_socketpair(name): + """Build a pair of UNIX sockets connected to each other through + the same UNIX file name. + Return a (server, client) tuple. + """ + assert psutil.POSIX + server = client = None + try: + server = bind_unix_socket(name, type=socket.SOCK_STREAM) + server.setblocking(0) + client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + client.setblocking(0) + client.connect(name) + # new = server.accept() + except Exception: + if server is not None: + server.close() + if client is not None: + client.close() + raise + return (server, client) + + +@contextlib.contextmanager +def create_sockets(): + """Open as many socket families / types as possible.""" + socks = [] + fname1 = fname2 = None + try: + socks.append(bind_socket(socket.AF_INET, socket.SOCK_STREAM)) + socks.append(bind_socket(socket.AF_INET, socket.SOCK_DGRAM)) + if supports_ipv6(): + socks.append(bind_socket(socket.AF_INET6, socket.SOCK_STREAM)) + socks.append(bind_socket(socket.AF_INET6, socket.SOCK_DGRAM)) + if POSIX and HAS_CONNECTIONS_UNIX: + fname1 = unix_socket_path().__enter__() + fname2 = unix_socket_path().__enter__() + s1, s2 = unix_socketpair(fname1) + s3 = bind_unix_socket(fname2, type=socket.SOCK_DGRAM) + # self.addCleanup(safe_rmpath, fname1) + # self.addCleanup(safe_rmpath, fname2) + for s in (s1, s2, s3): + socks.append(s) + yield socks + finally: + for s in socks: + s.close() + if fname1 is not None: + safe_rmpath(fname1) + if fname2 is not None: + safe_rmpath(fname2) + + def check_net_address(addr, family): """Check a net address validity. Supported families are IPv4, IPv6 and MAC addresses. """ + import ipaddress # python >= 3.3 / requires "pip install ipaddress" if enum and PY3: assert isinstance(family, enum.IntEnum), family - if family == AF_INET: + if family == socket.AF_INET: octs = [int(x) for x in addr.split('.')] assert len(octs) == 4, addr for num in octs: @@ -618,46 +1004,48 @@ def check_net_address(addr, family): if not PY3: addr = unicode(addr) ipaddress.IPv4Address(addr) - elif family == AF_INET6: + elif family == socket.AF_INET6: assert isinstance(addr, str), addr if not PY3: addr = unicode(addr) ipaddress.IPv6Address(addr) elif family == psutil.AF_LINK: - assert re.match('([a-fA-F0-9]{2}[:|\-]?){6}', addr) is not None, addr + assert re.match(r'([a-fA-F0-9]{2}[:|\-]?){6}', addr) is not None, addr else: raise ValueError("unknown family %r", family) def check_connection_ntuple(conn): """Check validity of a connection namedtuple.""" - valid_conn_states = [getattr(psutil, x) for x in dir(psutil) if - x.startswith('CONN_')] + # check ntuple + assert len(conn) in (6, 7), conn + has_pid = len(conn) == 7 + has_fd = getattr(conn, 'fd', -1) != -1 assert conn[0] == conn.fd assert conn[1] == conn.family assert conn[2] == conn.type assert conn[3] == conn.laddr assert conn[4] == conn.raddr assert conn[5] == conn.status - assert conn.type in (SOCK_STREAM, SOCK_DGRAM), repr(conn.type) - assert conn.family in (AF_INET, AF_INET6, AF_UNIX), repr(conn.family) - assert conn.status in valid_conn_states, conn.status + if has_pid: + assert conn[6] == conn.pid - # check IP address and port sanity - for addr in (conn.laddr, conn.raddr): - if not addr: - continue - if conn.family in (AF_INET, AF_INET6): - assert isinstance(addr, tuple), addr - ip, port = addr - assert isinstance(port, int), port - assert 0 <= port <= 65535, port - check_net_address(ip, conn.family) - elif conn.family == AF_UNIX: - assert isinstance(addr, (str, None)), addr - else: - raise ValueError("unknown family %r", conn.family) + # check fd + if has_fd: + assert conn.fd >= 0, conn + if hasattr(socket, 'fromfd') and not WINDOWS: + try: + dupsock = socket.fromfd(conn.fd, conn.family, conn.type) + except (socket.error, OSError) as err: + if err.args[0] != errno.EBADF: + raise + else: + with contextlib.closing(dupsock): + assert dupsock.family == conn.family + assert dupsock.type == conn.type + # check family + assert conn.family in (AF_INET, AF_INET6, AF_UNIX), repr(conn.family) if conn.family in (AF_INET, AF_INET6): # actually try to bind the local socket; ignore IPv6 # sockets as their address might be represented as @@ -672,36 +1060,64 @@ def check_connection_ntuple(conn): if err.errno != errno.EADDRNOTAVAIL: raise elif conn.family == AF_UNIX: - assert not conn.raddr, repr(conn.raddr) assert conn.status == psutil.CONN_NONE, conn.status - if getattr(conn, 'fd', -1) != -1: - assert conn.fd > 0, conn - if hasattr(socket, 'fromfd') and not WINDOWS: - try: - dupsock = socket.fromfd(conn.fd, conn.family, conn.type) - except (socket.error, OSError) as err: - if err.args[0] != errno.EBADF: - raise - else: - with contextlib.closing(dupsock): - assert dupsock.family == conn.family - assert dupsock.type == conn.type + # check type (SOCK_SEQPACKET may happen in case of AF_UNIX socks) + assert conn.type in (SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET), \ + repr(conn.type) + if conn.type == SOCK_DGRAM: + assert conn.status == psutil.CONN_NONE, conn.status + + # check laddr (IP address and port sanity) + for addr in (conn.laddr, conn.raddr): + if conn.family in (AF_INET, AF_INET6): + assert isinstance(addr, tuple), addr + if not addr: + continue + assert isinstance(addr.port, int), addr.port + assert 0 <= addr.port <= 65535, addr.port + check_net_address(addr.ip, conn.family) + elif conn.family == AF_UNIX: + assert isinstance(addr, str), addr + # check status + assert isinstance(conn.status, str), conn + valids = [getattr(psutil, x) for x in dir(psutil) if x.startswith('CONN_')] + assert conn.status in valids, conn -def cleanup(): - for name in os.listdir('.'): - if name.startswith(TESTFILE_PREFIX): - try: - safe_rmpath(name) - except UnicodeEncodeError as exc: - warn(exc) - for path in _testfiles: - safe_rmpath(path) + +# =================================================================== +# --- compatibility +# =================================================================== + + +def reload_module(module): + """Backport of importlib.reload of Python 3.3+.""" + try: + import importlib + if not hasattr(importlib, 'reload'): # python <=3.3 + raise ImportError + except ImportError: + import imp + return imp.reload(module) + else: + return importlib.reload(module) -atexit.register(cleanup) -atexit.register(lambda: DEVNULL.close()) +def import_module_by_path(path): + name = os.path.splitext(os.path.basename(path))[0] + if sys.version_info[0] == 2: + import imp + return imp.load_source(name, path) + elif sys.version_info[:2] <= (3, 4): + from importlib.machinery import SourceFileLoader + return SourceFileLoader(name, path).load_module() + else: + import importlib.util + spec = importlib.util.spec_from_file_location(name, path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod # =================================================================== @@ -714,19 +1130,69 @@ def warn(msg): warnings.warn(msg, UserWarning) -# In Python 3 paths are unicode objects by default. Surrogate escapes -# are used to handle non-character data. -def encode_path(path): - if PY3: - return path.encode(sys.getfilesystemencoding(), - errors="surrogateescape") - else: - return path - - -def decode_path(path): - if PY3: - return path.decode(sys.getfilesystemencoding(), - errors="surrogateescape") - else: - return path +def is_namedtuple(x): + """Check if object is an instance of namedtuple.""" + t = type(x) + b = t.__bases__ + if len(b) != 1 or b[0] != tuple: + return False + f = getattr(t, '_fields', None) + if not isinstance(f, tuple): + return False + return all(type(n) == str for n in f) + + +if POSIX: + @contextlib.contextmanager + def copyload_shared_lib(dst_prefix=TESTFILE_PREFIX): + """Ctx manager which picks up a random shared CO lib used + by this process, copies it in another location and loads it + in memory via ctypes. Return the new absolutized path. + """ + ext = ".so" + dst = tempfile.mktemp(prefix=dst_prefix, suffix=ext) + libs = [x.path for x in psutil.Process().memory_maps() if + os.path.splitext(x.path)[1] == ext and + 'python' in x.path.lower()] + src = random.choice(libs) + shutil.copyfile(src, dst) + try: + ctypes.CDLL(dst) + yield dst + finally: + safe_rmpath(dst) +else: + @contextlib.contextmanager + def copyload_shared_lib(dst_prefix=TESTFILE_PREFIX): + """Ctx manager which picks up a random shared DLL lib used + by this process, copies it in another location and loads it + in memory via ctypes. + Return the new absolutized, normcased path. + """ + from ctypes import wintypes + from ctypes import WinError + ext = ".dll" + dst = tempfile.mktemp(prefix=dst_prefix, suffix=ext) + libs = [x.path for x in psutil.Process().memory_maps() if + os.path.splitext(x.path)[1].lower() == ext and + 'python' in os.path.basename(x.path).lower() and + 'wow64' not in x.path.lower()] + src = random.choice(libs) + shutil.copyfile(src, dst) + cfile = None + try: + cfile = ctypes.WinDLL(dst) + yield dst + finally: + # Work around OverflowError: + # - https://ci.appveyor.com/project/giampaolo/psutil/build/1207/ + # job/o53330pbnri9bcw7 + # - http://bugs.python.org/issue30286 + # - http://stackoverflow.com/questions/23522055 + if cfile is not None: + FreeLibrary = ctypes.windll.kernel32.FreeLibrary + FreeLibrary.argtypes = [wintypes.HMODULE] + ret = FreeLibrary(cfile._handle) + if ret == 0: + WinError() + safe_rmpath(dst) diff --git a/psutil/tests/__main__.py b/psutil/tests/__main__.py new file mode 100755 index 000000000..2cdf5c425 --- /dev/null +++ b/psutil/tests/__main__.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Run unit tests. This is invoked by: + +$ python -m psutil.tests +""" + +import contextlib +import optparse +import os +import ssl +import sys +import tempfile +try: + from urllib.request import urlopen # py3 +except ImportError: + from urllib2 import urlopen + +from psutil.tests import PYTHON_EXE +from psutil.tests import run_suite + + +HERE = os.path.abspath(os.path.dirname(__file__)) +GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" +TEST_DEPS = [] +if sys.version_info[:2] == (2, 6): + TEST_DEPS.extend(["ipaddress", "unittest2", "argparse", "mock==1.0.1"]) +elif sys.version_info[:2] == (2, 7) or sys.version_info[:2] <= (3, 2): + TEST_DEPS.extend(["ipaddress", "mock"]) +elif sys.version_info[:2] == (3, 3): + TEST_DEPS.extend(["ipaddress"]) + + +def install_pip(): + try: + import pip # NOQA + except ImportError: + f = tempfile.NamedTemporaryFile(suffix='.py') + with contextlib.closing(f): + print("downloading %s to %s" % (GET_PIP_URL, f.name)) + if hasattr(ssl, '_create_unverified_context'): + ctx = ssl._create_unverified_context() + else: + ctx = None + kwargs = dict(context=ctx) if ctx else {} + req = urlopen(GET_PIP_URL, **kwargs) + data = req.read() + f.write(data) + f.flush() + + print("installing pip") + code = os.system('%s %s --user' % (PYTHON_EXE, f.name)) + return code + + +def install_test_deps(deps=None): + """Install test dependencies via pip.""" + if deps is None: + deps = TEST_DEPS + deps = set(deps) + if deps: + is_venv = hasattr(sys, 'real_prefix') + opts = "--user" if not is_venv else "" + install_pip() + code = os.system('%s -m pip install %s --upgrade %s' % ( + PYTHON_EXE, opts, " ".join(deps))) + return code + + +def main(): + usage = "%s -m psutil.tests [opts]" % PYTHON_EXE + parser = optparse.OptionParser(usage=usage, description="run unit tests") + parser.add_option("-i", "--install-deps", + action="store_true", default=False, + help="don't print status messages to stdout") + + opts, args = parser.parse_args() + if opts.install_deps: + install_pip() + install_test_deps() + else: + for dep in TEST_DEPS: + try: + __import__(dep.split("==")[0]) + except ImportError: + sys.exit("%r lib is not installed; run %s -m psutil.tests " + "--install-deps" % (dep, PYTHON_EXE)) + run_suite() + + +main() diff --git a/psutil/tests/runner.py b/psutil/tests/runner.py deleted file mode 100755 index 1c282f685..000000000 --- a/psutil/tests/runner.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python - -# Copyright (C) 2007-2016 Giampaolo Rodola' . -# Use of this source code is governed by MIT license that can be -# found in the LICENSE file. - -"""Script for running all test files (except memory leaks tests).""" - -import os -import sys - -from psutil.tests import unittest -from psutil.tests import VERBOSITY - - -HERE = os.path.abspath(os.path.dirname(__file__)) -testmodules = [os.path.splitext(x)[0] for x in os.listdir(HERE) - if x.endswith('.py') and x.startswith('test_') and not - x.startswith('test_memory_leaks')] -suite = unittest.TestSuite() -for tm in testmodules: - # ...so that "make test" will print the full test paths - tm = "psutil.tests.%s" % tm - suite.addTest(unittest.defaultTestLoader.loadTestsFromName(tm)) -result = unittest.TextTestRunner(verbosity=VERBOSITY).run(suite) -success = result.wasSuccessful() -sys.exit(0 if success else 1) diff --git a/psutil/tests/test_aix.py b/psutil/tests/test_aix.py new file mode 100755 index 000000000..7a8a4c334 --- /dev/null +++ b/psutil/tests/test_aix.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola' +# Copyright (c) 2017, Arnon Yaari +# All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""AIX specific tests.""" + +import re + +from psutil import AIX +from psutil.tests import run_test_module_by_name +from psutil.tests import sh +from psutil.tests import unittest +import psutil + + +@unittest.skipIf(not AIX, "AIX only") +class AIXSpecificTestCase(unittest.TestCase): + + def test_virtual_memory(self): + out = sh('/usr/bin/svmon -O unit=KB') + re_pattern = "memory\s*" + for field in ("size inuse free pin virtual available mmode").split(): + re_pattern += "(?P<%s>\S+)\s+" % (field,) + matchobj = re.search(re_pattern, out) + + self.assertIsNotNone( + matchobj, "svmon command returned unexpected output") + + KB = 1024 + total = int(matchobj.group("size")) * KB + available = int(matchobj.group("available")) * KB + used = int(matchobj.group("inuse")) * KB + free = int(matchobj.group("free")) * KB + + psutil_result = psutil.virtual_memory() + + # MEMORY_TOLERANCE from psutil.tests is not enough. For some reason + # we're seeing differences of ~1.2 MB. 2 MB is still a good tolerance + # when compared to GBs. + MEMORY_TOLERANCE = 2 * KB * KB # 2 MB + self.assertEqual(psutil_result.total, total) + self.assertAlmostEqual( + psutil_result.used, used, delta=MEMORY_TOLERANCE) + self.assertAlmostEqual( + psutil_result.available, available, delta=MEMORY_TOLERANCE) + self.assertAlmostEqual( + psutil_result.free, free, delta=MEMORY_TOLERANCE) + + def test_swap_memory(self): + out = sh('/usr/sbin/lsps -a') + # From the man page, "The size is given in megabytes" so we assume + # we'll always have 'MB' in the result + # TODO maybe try to use "swap -l" to check "used" too, but its units + # are not guaranteed to be "MB" so parsing may not be consistent + matchobj = re.search("(?P\S+)\s+" + "(?P\S+)\s+" + "(?P\S+)\s+" + "(?P\d+)MB", out) + + self.assertIsNotNone( + matchobj, "lsps command returned unexpected output") + + total_mb = int(matchobj.group("size")) + MB = 1024 ** 2 + psutil_result = psutil.swap_memory() + # we divide our result by MB instead of multiplying the lsps value by + # MB because lsps may round down, so we round down too + self.assertEqual(int(psutil_result.total / MB), total_mb) + + def test_cpu_stats(self): + out = sh('/usr/bin/mpstat -a') + + re_pattern = "ALL\s*" + for field in ("min maj mpcs mpcr dev soft dec ph cs ics bound rq " + "push S3pull S3grd S0rd S1rd S2rd S3rd S4rd S5rd " + "sysc").split(): + re_pattern += "(?P<%s>\S+)\s+" % (field,) + matchobj = re.search(re_pattern, out) + + self.assertIsNotNone( + matchobj, "mpstat command returned unexpected output") + + # numbers are usually in the millions so 1000 is ok for tolerance + CPU_STATS_TOLERANCE = 1000 + psutil_result = psutil.cpu_stats() + self.assertAlmostEqual( + psutil_result.ctx_switches, + int(matchobj.group("cs")), + delta=CPU_STATS_TOLERANCE) + self.assertAlmostEqual( + psutil_result.syscalls, + int(matchobj.group("sysc")), + delta=CPU_STATS_TOLERANCE) + self.assertAlmostEqual( + psutil_result.interrupts, + int(matchobj.group("dev")), + delta=CPU_STATS_TOLERANCE) + self.assertAlmostEqual( + psutil_result.soft_interrupts, + int(matchobj.group("soft")), + delta=CPU_STATS_TOLERANCE) + + def test_cpu_count_logical(self): + out = sh('/usr/bin/mpstat -a') + mpstat_lcpu = int(re.search("lcpu=(\d+)", out).group(1)) + psutil_lcpu = psutil.cpu_count(logical=True) + self.assertEqual(mpstat_lcpu, psutil_lcpu) + + def test_net_if_addrs_names(self): + out = sh('/etc/ifconfig -l') + ifconfig_names = set(out.split()) + psutil_names = set(psutil.net_if_addrs().keys()) + self.assertSetEqual(ifconfig_names, psutil_names) + + +if __name__ == '__main__': + run_test_module_by_name(__file__) diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py index 244672e6a..d3868ada1 100755 --- a/psutil/tests/test_bsd.py +++ b/psutil/tests/test_bsd.py @@ -13,8 +13,6 @@ import datetime import os import re -import subprocess -import sys import time import psutil @@ -22,8 +20,8 @@ from psutil import FREEBSD from psutil import NETBSD from psutil import OPENBSD -from psutil._compat import PY3 from psutil.tests import get_test_subprocess +from psutil.tests import HAS_BATTERY from psutil.tests import MEMORY_TOLERANCE from psutil.tests import reap_children from psutil.tests import retry_before_failing @@ -74,7 +72,7 @@ def muse(field): # ===================================================================== -@unittest.skipUnless(BSD, "BSD only") +@unittest.skipIf(not BSD, "BSD only") class BSDSpecificTestCase(unittest.TestCase): """Generic tests common to all BSD variants.""" @@ -86,12 +84,9 @@ def setUpClass(cls): def tearDownClass(cls): reap_children() + @unittest.skipIf(NETBSD, "-o lstart doesn't work on NETBSD") def test_process_create_time(self): - cmdline = "ps -o lstart -p %s" % self.pid - p = subprocess.Popen(cmdline, shell=1, stdout=subprocess.PIPE) - output = p.communicate()[0] - if PY3: - output = str(output, sys.stdout.encoding) + output = sh("ps -o lstart -p %s" % self.pid) start_ps = output.replace('STARTED', '').strip() start_psutil = psutil.Process(self.pid).create_time() start_psutil = time.strftime("%a %b %e %H:%M:%S %Y", @@ -143,8 +138,9 @@ def test_net_if_stats(self): pass else: self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) - self.assertEqual(stats.mtu, - int(re.findall('mtu (\d+)', out)[0])) + if "mtu" in out: + self.assertEqual(stats.mtu, + int(re.findall(r'mtu (\d+)', out)[0])) # ===================================================================== @@ -152,7 +148,7 @@ def test_net_if_stats(self): # ===================================================================== -@unittest.skipUnless(FREEBSD, "FREEBSD only") +@unittest.skipIf(not FREEBSD, "FREEBSD only") class FreeBSDSpecificTestCase(unittest.TestCase): @classmethod @@ -281,47 +277,47 @@ def test_vmem_buffers(self): # --- virtual_memory(); tests against muse - @unittest.skipUnless(MUSE_AVAILABLE, "muse not installed") + @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") def test_muse_vmem_total(self): num = muse('Total') self.assertEqual(psutil.virtual_memory().total, num) - @unittest.skipUnless(MUSE_AVAILABLE, "muse not installed") + @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") @retry_before_failing() def test_muse_vmem_active(self): num = muse('Active') self.assertAlmostEqual(psutil.virtual_memory().active, num, delta=MEMORY_TOLERANCE) - @unittest.skipUnless(MUSE_AVAILABLE, "muse not installed") + @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") @retry_before_failing() def test_muse_vmem_inactive(self): num = muse('Inactive') self.assertAlmostEqual(psutil.virtual_memory().inactive, num, delta=MEMORY_TOLERANCE) - @unittest.skipUnless(MUSE_AVAILABLE, "muse not installed") + @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") @retry_before_failing() def test_muse_vmem_wired(self): num = muse('Wired') self.assertAlmostEqual(psutil.virtual_memory().wired, num, delta=MEMORY_TOLERANCE) - @unittest.skipUnless(MUSE_AVAILABLE, "muse not installed") + @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") @retry_before_failing() def test_muse_vmem_cached(self): num = muse('Cache') self.assertAlmostEqual(psutil.virtual_memory().cached, num, delta=MEMORY_TOLERANCE) - @unittest.skipUnless(MUSE_AVAILABLE, "muse not installed") + @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") @retry_before_failing() def test_muse_vmem_free(self): num = muse('Free') self.assertAlmostEqual(psutil.virtual_memory().free, num, delta=MEMORY_TOLERANCE) - @unittest.skipUnless(MUSE_AVAILABLE, "muse not installed") + @unittest.skipIf(not MUSE_AVAILABLE, "muse not installed") @retry_before_failing() def test_muse_vmem_buffers(self): num = muse('Buffer') @@ -357,13 +353,57 @@ def test_boot_time(self): btime = int(s) self.assertEqual(btime, psutil.boot_time()) + # --- sensors_battery + + @unittest.skipIf(not HAS_BATTERY, "no battery") + def test_sensors_battery(self): + def secs2hours(secs): + m, s = divmod(secs, 60) + h, m = divmod(m, 60) + return "%d:%02d" % (h, m) + + out = sh("acpiconf -i 0") + fields = dict([(x.split('\t')[0], x.split('\t')[-1]) + for x in out.split("\n")]) + metrics = psutil.sensors_battery() + percent = int(fields['Remaining capacity:'].replace('%', '')) + remaining_time = fields['Remaining time:'] + self.assertEqual(metrics.percent, percent) + if remaining_time == 'unknown': + self.assertEqual(metrics.secsleft, psutil.POWER_TIME_UNLIMITED) + else: + self.assertEqual(secs2hours(metrics.secsleft), remaining_time) + + @unittest.skipIf(not HAS_BATTERY, "no battery") + def test_sensors_battery_against_sysctl(self): + self.assertEqual(psutil.sensors_battery().percent, + sysctl("hw.acpi.battery.life")) + self.assertEqual(psutil.sensors_battery().power_plugged, + sysctl("hw.acpi.acline") == 1) + secsleft = psutil.sensors_battery().secsleft + if secsleft < 0: + self.assertEqual(sysctl("hw.acpi.battery.time"), -1) + else: + self.assertEqual(secsleft, sysctl("hw.acpi.battery.time") * 60) + + @unittest.skipIf(HAS_BATTERY, "has battery") + def test_sensors_battery_no_battery(self): + # If no battery is present one of these calls is supposed + # to fail, see: + # https://github.com/giampaolo/psutil/issues/1074 + with self.assertRaises(RuntimeError): + sysctl("hw.acpi.battery.life") + sysctl("hw.acpi.battery.time") + sysctl("hw.acpi.acline") + self.assertIsNone(psutil.sensors_battery()) + # ===================================================================== # --- OpenBSD # ===================================================================== -@unittest.skipUnless(OPENBSD, "OPENBSD only") +@unittest.skipIf(not OPENBSD, "OPENBSD only") class OpenBSDSpecificTestCase(unittest.TestCase): def test_boot_time(self): @@ -378,10 +418,11 @@ def test_boot_time(self): # ===================================================================== -@unittest.skipUnless(NETBSD, "NETBSD only") +@unittest.skipIf(not NETBSD, "NETBSD only") class NetBSDSpecificTestCase(unittest.TestCase): - def parse_meminfo(self, look_for): + @staticmethod + def parse_meminfo(look_for): with open('/proc/meminfo', 'rb') as f: for line in f: if line.startswith(look_for): diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py new file mode 100755 index 000000000..176e26648 --- /dev/null +++ b/psutil/tests/test_connections.py @@ -0,0 +1,525 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Tests for net_connections() and Process.connections() APIs.""" + +import os +import socket +import textwrap +from contextlib import closing +from socket import AF_INET +from socket import AF_INET6 +from socket import SOCK_DGRAM +from socket import SOCK_STREAM + +import psutil +from psutil import FREEBSD +from psutil import LINUX +from psutil import NETBSD +from psutil import OPENBSD +from psutil import OSX +from psutil import POSIX +from psutil import SUNOS +from psutil import WINDOWS +from psutil._common import supports_ipv6 +from psutil._compat import PY3 +from psutil.tests import AF_UNIX +from psutil.tests import bind_socket +from psutil.tests import bind_unix_socket +from psutil.tests import check_connection_ntuple +from psutil.tests import create_sockets +from psutil.tests import get_free_port +from psutil.tests import HAS_CONNECTIONS_UNIX +from psutil.tests import pyrun +from psutil.tests import reap_children +from psutil.tests import run_test_module_by_name +from psutil.tests import safe_rmpath +from psutil.tests import skip_on_access_denied +from psutil.tests import tcp_socketpair +from psutil.tests import TESTFN +from psutil.tests import TRAVIS +from psutil.tests import unittest +from psutil.tests import unix_socket_path +from psutil.tests import unix_socketpair +from psutil.tests import wait_for_file + + +thisproc = psutil.Process() + + +class Base(object): + + def setUp(self): + if not NETBSD: + # NetBSD opens a UNIX socket to /var/log/run. + cons = thisproc.connections(kind='all') + assert not cons, cons + + def tearDown(self): + safe_rmpath(TESTFN) + reap_children() + if not NETBSD: + # Make sure we closed all resources. + # NetBSD opens a UNIX socket to /var/log/run. + cons = thisproc.connections(kind='all') + assert not cons, cons + + def get_conn_from_sock(self, sock): + cons = thisproc.connections(kind='all') + smap = dict([(c.fd, c) for c in cons]) + if NETBSD: + # NetBSD opens a UNIX socket to /var/log/run + # so there may be more connections. + return smap[sock.fileno()] + else: + self.assertEqual(len(cons), 1) + if cons[0].fd != -1: + self.assertEqual(smap[sock.fileno()].fd, sock.fileno()) + return cons[0] + + def check_socket(self, sock, conn=None): + """Given a socket, makes sure it matches the one obtained + via psutil. It assumes this process created one connection + only (the one supposed to be checked). + """ + if conn is None: + conn = self.get_conn_from_sock(sock) + check_connection_ntuple(conn) + + # fd, family, type + if conn.fd != -1: + self.assertEqual(conn.fd, sock.fileno()) + self.assertEqual(conn.family, sock.family) + # see: http://bugs.python.org/issue30204 + self.assertEqual( + conn.type, sock.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE)) + + # local address + laddr = sock.getsockname() + if not laddr and PY3 and isinstance(laddr, bytes): + # See: http://bugs.python.org/issue30205 + laddr = laddr.decode() + if sock.family == AF_INET6: + laddr = laddr[:2] + if sock.family == AF_UNIX and OPENBSD: + # No addresses are set for UNIX sockets on OpenBSD. + pass + else: + self.assertEqual(conn.laddr, laddr) + + # XXX Solaris can't retrieve system-wide UNIX sockets + if sock.family == AF_UNIX and HAS_CONNECTIONS_UNIX: + cons = thisproc.connections(kind='all') + self.compare_procsys_connections(os.getpid(), cons) + return conn + + def compare_procsys_connections(self, pid, proc_cons, kind='all'): + """Given a process PID and its list of connections compare + those against system-wide connections retrieved via + psutil.net_connections. + """ + try: + sys_cons = psutil.net_connections(kind=kind) + except psutil.AccessDenied: + # On OSX, system-wide connections are retrieved by iterating + # over all processes + if OSX: + return + else: + raise + # Filter for this proc PID and exlucde PIDs from the tuple. + sys_cons = [c[:-1] for c in sys_cons if c.pid == pid] + sys_cons.sort() + proc_cons.sort() + self.assertEqual(proc_cons, sys_cons) + + +# ===================================================================== +# --- Test unconnected sockets +# ===================================================================== + + +class TestUnconnectedSockets(Base, unittest.TestCase): + """Tests sockets which are open but not connected to anything.""" + + def test_tcp_v4(self): + addr = ("127.0.0.1", get_free_port()) + with closing(bind_socket(AF_INET, SOCK_STREAM, addr=addr)) as sock: + conn = self.check_socket(sock) + assert not conn.raddr + self.assertEqual(conn.status, psutil.CONN_LISTEN) + + @unittest.skipIf(not supports_ipv6(), "IPv6 not supported") + def test_tcp_v6(self): + addr = ("::1", get_free_port()) + with closing(bind_socket(AF_INET6, SOCK_STREAM, addr=addr)) as sock: + conn = self.check_socket(sock) + assert not conn.raddr + self.assertEqual(conn.status, psutil.CONN_LISTEN) + + def test_udp_v4(self): + addr = ("127.0.0.1", get_free_port()) + with closing(bind_socket(AF_INET, SOCK_DGRAM, addr=addr)) as sock: + conn = self.check_socket(sock) + assert not conn.raddr + self.assertEqual(conn.status, psutil.CONN_NONE) + + @unittest.skipIf(not supports_ipv6(), "IPv6 not supported") + def test_udp_v6(self): + addr = ("::1", get_free_port()) + with closing(bind_socket(AF_INET6, SOCK_DGRAM, addr=addr)) as sock: + conn = self.check_socket(sock) + assert not conn.raddr + self.assertEqual(conn.status, psutil.CONN_NONE) + + @unittest.skipIf(not POSIX, 'POSIX only') + def test_unix_tcp(self): + with unix_socket_path() as name: + with closing(bind_unix_socket(name, type=SOCK_STREAM)) as sock: + conn = self.check_socket(sock) + assert not conn.raddr + self.assertEqual(conn.status, psutil.CONN_NONE) + + @unittest.skipIf(not POSIX, 'POSIX only') + def test_unix_udp(self): + with unix_socket_path() as name: + with closing(bind_unix_socket(name, type=SOCK_STREAM)) as sock: + conn = self.check_socket(sock) + assert not conn.raddr + self.assertEqual(conn.status, psutil.CONN_NONE) + + +# ===================================================================== +# --- Test connected sockets +# ===================================================================== + + +class TestConnectedSocketPairs(Base, unittest.TestCase): + """Test socket pairs which are are actually connected to + each other. + """ + + # On SunOS, even after we close() it, the server socket stays around + # in TIME_WAIT state. + @unittest.skipIf(SUNOS, "unreliable on SUONS") + def test_tcp(self): + addr = ("127.0.0.1", get_free_port()) + assert not thisproc.connections(kind='tcp4') + server, client = tcp_socketpair(AF_INET, addr=addr) + try: + cons = thisproc.connections(kind='tcp4') + self.assertEqual(len(cons), 2) + self.assertEqual(cons[0].status, psutil.CONN_ESTABLISHED) + self.assertEqual(cons[1].status, psutil.CONN_ESTABLISHED) + # May not be fast enough to change state so it stays + # commenteed. + # client.close() + # cons = thisproc.connections(kind='all') + # self.assertEqual(len(cons), 1) + # self.assertEqual(cons[0].status, psutil.CONN_CLOSE_WAIT) + finally: + server.close() + client.close() + + @unittest.skipIf(not POSIX, 'POSIX only') + def test_unix(self): + with unix_socket_path() as name: + server, client = unix_socketpair(name) + try: + cons = thisproc.connections(kind='unix') + assert not (cons[0].laddr and cons[0].raddr) + assert not (cons[1].laddr and cons[1].raddr) + if NETBSD: + # On NetBSD creating a UNIX socket will cause + # a UNIX connection to /var/run/log. + cons = [c for c in cons if c.raddr != '/var/run/log'] + self.assertEqual(len(cons), 2) + if LINUX or FREEBSD or SUNOS: + # remote path is never set + self.assertEqual(cons[0].raddr, "") + self.assertEqual(cons[1].raddr, "") + # one local address should though + self.assertEqual(name, cons[0].laddr or cons[1].laddr) + elif OPENBSD: + # No addresses whatsoever here. + for addr in (cons[0].laddr, cons[0].raddr, + cons[1].laddr, cons[1].raddr): + self.assertEqual(addr, "") + else: + # On other systems either the laddr or raddr + # of both peers are set. + self.assertEqual(cons[0].laddr or cons[1].laddr, name) + self.assertEqual(cons[0].raddr or cons[1].raddr, name) + finally: + server.close() + client.close() + + @skip_on_access_denied(only_if=OSX) + def test_combos(self): + def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): + all_kinds = ("all", "inet", "inet4", "inet6", "tcp", "tcp4", + "tcp6", "udp", "udp4", "udp6") + check_connection_ntuple(conn) + self.assertEqual(conn.family, family) + self.assertEqual(conn.type, type) + self.assertEqual(conn.laddr, laddr) + self.assertEqual(conn.raddr, raddr) + self.assertEqual(conn.status, status) + for kind in all_kinds: + cons = proc.connections(kind=kind) + if kind in kinds: + assert cons + else: + assert not cons, cons + # compare against system-wide connections + # XXX Solaris can't retrieve system-wide UNIX + # sockets. + if HAS_CONNECTIONS_UNIX: + self.compare_procsys_connections(proc.pid, [conn]) + + tcp_template = textwrap.dedent(""" + import socket, time + s = socket.socket($family, socket.SOCK_STREAM) + s.bind(('$addr', 0)) + s.listen(1) + with open('$testfn', 'w') as f: + f.write(str(s.getsockname()[:2])) + time.sleep(60) + """) + + udp_template = textwrap.dedent(""" + import socket, time + s = socket.socket($family, socket.SOCK_DGRAM) + s.bind(('$addr', 0)) + with open('$testfn', 'w') as f: + f.write(str(s.getsockname()[:2])) + time.sleep(60) + """) + + from string import Template + testfile = os.path.basename(TESTFN) + tcp4_template = Template(tcp_template).substitute( + family=int(AF_INET), addr="127.0.0.1", testfn=testfile) + udp4_template = Template(udp_template).substitute( + family=int(AF_INET), addr="127.0.0.1", testfn=testfile) + tcp6_template = Template(tcp_template).substitute( + family=int(AF_INET6), addr="::1", testfn=testfile) + udp6_template = Template(udp_template).substitute( + family=int(AF_INET6), addr="::1", testfn=testfile) + + # launch various subprocess instantiating a socket of various + # families and types to enrich psutil results + tcp4_proc = pyrun(tcp4_template) + tcp4_addr = eval(wait_for_file(testfile)) + udp4_proc = pyrun(udp4_template) + udp4_addr = eval(wait_for_file(testfile)) + if supports_ipv6(): + tcp6_proc = pyrun(tcp6_template) + tcp6_addr = eval(wait_for_file(testfile)) + udp6_proc = pyrun(udp6_template) + udp6_addr = eval(wait_for_file(testfile)) + else: + tcp6_proc = None + udp6_proc = None + tcp6_addr = None + udp6_addr = None + + for p in thisproc.children(): + cons = p.connections() + self.assertEqual(len(cons), 1) + for conn in cons: + # TCP v4 + if p.pid == tcp4_proc.pid: + check_conn(p, conn, AF_INET, SOCK_STREAM, tcp4_addr, (), + psutil.CONN_LISTEN, + ("all", "inet", "inet4", "tcp", "tcp4")) + # UDP v4 + elif p.pid == udp4_proc.pid: + check_conn(p, conn, AF_INET, SOCK_DGRAM, udp4_addr, (), + psutil.CONN_NONE, + ("all", "inet", "inet4", "udp", "udp4")) + # TCP v6 + elif p.pid == getattr(tcp6_proc, "pid", None): + check_conn(p, conn, AF_INET6, SOCK_STREAM, tcp6_addr, (), + psutil.CONN_LISTEN, + ("all", "inet", "inet6", "tcp", "tcp6")) + # UDP v6 + elif p.pid == getattr(udp6_proc, "pid", None): + check_conn(p, conn, AF_INET6, SOCK_DGRAM, udp6_addr, (), + psutil.CONN_NONE, + ("all", "inet", "inet6", "udp", "udp6")) + + # err + self.assertRaises(ValueError, p.connections, kind='???') + + def test_multi_sockets_filtering(self): + with create_sockets() as socks: + cons = thisproc.connections(kind='all') + self.assertEqual(len(cons), len(socks)) + # tcp + cons = thisproc.connections(kind='tcp') + self.assertEqual(len(cons), 2 if supports_ipv6() else 1) + for conn in cons: + self.assertIn(conn.family, (AF_INET, AF_INET6)) + self.assertEqual(conn.type, SOCK_STREAM) + # tcp4 + cons = thisproc.connections(kind='tcp4') + self.assertEqual(len(cons), 1) + self.assertEqual(cons[0].family, AF_INET) + self.assertEqual(cons[0].type, SOCK_STREAM) + # tcp6 + if supports_ipv6(): + cons = thisproc.connections(kind='tcp6') + self.assertEqual(len(cons), 1) + self.assertEqual(cons[0].family, AF_INET6) + self.assertEqual(cons[0].type, SOCK_STREAM) + # udp + cons = thisproc.connections(kind='udp') + self.assertEqual(len(cons), 2 if supports_ipv6() else 1) + for conn in cons: + self.assertIn(conn.family, (AF_INET, AF_INET6)) + self.assertEqual(conn.type, SOCK_DGRAM) + # udp4 + cons = thisproc.connections(kind='udp4') + self.assertEqual(len(cons), 1) + self.assertEqual(cons[0].family, AF_INET) + self.assertEqual(cons[0].type, SOCK_DGRAM) + # udp6 + if supports_ipv6(): + cons = thisproc.connections(kind='udp6') + self.assertEqual(len(cons), 1) + self.assertEqual(cons[0].family, AF_INET6) + self.assertEqual(cons[0].type, SOCK_DGRAM) + # inet + cons = thisproc.connections(kind='inet') + self.assertEqual(len(cons), 4 if supports_ipv6() else 2) + for conn in cons: + self.assertIn(conn.family, (AF_INET, AF_INET6)) + self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM)) + # inet6 + if supports_ipv6(): + cons = thisproc.connections(kind='inet6') + self.assertEqual(len(cons), 2) + for conn in cons: + self.assertEqual(conn.family, AF_INET6) + self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM)) + # unix + if HAS_CONNECTIONS_UNIX: + cons = thisproc.connections(kind='unix') + self.assertEqual(len(cons), 3) + for conn in cons: + self.assertEqual(conn.family, AF_UNIX) + self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM)) + + +# ===================================================================== +# --- Miscellaneous tests +# ===================================================================== + + +class TestSystemWideConnections(Base, unittest.TestCase): + """Tests for net_connections().""" + + @skip_on_access_denied() + def test_it(self): + def check(cons, families, types_): + AF_UNIX = getattr(socket, 'AF_UNIX', object()) + for conn in cons: + self.assertIn(conn.family, families, msg=conn) + if conn.family != AF_UNIX: + self.assertIn(conn.type, types_, msg=conn) + check_connection_ntuple(conn) + + with create_sockets(): + from psutil._common import conn_tmap + for kind, groups in conn_tmap.items(): + # XXX: SunOS does not retrieve UNIX sockets. + if kind == 'unix' and not HAS_CONNECTIONS_UNIX: + continue + families, types_ = groups + cons = psutil.net_connections(kind) + self.assertEqual(len(cons), len(set(cons))) + check(cons, families, types_) + + self.assertRaises(ValueError, psutil.net_connections, kind='???') + + @skip_on_access_denied() + def test_multi_socks(self): + with create_sockets() as socks: + cons = [x for x in psutil.net_connections(kind='all') + if x.pid == os.getpid()] + self.assertEqual(len(cons), len(socks)) + + @skip_on_access_denied() + # See: https://travis-ci.org/giampaolo/psutil/jobs/237566297 + @unittest.skipIf(OSX and TRAVIS, "unreliable on OSX + TRAVIS") + def test_multi_sockets_procs(self): + # Creates multiple sub processes, each creating different + # sockets. For each process check that proc.connections() + # and net_connections() return the same results. + # This is done mainly to check whether net_connections()'s + # pid is properly set, see: + # https://github.com/giampaolo/psutil/issues/1013 + with create_sockets() as socks: + expected = len(socks) + pids = [] + times = 10 + for i in range(times): + fname = os.path.realpath(TESTFN) + str(i) + src = textwrap.dedent("""\ + import time, os + from psutil.tests import create_sockets + with create_sockets(): + with open('%s', 'w') as f: + f.write(str(os.getpid())) + time.sleep(60) + """ % fname) + sproc = pyrun(src) + pids.append(sproc.pid) + self.addCleanup(safe_rmpath, fname) + + # sync + for i in range(times): + fname = TESTFN + str(i) + wait_for_file(fname) + + syscons = [x for x in psutil.net_connections(kind='all') if x.pid + in pids] + for pid in pids: + self.assertEqual(len([x for x in syscons if x.pid == pid]), + expected) + p = psutil.Process(pid) + self.assertEqual(len(p.connections('all')), expected) + + +# ===================================================================== +# --- Miscellaneous tests +# ===================================================================== + + +class TestMisc(unittest.TestCase): + + def test_connection_constants(self): + ints = [] + strs = [] + for name in dir(psutil): + if name.startswith('CONN_'): + num = getattr(psutil, name) + str_ = str(num) + assert str_.isupper(), str_ + self.assertNotIn(str, strs) + self.assertNotIn(num, ints) + ints.append(num) + strs.append(str_) + if SUNOS: + psutil.CONN_IDLE + psutil.CONN_BOUND + if WINDOWS: + psutil.CONN_DELETE_TCB + + +if __name__ == '__main__': + run_test_module_by_name(__file__) diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py new file mode 100755 index 000000000..855b53bf9 --- /dev/null +++ b/psutil/tests/test_contracts.py @@ -0,0 +1,651 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Contracts tests. These tests mainly check API sanity in terms of +returned types and APIs availability. +Some of these are duplicates of tests test_system.py and test_process.py +""" + +import errno +import os +import stat +import time +import traceback +import warnings +from contextlib import closing + +from psutil import AIX +from psutil import BSD +from psutil import FREEBSD +from psutil import LINUX +from psutil import NETBSD +from psutil import OPENBSD +from psutil import OSX +from psutil import POSIX +from psutil import SUNOS +from psutil import WINDOWS +from psutil._compat import callable +from psutil._compat import long +from psutil.tests import bind_unix_socket +from psutil.tests import check_connection_ntuple +from psutil.tests import get_kernel_version +from psutil.tests import HAS_CONNECTIONS_UNIX +from psutil.tests import HAS_RLIMIT +from psutil.tests import HAS_SENSORS_FANS +from psutil.tests import HAS_SENSORS_TEMPERATURES +from psutil.tests import is_namedtuple +from psutil.tests import run_test_module_by_name +from psutil.tests import safe_rmpath +from psutil.tests import skip_on_access_denied +from psutil.tests import TESTFN +from psutil.tests import unittest +from psutil.tests import unix_socket_path +from psutil.tests import VALID_PROC_STATUSES +from psutil.tests import warn +import psutil + + +# =================================================================== +# --- APIs availability +# =================================================================== + + +class TestAvailability(unittest.TestCase): + """Make sure code reflects what doc promises in terms of APIs + availability. + """ + + def test_cpu_affinity(self): + hasit = LINUX or WINDOWS or FREEBSD + self.assertEqual(hasattr(psutil.Process, "cpu_affinity"), hasit) + + def test_win_service(self): + self.assertEqual(hasattr(psutil, "win_service_iter"), WINDOWS) + self.assertEqual(hasattr(psutil, "win_service_get"), WINDOWS) + + def test_PROCFS_PATH(self): + self.assertEqual(hasattr(psutil, "PROCFS_PATH"), + LINUX or SUNOS or AIX) + + def test_win_priority(self): + ae = self.assertEqual + ae(hasattr(psutil, "ABOVE_NORMAL_PRIORITY_CLASS"), WINDOWS) + ae(hasattr(psutil, "BELOW_NORMAL_PRIORITY_CLASS"), WINDOWS) + ae(hasattr(psutil, "HIGH_PRIORITY_CLASS"), WINDOWS) + ae(hasattr(psutil, "IDLE_PRIORITY_CLASS"), WINDOWS) + ae(hasattr(psutil, "NORMAL_PRIORITY_CLASS"), WINDOWS) + ae(hasattr(psutil, "REALTIME_PRIORITY_CLASS"), WINDOWS) + + def test_linux_ioprio(self): + ae = self.assertEqual + ae(hasattr(psutil, "IOPRIO_CLASS_NONE"), LINUX) + ae(hasattr(psutil, "IOPRIO_CLASS_RT"), LINUX) + ae(hasattr(psutil, "IOPRIO_CLASS_BE"), LINUX) + ae(hasattr(psutil, "IOPRIO_CLASS_IDLE"), LINUX) + + def test_linux_rlimit(self): + ae = self.assertEqual + hasit = LINUX and get_kernel_version() >= (2, 6, 36) + ae(hasattr(psutil.Process, "rlimit"), hasit) + ae(hasattr(psutil, "RLIM_INFINITY"), hasit) + ae(hasattr(psutil, "RLIMIT_AS"), hasit) + ae(hasattr(psutil, "RLIMIT_CORE"), hasit) + ae(hasattr(psutil, "RLIMIT_CPU"), hasit) + ae(hasattr(psutil, "RLIMIT_DATA"), hasit) + ae(hasattr(psutil, "RLIMIT_FSIZE"), hasit) + ae(hasattr(psutil, "RLIMIT_LOCKS"), hasit) + ae(hasattr(psutil, "RLIMIT_MEMLOCK"), hasit) + ae(hasattr(psutil, "RLIMIT_NOFILE"), hasit) + ae(hasattr(psutil, "RLIMIT_NPROC"), hasit) + ae(hasattr(psutil, "RLIMIT_RSS"), hasit) + ae(hasattr(psutil, "RLIMIT_STACK"), hasit) + + hasit = LINUX and get_kernel_version() >= (3, 0) + ae(hasattr(psutil, "RLIMIT_MSGQUEUE"), hasit) + ae(hasattr(psutil, "RLIMIT_NICE"), hasit) + ae(hasattr(psutil, "RLIMIT_RTPRIO"), hasit) + ae(hasattr(psutil, "RLIMIT_RTTIME"), hasit) + ae(hasattr(psutil, "RLIMIT_SIGPENDING"), hasit) + + def test_cpu_freq(self): + linux = (LINUX and + (os.path.exists("/sys/devices/system/cpu/cpufreq") or + os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq"))) + self.assertEqual(hasattr(psutil, "cpu_freq"), linux or OSX or WINDOWS) + + def test_sensors_temperatures(self): + self.assertEqual(hasattr(psutil, "sensors_temperatures"), LINUX) + + def test_sensors_fans(self): + self.assertEqual(hasattr(psutil, "sensors_fans"), LINUX) + + def test_battery(self): + self.assertEqual(hasattr(psutil, "sensors_battery"), + LINUX or WINDOWS or FREEBSD or OSX) + + def test_proc_environ(self): + self.assertEqual(hasattr(psutil.Process, "environ"), + LINUX or OSX or WINDOWS) + + def test_proc_uids(self): + self.assertEqual(hasattr(psutil.Process, "uids"), POSIX) + + def test_proc_gids(self): + self.assertEqual(hasattr(psutil.Process, "uids"), POSIX) + + def test_proc_terminal(self): + self.assertEqual(hasattr(psutil.Process, "terminal"), POSIX) + + def test_proc_ionice(self): + self.assertEqual(hasattr(psutil.Process, "ionice"), LINUX or WINDOWS) + + def test_proc_rlimit(self): + self.assertEqual(hasattr(psutil.Process, "rlimit"), LINUX) + + def test_proc_io_counters(self): + hasit = hasattr(psutil.Process, "io_counters") + self.assertEqual(hasit, False if OSX or SUNOS else True) + + def test_proc_num_fds(self): + self.assertEqual(hasattr(psutil.Process, "num_fds"), POSIX) + + def test_proc_num_handles(self): + self.assertEqual(hasattr(psutil.Process, "num_handles"), WINDOWS) + + def test_proc_cpu_affinity(self): + self.assertEqual(hasattr(psutil.Process, "cpu_affinity"), + LINUX or WINDOWS or FREEBSD) + + def test_proc_cpu_num(self): + self.assertEqual(hasattr(psutil.Process, "cpu_num"), + LINUX or FREEBSD or SUNOS) + + def test_proc_memory_maps(self): + hasit = hasattr(psutil.Process, "memory_maps") + self.assertEqual(hasit, False if OPENBSD or NETBSD or AIX else True) + + +# =================================================================== +# --- Test deprecations +# =================================================================== + + +class TestDeprecations(unittest.TestCase): + + def test_memory_info_ex(self): + with warnings.catch_warnings(record=True) as ws: + psutil.Process().memory_info_ex() + w = ws[0] + self.assertIsInstance(w.category(), FutureWarning) + self.assertIn("memory_info_ex() is deprecated", str(w.message)) + self.assertIn("use memory_info() instead", str(w.message)) + + +# =================================================================== +# --- System API types +# =================================================================== + + +class TestSystem(unittest.TestCase): + """Check the return types of system related APIs. + Mainly we want to test we never return unicode on Python 2, see: + https://github.com/giampaolo/psutil/issues/1039 + """ + + @classmethod + def setUpClass(cls): + cls.proc = psutil.Process() + + def tearDown(self): + safe_rmpath(TESTFN) + + def test_cpu_times(self): + # Duplicate of test_system.py. Keep it anyway. + ret = psutil.cpu_times() + assert is_namedtuple(ret) + for n in ret: + self.assertIsInstance(n, float) + self.assertGreaterEqual(n, 0) + + def test_io_counters(self): + # Duplicate of test_system.py. Keep it anyway. + for k in psutil.disk_io_counters(perdisk=True): + self.assertIsInstance(k, str) + + def test_disk_partitions(self): + # Duplicate of test_system.py. Keep it anyway. + for disk in psutil.disk_partitions(): + self.assertIsInstance(disk.device, str) + self.assertIsInstance(disk.mountpoint, str) + self.assertIsInstance(disk.fstype, str) + self.assertIsInstance(disk.opts, str) + + @unittest.skipIf(not POSIX, 'POSIX only') + @unittest.skipIf(not HAS_CONNECTIONS_UNIX, "can't list UNIX sockets") + @skip_on_access_denied(only_if=OSX) + def test_net_connections(self): + with unix_socket_path() as name: + with closing(bind_unix_socket(name)): + cons = psutil.net_connections(kind='unix') + assert cons + for conn in cons: + self.assertIsInstance(conn.laddr, str) + + def test_net_if_addrs(self): + # Duplicate of test_system.py. Keep it anyway. + for ifname, addrs in psutil.net_if_addrs().items(): + self.assertIsInstance(ifname, str) + for addr in addrs: + self.assertIsInstance(addr.address, str) + self.assertIsInstance(addr.netmask, (str, type(None))) + self.assertIsInstance(addr.broadcast, (str, type(None))) + + def test_net_if_stats(self): + # Duplicate of test_system.py. Keep it anyway. + for ifname, _ in psutil.net_if_stats().items(): + self.assertIsInstance(ifname, str) + + def test_net_io_counters(self): + # Duplicate of test_system.py. Keep it anyway. + for ifname, _ in psutil.net_io_counters(pernic=True).items(): + self.assertIsInstance(ifname, str) + + @unittest.skipIf(not HAS_SENSORS_FANS, "not supported") + def test_sensors_fans(self): + # Duplicate of test_system.py. Keep it anyway. + for name, units in psutil.sensors_fans().items(): + self.assertIsInstance(name, str) + for unit in units: + self.assertIsInstance(unit.label, str) + + @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") + def test_sensors_temperatures(self): + # Duplicate of test_system.py. Keep it anyway. + for name, units in psutil.sensors_temperatures().items(): + self.assertIsInstance(name, str) + for unit in units: + self.assertIsInstance(unit.label, str) + + def test_users(self): + # Duplicate of test_system.py. Keep it anyway. + for user in psutil.users(): + self.assertIsInstance(user.name, str) + self.assertIsInstance(user.terminal, (str, type(None))) + self.assertIsInstance(user.host, (str, type(None))) + self.assertIsInstance(user.pid, (int, type(None))) + + +# =================================================================== +# --- Featch all processes test +# =================================================================== + + +class TestFetchAllProcesses(unittest.TestCase): + """Test which iterates over all running processes and performs + some sanity checks against Process API's returned values. + """ + + def setUp(self): + if POSIX: + import pwd + import grp + users = pwd.getpwall() + groups = grp.getgrall() + self.all_uids = set([x.pw_uid for x in users]) + self.all_usernames = set([x.pw_name for x in users]) + self.all_gids = set([x.gr_gid for x in groups]) + + def test_fetch_all(self): + valid_procs = 0 + excluded_names = set([ + 'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', + 'as_dict', 'parent', 'children', 'memory_info_ex', 'oneshot', + ]) + if LINUX and not HAS_RLIMIT: + excluded_names.add('rlimit') + attrs = [] + for name in dir(psutil.Process): + if name.startswith("_"): + continue + if name in excluded_names: + continue + attrs.append(name) + + default = object() + failures = [] + for p in psutil.process_iter(): + with p.oneshot(): + for name in attrs: + ret = default + try: + args = () + kwargs = {} + attr = getattr(p, name, None) + if attr is not None and callable(attr): + if name == 'rlimit': + args = (psutil.RLIMIT_NOFILE,) + elif name == 'memory_maps': + kwargs = {'grouped': False} + ret = attr(*args, **kwargs) + else: + ret = attr + valid_procs += 1 + except NotImplementedError: + msg = "%r was skipped because not implemented" % ( + self.__class__.__name__ + '.test_' + name) + warn(msg) + except (psutil.NoSuchProcess, psutil.AccessDenied) as err: + self.assertEqual(err.pid, p.pid) + if err.name: + # make sure exception's name attr is set + # with the actual process name + self.assertEqual(err.name, p.name()) + assert str(err) + assert err.msg + except Exception as err: + s = '\n' + '=' * 70 + '\n' + s += "FAIL: test_%s (proc=%s" % (name, p) + if ret != default: + s += ", ret=%s)" % repr(ret) + s += ')\n' + s += '-' * 70 + s += "\n%s" % traceback.format_exc() + s = "\n".join((" " * 4) + i for i in s.splitlines()) + s += '\n' + failures.append(s) + break + else: + if ret not in (0, 0.0, [], None, '', {}): + assert ret, ret + meth = getattr(self, name) + meth(ret, p) + + if failures: + self.fail(''.join(failures)) + + # we should always have a non-empty list, not including PID 0 etc. + # special cases. + assert valid_procs + + def cmdline(self, ret, proc): + self.assertIsInstance(ret, list) + for part in ret: + self.assertIsInstance(part, str) + + def exe(self, ret, proc): + self.assertIsInstance(ret, (str, type(None))) + if not ret: + self.assertEqual(ret, '') + else: + assert os.path.isabs(ret), ret + # Note: os.stat() may return False even if the file is there + # hence we skip the test, see: + # http://stackoverflow.com/questions/3112546/os-path-exists-lies + if POSIX and os.path.isfile(ret): + if hasattr(os, 'access') and hasattr(os, "X_OK"): + # XXX may fail on OSX + assert os.access(ret, os.X_OK) + + def pid(self, ret, proc): + self.assertIsInstance(ret, int) + self.assertGreaterEqual(ret, 0) + + def ppid(self, ret, proc): + self.assertIsInstance(ret, (int, long)) + self.assertGreaterEqual(ret, 0) + + def name(self, ret, proc): + self.assertIsInstance(ret, str) + # on AIX, "" processes don't have names + if not AIX: + assert ret + + def create_time(self, ret, proc): + self.assertIsInstance(ret, float) + try: + self.assertGreaterEqual(ret, 0) + except AssertionError: + # XXX + if OPENBSD and proc.status() == psutil.STATUS_ZOMBIE: + pass + else: + raise + # this can't be taken for granted on all platforms + # self.assertGreaterEqual(ret, psutil.boot_time()) + # make sure returned value can be pretty printed + # with strftime + time.strftime("%Y %m %d %H:%M:%S", time.localtime(ret)) + + def uids(self, ret, proc): + assert is_namedtuple(ret) + for uid in ret: + self.assertIsInstance(uid, int) + self.assertGreaterEqual(uid, 0) + self.assertIn(uid, self.all_uids) + + def gids(self, ret, proc): + assert is_namedtuple(ret) + # note: testing all gids as above seems not to be reliable for + # gid == 30 (nodoby); not sure why. + for gid in ret: + self.assertIsInstance(gid, int) + if not OSX and not NETBSD: + self.assertGreaterEqual(gid, 0) + self.assertIn(gid, self.all_gids) + + def username(self, ret, proc): + self.assertIsInstance(ret, str) + assert ret + if POSIX: + self.assertIn(ret, self.all_usernames) + + def status(self, ret, proc): + self.assertIsInstance(ret, str) + assert ret + self.assertNotEqual(ret, '?') # XXX + self.assertIn(ret, VALID_PROC_STATUSES) + + def io_counters(self, ret, proc): + assert is_namedtuple(ret) + for field in ret: + self.assertIsInstance(field, (int, long)) + if field != -1: + self.assertGreaterEqual(field, 0) + + def ionice(self, ret, proc): + if POSIX: + assert is_namedtuple(ret) + for field in ret: + self.assertIsInstance(field, int) + if LINUX: + self.assertGreaterEqual(ret.ioclass, 0) + self.assertGreaterEqual(ret.value, 0) + else: + self.assertGreaterEqual(ret, 0) + self.assertIn(ret, (0, 1, 2)) + + def num_threads(self, ret, proc): + self.assertIsInstance(ret, int) + self.assertGreaterEqual(ret, 1) + + def threads(self, ret, proc): + self.assertIsInstance(ret, list) + for t in ret: + assert is_namedtuple(t) + self.assertGreaterEqual(t.id, 0) + self.assertGreaterEqual(t.user_time, 0) + self.assertGreaterEqual(t.system_time, 0) + for field in t: + self.assertIsInstance(field, (int, float)) + + def cpu_times(self, ret, proc): + assert is_namedtuple(ret) + for n in ret: + self.assertIsInstance(n, float) + self.assertGreaterEqual(n, 0) + # TODO: check ntuple fields + + def cpu_percent(self, ret, proc): + self.assertIsInstance(ret, float) + assert 0.0 <= ret <= 100.0, ret + + def cpu_num(self, ret, proc): + self.assertIsInstance(ret, int) + if FREEBSD and ret == -1: + return + self.assertGreaterEqual(ret, 0) + if psutil.cpu_count() == 1: + self.assertEqual(ret, 0) + self.assertIn(ret, list(range(psutil.cpu_count()))) + + def memory_info(self, ret, proc): + assert is_namedtuple(ret) + for value in ret: + self.assertIsInstance(value, (int, long)) + self.assertGreaterEqual(value, 0) + if POSIX and not AIX and ret.vms != 0: + # VMS is always supposed to be the highest + for name in ret._fields: + if name != 'vms': + value = getattr(ret, name) + self.assertGreater(ret.vms, value, msg=ret) + elif WINDOWS: + self.assertGreaterEqual(ret.peak_wset, ret.wset) + self.assertGreaterEqual(ret.peak_paged_pool, ret.paged_pool) + self.assertGreaterEqual(ret.peak_nonpaged_pool, ret.nonpaged_pool) + self.assertGreaterEqual(ret.peak_pagefile, ret.pagefile) + + def memory_full_info(self, ret, proc): + assert is_namedtuple(ret) + total = psutil.virtual_memory().total + for name in ret._fields: + value = getattr(ret, name) + self.assertIsInstance(value, (int, long)) + self.assertGreaterEqual(value, 0, msg=(name, value)) + self.assertLessEqual(value, total, msg=(name, value, total)) + + if LINUX: + self.assertGreaterEqual(ret.pss, ret.uss) + + def open_files(self, ret, proc): + self.assertIsInstance(ret, list) + for f in ret: + self.assertIsInstance(f.fd, int) + self.assertIsInstance(f.path, str) + if WINDOWS: + self.assertEqual(f.fd, -1) + elif LINUX: + self.assertIsInstance(f.position, int) + self.assertIsInstance(f.mode, str) + self.assertIsInstance(f.flags, int) + self.assertGreaterEqual(f.position, 0) + self.assertIn(f.mode, ('r', 'w', 'a', 'r+', 'a+')) + self.assertGreater(f.flags, 0) + elif BSD and not f.path: + # XXX see: https://github.com/giampaolo/psutil/issues/595 + continue + assert os.path.isabs(f.path), f + assert os.path.isfile(f.path), f + + def num_fds(self, ret, proc): + self.assertIsInstance(ret, int) + self.assertGreaterEqual(ret, 0) + + def connections(self, ret, proc): + self.assertEqual(len(ret), len(set(ret))) + for conn in ret: + check_connection_ntuple(conn) + + def cwd(self, ret, proc): + if ret: # 'ret' can be None or empty + self.assertIsInstance(ret, str) + assert os.path.isabs(ret), ret + try: + st = os.stat(ret) + except OSError as err: + if WINDOWS and err.errno in \ + psutil._psplatform.ACCESS_DENIED_SET: + pass + # directory has been removed in mean time + elif err.errno != errno.ENOENT: + raise + else: + assert stat.S_ISDIR(st.st_mode) + + def memory_percent(self, ret, proc): + self.assertIsInstance(ret, float) + assert 0 <= ret <= 100, ret + + def is_running(self, ret, proc): + self.assertIsInstance(ret, bool) + + def cpu_affinity(self, ret, proc): + self.assertIsInstance(ret, list) + assert ret != [], ret + cpus = range(psutil.cpu_count()) + for n in ret: + self.assertIsInstance(n, int) + self.assertIn(n, cpus) + + def terminal(self, ret, proc): + self.assertIsInstance(ret, (str, type(None))) + if ret is not None: + assert os.path.isabs(ret), ret + assert os.path.exists(ret), ret + + def memory_maps(self, ret, proc): + for nt in ret: + self.assertIsInstance(nt.addr, str) + self.assertIsInstance(nt.perms, str) + self.assertIsInstance(nt.path, str) + for fname in nt._fields: + value = getattr(nt, fname) + if fname == 'path': + if not value.startswith('['): + assert os.path.isabs(nt.path), nt.path + # commented as on Linux we might get + # '/foo/bar (deleted)' + # assert os.path.exists(nt.path), nt.path + elif fname in ('addr', 'perms'): + assert value + else: + self.assertIsInstance(value, (int, long)) + self.assertGreaterEqual(value, 0) + + def num_handles(self, ret, proc): + self.assertIsInstance(ret, int) + self.assertGreaterEqual(ret, 0) + + def nice(self, ret, proc): + self.assertIsInstance(ret, int) + if POSIX: + assert -20 <= ret <= 20, ret + else: + priorities = [getattr(psutil, x) for x in dir(psutil) + if x.endswith('_PRIORITY_CLASS')] + self.assertIn(ret, priorities) + + def num_ctx_switches(self, ret, proc): + assert is_namedtuple(ret) + for value in ret: + self.assertIsInstance(value, (int, long)) + self.assertGreaterEqual(value, 0) + + def rlimit(self, ret, proc): + self.assertIsInstance(ret, tuple) + self.assertEqual(len(ret), 2) + self.assertGreaterEqual(ret[0], -1) + self.assertGreaterEqual(ret[1], -1) + + def environ(self, ret, proc): + self.assertIsInstance(ret, dict) + for k, v in ret.items(): + self.assertIsInstance(k, str) + self.assertIsInstance(v, str) + + +if __name__ == '__main__': + run_test_module_by_name(__file__) diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index fe2f08106..6ba17b254 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -10,6 +10,7 @@ import collections import contextlib import errno +import glob import io import os import pprint @@ -27,19 +28,22 @@ from psutil._compat import PY3 from psutil._compat import u from psutil.tests import call_until -from psutil.tests import get_kernel_version -from psutil.tests import importlib +from psutil.tests import HAS_BATTERY +from psutil.tests import HAS_CPU_FREQ +from psutil.tests import HAS_RLIMIT from psutil.tests import MEMORY_TOLERANCE from psutil.tests import mock from psutil.tests import PYPY from psutil.tests import pyrun from psutil.tests import reap_children +from psutil.tests import reload_module from psutil.tests import retry_before_failing from psutil.tests import run_test_module_by_name from psutil.tests import safe_rmpath from psutil.tests import sh from psutil.tests import skip_on_not_implemented from psutil.tests import TESTFN +from psutil.tests import ThreadTask from psutil.tests import TRAVIS from psutil.tests import unittest from psutil.tests import which @@ -50,11 +54,11 @@ SIOCGIFCONF = 0x8912 SIOCGIFHWADDR = 0x8927 if LINUX: - SECTOR_SIZE = psutil._psplatform.SECTOR_SIZE + SECTOR_SIZE = 512 # ===================================================================== -# utils +# --- utils # ===================================================================== @@ -140,11 +144,11 @@ def get_free_version_info(): # ===================================================================== -# system virtual memory +# --- system virtual memory # ===================================================================== -@unittest.skipUnless(LINUX, "LINUX only") +@unittest.skipIf(not LINUX, "LINUX only") class TestSystemVirtualMemory(unittest.TestCase): def test_total(self): @@ -159,8 +163,8 @@ def test_total(self): # This got changed in: # https://gitlab.com/procps-ng/procps/commit/ # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e - @unittest.skipUnless( - LINUX and get_free_version_info() >= (3, 3, 12), "old free version") + @unittest.skipIf(LINUX and get_free_version_info() < (3, 3, 12), + "old free version") @retry_before_failing() def test_used(self): free = free_physmem() @@ -170,6 +174,7 @@ def test_used(self): free_value, psutil_value, delta=MEMORY_TOLERANCE, msg='%s %s \n%s' % (free_value, psutil_value, free.output)) + @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") @retry_before_failing() def test_free(self): # _, _, free_value, _ = free_physmem() @@ -188,6 +193,8 @@ def test_buffers(self): self.assertAlmostEqual( vmstat_value, psutil_value, delta=MEMORY_TOLERANCE) + # https://travis-ci.org/giampaolo/psutil/jobs/226719664 + @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") @retry_before_failing() def test_active(self): vmstat_value = vmstat('active memory') * 1024 @@ -195,6 +202,8 @@ def test_active(self): self.assertAlmostEqual( vmstat_value, psutil_value, delta=MEMORY_TOLERANCE) + # https://travis-ci.org/giampaolo/psutil/jobs/227242952 + @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") @retry_before_failing() def test_inactive(self): vmstat_value = vmstat('inactive memory') * 1024 @@ -228,16 +237,18 @@ def test_available(self): free_value, psutil_value, delta=MEMORY_TOLERANCE, msg='%s %s \n%s' % (free_value, psutil_value, out)) - def test_warnings_mocked(self): + def test_warnings_on_misses(self): + # Emulate a case where /proc/meminfo provides few info. + # psutil is supposed to set the missing fields to 0 and + # raise a warning. def open_mock(name, *args, **kwargs): if name == '/proc/meminfo': return io.BytesIO(textwrap.dedent("""\ Active(anon): 6145416 kB Active(file): 2950064 kB - Buffers: 287952 kB Inactive(anon): 574764 kB Inactive(file): 1567648 kB - MemAvailable: 6574984 kB + MemAvailable: -1 kB MemFree: 2057400 kB MemTotal: 16325648 kB SReclaimable: 346648 kB @@ -254,17 +265,21 @@ def open_mock(name, *args, **kwargs): assert m.called self.assertEqual(len(ws), 1) w = ws[0] - self.assertTrue(w.filename.endswith('psutil/_pslinux.py')) + assert w.filename.endswith('psutil/_pslinux.py') self.assertIn( "memory stats couldn't be determined", str(w.message)) self.assertIn("cached", str(w.message)) self.assertIn("shared", str(w.message)) self.assertIn("active", str(w.message)) self.assertIn("inactive", str(w.message)) + self.assertIn("buffers", str(w.message)) + self.assertIn("available", str(w.message)) self.assertEqual(ret.cached, 0) self.assertEqual(ret.active, 0) self.assertEqual(ret.inactive, 0) self.assertEqual(ret.shared, 0) + self.assertEqual(ret.buffers, 0) + self.assertEqual(ret.available, 0) def test_avail_old_percent(self): # Make sure that our calculation of avail mem for old kernels @@ -310,9 +325,13 @@ def open_mock(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: - ret = psutil.virtual_memory() + with warnings.catch_warnings(record=True) as ws: + ret = psutil.virtual_memory() assert m.called self.assertEqual(ret.available, 6574984 * 1024) + w = ws[0] + self.assertIn( + "inactive memory stats couldn't be determined", str(w.message)) def test_avail_old_missing_fields(self): # Remove Active(file), Inactive(file) and SReclaimable @@ -337,9 +356,13 @@ def open_mock(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: - ret = psutil.virtual_memory() + with warnings.catch_warnings(record=True) as ws: + ret = psutil.virtual_memory() assert m.called self.assertEqual(ret.available, 2057400 * 1024 + 4818144 * 1024) + w = ws[0] + self.assertIn( + "inactive memory stats couldn't be determined", str(w.message)) def test_avail_old_missing_zoneinfo(self): # Remove /proc/zoneinfo file. Make sure fallback is used @@ -368,19 +391,30 @@ def open_mock(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: - ret = psutil.virtual_memory() + with warnings.catch_warnings(record=True) as ws: + ret = psutil.virtual_memory() assert m.called self.assertEqual(ret.available, 2057400 * 1024 + 4818144 * 1024) + w = ws[0] + self.assertIn( + "inactive memory stats couldn't be determined", str(w.message)) # ===================================================================== -# system swap memory +# --- system swap memory # ===================================================================== -@unittest.skipUnless(LINUX, "LINUX only") +@unittest.skipIf(not LINUX, "LINUX only") class TestSystemSwapMemory(unittest.TestCase): + @staticmethod + def meminfo_has_swap_info(): + """Return True if /proc/meminfo provides swap metrics.""" + with open("/proc/meminfo") as f: + data = f.read() + return 'SwapTotal:' in data and 'SwapFree:' in data + def test_total(self): free_value = free_swap().total psutil_value = psutil.swap_memory().total @@ -401,7 +435,7 @@ def test_free(self): return self.assertAlmostEqual( free_value, psutil_value, delta=MEMORY_TOLERANCE) - def test_warnings_mocked(self): + def test_missing_sin_sout(self): with mock.patch('psutil._pslinux.open', create=True) as m: with warnings.catch_warnings(record=True) as ws: warnings.simplefilter("always") @@ -409,7 +443,7 @@ def test_warnings_mocked(self): assert m.called self.assertEqual(len(ws), 1) w = ws[0] - self.assertTrue(w.filename.endswith('psutil/_pslinux.py')) + assert w.filename.endswith('psutil/_pslinux.py') self.assertIn( "'sin' and 'sout' swap memory stats couldn't " "be determined", str(w.message)) @@ -418,15 +452,22 @@ 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() assert m.called self.assertEqual(len(ws), 1) w = ws[0] - self.assertTrue(w.filename.endswith('psutil/_pslinux.py')) + assert w.filename.endswith('psutil/_pslinux.py') self.assertIn( "'sin' and 'sout' swap memory stats couldn't " "be determined and were set to 0", @@ -434,19 +475,51 @@ def test_no_vmstat_mocked(self): self.assertEqual(ret.sin, 0) self.assertEqual(ret.sout, 0) + def test_meminfo_against_sysinfo(self): + # Make sure the content of /proc/meminfo about swap memory + # matches sysinfo() syscall, see: + # https://github.com/giampaolo/psutil/issues/1015 + if not self.meminfo_has_swap_info(): + return unittest.skip("/proc/meminfo has no swap metrics") + 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) + + def test_emulate_meminfo_has_no_metrics(self): + # Emulate a case where /proc/meminfo provides no swap metrics + # in which case sysinfo() syscall is supposed to be used + # as a fallback. + def open_mock(name, *args, **kwargs): + if name == "/proc/meminfo": + return io.BytesIO(b"") + 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: + psutil.swap_memory() + assert m.called + # ===================================================================== -# system CPU +# --- system CPU # ===================================================================== -@unittest.skipUnless(LINUX, "LINUX only") +@unittest.skipIf(not LINUX, "LINUX only") class TestSystemCPU(unittest.TestCase): @unittest.skipIf(TRAVIS, "unknown failure on travis") def test_cpu_times(self): fields = psutil.cpu_times()._fields - kernel_ver = re.findall('\d+\.\d+\.\d+', os.uname()[2])[0] + kernel_ver = re.findall(r'\d+\.\d+\.\d+', os.uname()[2])[0] kernel_ver_info = tuple(map(int, kernel_ver.split('.'))) if kernel_ver_info >= (2, 6, 11): self.assertIn('steal', fields) @@ -461,12 +534,28 @@ def test_cpu_times(self): else: self.assertNotIn('guest_nice', fields) - @unittest.skipUnless(which("nproc"), "nproc utility not available") + @unittest.skipIf(not os.path.exists("/sys/devices/system/cpu/online"), + "/sys/devices/system/cpu/online does not exist") + def test_cpu_count_logical_w_sysdev_cpu_online(self): + with open("/sys/devices/system/cpu/online") as f: + value = f.read().strip() + if "-" in str(value): + value = int(value.split('-')[1]) + 1 + self.assertEqual(psutil.cpu_count(), value) + + @unittest.skipIf(not os.path.exists("/sys/devices/system/cpu"), + "/sys/devices/system/cpu does not exist") + def test_cpu_count_logical_w_sysdev_cpu_num(self): + ls = os.listdir("/sys/devices/system/cpu") + count = len([x for x in ls if re.search(r"cpu\d+$", x) is not None]) + self.assertEqual(psutil.cpu_count(), count) + + @unittest.skipIf(not which("nproc"), "nproc utility not available") def test_cpu_count_logical_w_nproc(self): num = int(sh("nproc --all")) self.assertEqual(psutil.cpu_count(logical=True), num) - @unittest.skipUnless(which("lscpu"), "lscpu utility not available") + @unittest.skipIf(not which("lscpu"), "lscpu utility not available") def test_cpu_count_logical_w_lscpu(self): out = sh("lscpu -p") num = len([x for x in out.split('\n') if not x.startswith('#')]) @@ -519,13 +608,124 @@ def test_cpu_count_physical_mocked(self): self.assertIsNone(psutil._pslinux.cpu_count_physical()) assert m.called + @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + def test_cpu_freq_no_result(self): + with mock.patch("psutil._pslinux.glob.glob", return_value=[]): + self.assertIsNone(psutil.cpu_freq()) + + @unittest.skipIf(TRAVIS, "fails on Travis") + @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + def test_cpu_freq_use_second_file(self): + # https://github.com/giampaolo/psutil/issues/981 + def glob_mock(pattern): + if pattern.startswith("/sys/devices/system/cpu/cpufreq/policy"): + flags.append(None) + return [] + else: + flags.append(None) + return orig_glob(pattern) + + flags = [] + orig_glob = glob.glob + with mock.patch("psutil._pslinux.glob.glob", side_effect=glob_mock, + create=True): + assert psutil.cpu_freq() + self.assertEqual(len(flags), 2) + + @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + def test_cpu_freq_emulate_data(self): + def open_mock(name, *args, **kwargs): + if name.endswith('/scaling_cur_freq'): + return io.BytesIO(b"500000") + elif name.endswith('/scaling_min_freq'): + return io.BytesIO(b"600000") + elif name.endswith('/scaling_max_freq'): + return io.BytesIO(b"700000") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock): + with mock.patch( + 'glob.glob', + return_value=['/sys/devices/system/cpu/cpufreq/policy0']): + freq = psutil.cpu_freq() + self.assertEqual(freq.current, 500.0) + self.assertEqual(freq.min, 600.0) + self.assertEqual(freq.max, 700.0) + + @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + def test_cpu_freq_emulate_multi_cpu(self): + def open_mock(name, *args, **kwargs): + if name.endswith('/scaling_cur_freq'): + return io.BytesIO(b"100000") + elif name.endswith('/scaling_min_freq'): + return io.BytesIO(b"200000") + elif name.endswith('/scaling_max_freq'): + return io.BytesIO(b"300000") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + policies = ['/sys/devices/system/cpu/cpufreq/policy0', + '/sys/devices/system/cpu/cpufreq/policy1', + '/sys/devices/system/cpu/cpufreq/policy2'] + with mock.patch(patch_point, side_effect=open_mock): + with mock.patch('glob.glob', return_value=policies): + freq = psutil.cpu_freq() + self.assertEqual(freq.current, 100.0) + self.assertEqual(freq.min, 200.0) + self.assertEqual(freq.max, 300.0) + + @unittest.skipIf(TRAVIS, "fails on Travis") + @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + def test_cpu_freq_no_scaling_cur_freq_file(self): + # See: https://github.com/giampaolo/psutil/issues/1071 + def open_mock(name, *args, **kwargs): + if name.endswith('/scaling_cur_freq'): + raise IOError(errno.ENOENT, "") + elif name.endswith('/cpuinfo_cur_freq'): + return io.BytesIO(b"200000") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + policies = ['/sys/devices/system/cpu/cpufreq/policy0', + '/sys/devices/system/cpu/cpufreq/policy1', + '/sys/devices/system/cpu/cpufreq/policy2'] + + with mock.patch(patch_point, side_effect=open_mock): + with mock.patch('glob.glob', return_value=policies): + freq = psutil.cpu_freq() + self.assertEqual(freq.current, 200) + + # Also test that NotImplementedError is raised in case no + # current freq file is present. + + def open_mock(name, *args, **kwargs): + if name.endswith('/scaling_cur_freq'): + raise IOError(errno.ENOENT, "") + elif name.endswith('/cpuinfo_cur_freq'): + raise IOError(errno.ENOENT, "") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock): + with mock.patch('glob.glob', return_value=policies): + self.assertRaises(NotImplementedError, psutil.cpu_freq) + # ===================================================================== -# system CPU stats +# --- system CPU stats # ===================================================================== -@unittest.skipUnless(LINUX, "LINUX only") +@unittest.skipIf(not LINUX, "LINUX only") class TestSystemCPUStats(unittest.TestCase): @unittest.skipIf(TRAVIS, "fails on Travis") @@ -534,6 +734,7 @@ def test_ctx_switches(self): psutil_value = psutil.cpu_stats().ctx_switches self.assertAlmostEqual(vmstat_value, psutil_value, delta=500) + @unittest.skipIf(TRAVIS, "fails on Travis") def test_interrupts(self): vmstat_value = vmstat("interrupts") psutil_value = psutil.cpu_stats().interrupts @@ -541,11 +742,11 @@ def test_interrupts(self): # ===================================================================== -# system network +# --- system network # ===================================================================== -@unittest.skipUnless(LINUX, "LINUX only") +@unittest.skipIf(not LINUX, "LINUX only") class TestSystemNetwork(unittest.TestCase): def test_net_if_addrs_ips(self): @@ -564,26 +765,32 @@ def test_net_if_stats(self): except RuntimeError: pass else: - self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) + # Not always reliable. + # self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) self.assertEqual(stats.mtu, - int(re.findall('MTU:(\d+)', out)[0])) + int(re.findall(r'(?i)MTU[: ](\d+)', out)[0])) @retry_before_failing() def test_net_io_counters(self): def ifconfig(nic): ret = {} out = sh("ifconfig %s" % name) - ret['packets_recv'] = int(re.findall('RX packets:(\d+)', out)[0]) - ret['packets_sent'] = int(re.findall('TX packets:(\d+)', out)[0]) - ret['errin'] = int(re.findall('errors:(\d+)', out)[0]) - ret['errout'] = int(re.findall('errors:(\d+)', out)[1]) - ret['dropin'] = int(re.findall('dropped:(\d+)', out)[0]) - ret['dropout'] = int(re.findall('dropped:(\d+)', out)[1]) - ret['bytes_recv'] = int(re.findall('RX bytes:(\d+)', out)[0]) - ret['bytes_sent'] = int(re.findall('TX bytes:(\d+)', out)[0]) + ret['packets_recv'] = int( + re.findall(r'RX packets[: ](\d+)', out)[0]) + ret['packets_sent'] = int( + re.findall(r'TX packets[: ](\d+)', out)[0]) + ret['errin'] = int(re.findall(r'errors[: ](\d+)', out)[0]) + ret['errout'] = int(re.findall(r'errors[: ](\d+)', out)[1]) + ret['dropin'] = int(re.findall(r'dropped[: ](\d+)', out)[0]) + ret['dropout'] = int(re.findall(r'dropped[: ](\d+)', out)[1]) + ret['bytes_recv'] = int( + re.findall(r'RX (?:packets \d+ +)?bytes[: ](\d+)', out)[0]) + ret['bytes_sent'] = int( + re.findall(r'TX (?:packets \d+ +)?bytes[: ](\d+)', out)[0]) return ret - for name, stats in psutil.net_io_counters(pernic=True).items(): + nio = psutil.net_io_counters(pernic=True, nowrap=False) + for name, stats in nio.items(): try: ifconfig_ret = ifconfig(name) except RuntimeError: @@ -605,7 +812,7 @@ def ifconfig(nic): self.assertAlmostEqual( stats.dropout, ifconfig_ret['dropout'], delta=10) - @unittest.skipUnless(which('ip'), "'ip' utility not available") + @unittest.skipIf(not which('ip'), "'ip' utility not available") @unittest.skipIf(TRAVIS, "skipped on Travis") def test_net_if_names(self): out = sh("ip addr").strip() @@ -613,7 +820,7 @@ def test_net_if_names(self): found = 0 for line in out.split('\n'): line = line.strip() - if re.search("^\d+:", line): + if re.search(r"^\d+:", line): found += 1 name = line.split(':')[1].strip() self.assertIn(name, nics) @@ -652,15 +859,14 @@ def open_mock(name, *args, **kwargs): # ===================================================================== -# system disk +# --- system disk # ===================================================================== -@unittest.skipUnless(LINUX, "LINUX only") +@unittest.skipIf(not LINUX, "LINUX only") class TestSystemDisks(unittest.TestCase): - @unittest.skipUnless( - hasattr(os, 'statvfs'), "os.statvfs() function not available") + @unittest.skipIf(not hasattr(os, 'statvfs'), "os.statvfs() not available") @skip_on_not_implemented() def test_disk_partitions_and_usage(self): # test psutil.disk_usage() and psutil.disk_partitions() @@ -729,7 +935,7 @@ def open_mock(name, *args, **kwargs): orig_open = open patch_point = 'builtins.open' if PY3 else '__builtin__.open' with mock.patch(patch_point, side_effect=open_mock) as m: - ret = psutil.disk_io_counters() + ret = psutil.disk_io_counters(nowrap=False) assert m.called self.assertEqual(ret.read_count, 1) self.assertEqual(ret.read_merged_count, 2) @@ -761,7 +967,7 @@ def open_mock(name, *args, **kwargs): orig_open = open patch_point = 'builtins.open' if PY3 else '__builtin__.open' with mock.patch(patch_point, side_effect=open_mock) as m: - ret = psutil.disk_io_counters() + ret = psutil.disk_io_counters(nowrap=False) assert m.called self.assertEqual(ret.read_count, 1) self.assertEqual(ret.read_merged_count, 2) @@ -795,7 +1001,7 @@ def open_mock(name, *args, **kwargs): orig_open = open patch_point = 'builtins.open' if PY3 else '__builtin__.open' with mock.patch(patch_point, side_effect=open_mock) as m: - ret = psutil.disk_io_counters() + ret = psutil.disk_io_counters(nowrap=False) assert m.called self.assertEqual(ret.read_count, 1) self.assertEqual(ret.read_bytes, 2 * SECTOR_SIZE) @@ -810,11 +1016,11 @@ def open_mock(name, *args, **kwargs): # ===================================================================== -# misc +# --- misc # ===================================================================== -@unittest.skipUnless(LINUX, "LINUX only") +@unittest.skipIf(not LINUX, "LINUX only") class TestMisc(unittest.TestCase): def test_boot_time(self): @@ -841,7 +1047,7 @@ def open_mock(name, *args, **kwargs): patch_point = 'builtins.open' if PY3 else '__builtin__.open' with mock.patch(patch_point, side_effect=open_mock): - importlib.reload(psutil) + reload_module(psutil) assert tb.called self.assertRaises(IOError, psutil.cpu_times) @@ -880,43 +1086,10 @@ def open_mock(name, *args, **kwargs): sum(map(sum, psutil.cpu_times_percent(percpu=True))), 0) finally: shutil.rmtree(my_procfs) - importlib.reload(psutil) + reload_module(psutil) self.assertEqual(psutil.PROCFS_PATH, '/proc') - @unittest.skipUnless( - get_kernel_version() >= (2, 6, 36), - "prlimit() not available on this Linux kernel version") - def test_prlimit_availability(self): - # prlimit() should be available starting from kernel 2.6.36 - p = psutil.Process(os.getpid()) - p.rlimit(psutil.RLIMIT_NOFILE) - # if prlimit() is supported *at least* these constants should - # be available - self.assertTrue(hasattr(psutil, "RLIM_INFINITY")) - self.assertTrue(hasattr(psutil, "RLIMIT_AS")) - self.assertTrue(hasattr(psutil, "RLIMIT_CORE")) - self.assertTrue(hasattr(psutil, "RLIMIT_CPU")) - self.assertTrue(hasattr(psutil, "RLIMIT_DATA")) - self.assertTrue(hasattr(psutil, "RLIMIT_FSIZE")) - self.assertTrue(hasattr(psutil, "RLIMIT_LOCKS")) - self.assertTrue(hasattr(psutil, "RLIMIT_MEMLOCK")) - self.assertTrue(hasattr(psutil, "RLIMIT_NOFILE")) - self.assertTrue(hasattr(psutil, "RLIMIT_NPROC")) - self.assertTrue(hasattr(psutil, "RLIMIT_RSS")) - self.assertTrue(hasattr(psutil, "RLIMIT_STACK")) - - @unittest.skipUnless( - get_kernel_version() >= (3, 0), - "prlimit constants not available on this Linux kernel version") - def test_resource_consts_kernel_v(self): - # more recent constants - self.assertTrue(hasattr(psutil, "RLIMIT_MSGQUEUE")) - self.assertTrue(hasattr(psutil, "RLIMIT_NICE")) - self.assertTrue(hasattr(psutil, "RLIMIT_RTPRIO")) - self.assertTrue(hasattr(psutil, "RLIMIT_RTTIME")) - self.assertTrue(hasattr(psutil, "RLIMIT_SIGPENDING")) - def test_boot_time_mocked(self): with mock.patch('psutil._pslinux.open', create=True) as m: self.assertRaises( @@ -929,18 +1102,18 @@ def test_users_mocked(self): # to 'localhost'. with mock.patch('psutil._pslinux.cext.users', return_value=[('giampaolo', 'pts/2', ':0', - 1436573184.0, True)]) as m: + 1436573184.0, True, 2)]) as m: self.assertEqual(psutil.users()[0].host, 'localhost') assert m.called with mock.patch('psutil._pslinux.cext.users', return_value=[('giampaolo', 'pts/2', ':0.0', - 1436573184.0, True)]) as m: + 1436573184.0, True, 2)]) as m: self.assertEqual(psutil.users()[0].host, 'localhost') assert m.called # ...otherwise it should be returned as-is with mock.patch('psutil._pslinux.cext.users', return_value=[('giampaolo', 'pts/2', 'foo', - 1436573184.0, True)]) as m: + 1436573184.0, True, 2)]) as m: self.assertEqual(psutil.users()[0].host, 'foo') assert m.called @@ -969,7 +1142,7 @@ def test_sector_size_mock(self): def open_mock(name, *args, **kwargs): if PY3 and isinstance(name, bytes): name = name.decode() - if name.startswith("/sys/block/sda/queue/hw_sector_size"): + if "hw_sector_size" in name: flag.append(None) raise IOError(errno.ENOENT, '') else: @@ -978,23 +1151,309 @@ def open_mock(name, *args, **kwargs): flag = [] orig_open = open patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock): + psutil.disk_io_counters() + assert flag + + def test_issue_687(self): + # In case of thread ID: + # - pid_exists() is supposed to return False + # - Process(tid) is supposed to work + # - pids() should not return the TID + # See: https://github.com/giampaolo/psutil/issues/687 + t = ThreadTask() + t.start() try: - with mock.patch(patch_point, side_effect=open_mock): - importlib.reload(psutil._pslinux) - importlib.reload(psutil) - self.assertEqual(flag, [None]) - self.assertEqual(psutil._pslinux.SECTOR_SIZE, 512) + p = psutil.Process() + tid = p.threads()[1].id + assert not psutil.pid_exists(tid), tid + pt = psutil.Process(tid) + pt.as_dict() + self.assertNotIn(tid, psutil.pids()) finally: - importlib.reload(psutil._pslinux) - importlib.reload(psutil) + t.stop() + + def test_pid_exists_no_proc_status(self): + # Internally pid_exists relies on /proc/{pid}/status. + # Emulate a case where this file is empty in which case + # psutil is supposed to fall back on using pids(). + def open_mock(name, *args, **kwargs): + if name == "/proc/%s/status" % os.getpid(): + return io.StringIO(u("")) + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock): + assert psutil.pid_exists(os.getpid()) + + +# ===================================================================== +# --- sensors +# ===================================================================== + + +@unittest.skipIf(not LINUX, "LINUX only") +@unittest.skipIf(not HAS_BATTERY, "no battery") +class TestSensorsBattery(unittest.TestCase): + + @unittest.skipIf(not which("acpi"), "acpi utility not available") + def test_percent(self): + out = sh("acpi -b") + acpi_value = int(out.split(",")[1].strip().replace('%', '')) + psutil_value = psutil.sensors_battery().percent + self.assertAlmostEqual(acpi_value, psutil_value, delta=1) + + @unittest.skipIf(not which("acpi"), "acpi utility not available") + def test_power_plugged(self): + out = sh("acpi -b") + if 'unknown' in out.lower(): + return unittest.skip("acpi output not reliable") + if 'discharging at zero rate' in out: + plugged = True + else: + plugged = "Charging" in out.split('\n')[0] + self.assertEqual(psutil.sensors_battery().power_plugged, plugged) + + def test_emulate_power_plugged(self): + # Pretend the AC power cable is connected. + def open_mock(name, *args, **kwargs): + if name.endswith("AC0/online") or name.endswith("AC/online"): + return io.BytesIO(b"1") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + self.assertEqual(psutil.sensors_battery().power_plugged, True) + self.assertEqual( + psutil.sensors_battery().secsleft, psutil.POWER_TIME_UNLIMITED) + assert m.called + + def test_emulate_power_plugged_2(self): + # Same as above but pretend /AC0/online does not exist in which + # case code relies on /status file. + def open_mock(name, *args, **kwargs): + if name.endswith("AC0/online") or name.endswith("AC/online"): + raise IOError(errno.ENOENT, "") + elif name.endswith("/status"): + return io.StringIO(u("charging")) + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + self.assertEqual(psutil.sensors_battery().power_plugged, True) + assert m.called + + def test_emulate_power_not_plugged(self): + # Pretend the AC power cable is not connected. + def open_mock(name, *args, **kwargs): + if name.endswith("AC0/online") or name.endswith("AC/online"): + return io.BytesIO(b"0") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + self.assertEqual(psutil.sensors_battery().power_plugged, False) + assert m.called + + def test_emulate_power_not_plugged_2(self): + # Same as above but pretend /AC0/online does not exist in which + # case code relies on /status file. + def open_mock(name, *args, **kwargs): + if name.endswith("AC0/online") or name.endswith("AC/online"): + raise IOError(errno.ENOENT, "") + elif name.endswith("/status"): + return io.StringIO(u("discharging")) + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + self.assertEqual(psutil.sensors_battery().power_plugged, False) + assert m.called + + def test_emulate_power_undetermined(self): + # Pretend we can't know whether the AC power cable not + # connected (assert fallback to False). + def open_mock(name, *args, **kwargs): + if name.startswith("/sys/class/power_supply/AC0/online") or \ + name.startswith("/sys/class/power_supply/AC/online"): + raise IOError(errno.ENOENT, "") + elif name.startswith("/sys/class/power_supply/BAT0/status"): + return io.BytesIO(b"???") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + self.assertIsNone(psutil.sensors_battery().power_plugged) + assert m.called + + def test_emulate_no_base_files(self): + # Emulate a case where base metrics files are not present, + # in which case we're supposed to get None. + def open_mock(name, *args, **kwargs): + if name.startswith("/sys/class/power_supply/BAT0/energy_now") or \ + name.startswith("/sys/class/power_supply/BAT0/charge_now"): + raise IOError(errno.ENOENT, "") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + self.assertIsNone(psutil.sensors_battery()) + assert m.called + + def test_emulate_energy_full_0(self): + # Emulate a case where energy_full files returns 0. + def open_mock(name, *args, **kwargs): + if name.startswith("/sys/class/power_supply/BAT0/energy_full"): + return io.BytesIO(b"0") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + self.assertEqual(psutil.sensors_battery().percent, 0) + assert m.called + + def test_emulate_energy_full_not_avail(self): + # Emulate a case where energy_full file does not exist. + # Expected fallback on /capacity. + def open_mock(name, *args, **kwargs): + energy_full = "/sys/class/power_supply/BAT0/energy_full" + charge_full = "/sys/class/power_supply/BAT0/charge_full" + if name.startswith(energy_full) or name.startswith(charge_full): + raise IOError(errno.ENOENT, "") + elif name.startswith("/sys/class/power_supply/BAT0/capacity"): + return io.BytesIO(b"88") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + self.assertEqual(psutil.sensors_battery().percent, 88) + assert m.called + + def test_emulate_no_ac0_online(self): + # Emulate a case where /AC0/online file does not exist. + def path_exists_mock(name): + if name.startswith("/sys/class/power_supply/AC0/online"): + return False + else: + return orig_path_exists(name) + + orig_path_exists = os.path.exists + with mock.patch("psutil._pslinux.os.path.exists", + side_effect=path_exists_mock) as m: + psutil.sensors_battery() + assert m.called + + def test_emulate_no_power(self): + # Emulate a case where /AC0/online file nor /BAT0/status exist. + def open_mock(name, *args, **kwargs): + if name.startswith("/sys/class/power_supply/AC/online") or \ + name.startswith("/sys/class/power_supply/AC0/online") or \ + name.startswith("/sys/class/power_supply/BAT0/status"): + raise IOError(errno.ENOENT, "") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + self.assertIsNone(psutil.sensors_battery().power_plugged) + assert m.called + + +@unittest.skipIf(not LINUX, "LINUX only") +class TestSensorsTemperatures(unittest.TestCase): + + @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") + def test_emulate_eio_error(self): + def open_mock(name, *args, **kwargs): + if name.endswith("_input"): + raise OSError(errno.EIO, "") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + with warnings.catch_warnings(record=True) as ws: + self.assertEqual(psutil.sensors_temperatures(), {}) + assert m.called + self.assertIn("ignoring", str(ws[0].message)) + + def test_emulate_data(self): + def open_mock(name, *args, **kwargs): + if name.endswith('/name'): + return io.StringIO(u("name")) + elif name.endswith('/temp1_label'): + return io.StringIO(u("label")) + elif name.endswith('/temp1_input'): + return io.BytesIO(b"30000") + elif name.endswith('/temp1_max'): + return io.BytesIO(b"40000") + elif name.endswith('/temp1_crit'): + return io.BytesIO(b"50000") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock): + with mock.patch('glob.glob', + return_value=['/sys/class/hwmon/hwmon0/temp1']): + temp = psutil.sensors_temperatures()['name'][0] + self.assertEqual(temp.label, 'label') + self.assertEqual(temp.current, 30.0) + self.assertEqual(temp.high, 40.0) + self.assertEqual(temp.critical, 50.0) + + +@unittest.skipIf(not LINUX, "LINUX only") +class TestSensorsFans(unittest.TestCase): + + def test_emulate_data(self): + def open_mock(name, *args, **kwargs): + if name.endswith('/name'): + return io.StringIO(u("name")) + elif name.endswith('/fan1_label'): + return io.StringIO(u("label")) + elif name.endswith('/fan1_input'): + return io.StringIO(u("2000")) + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock): + with mock.patch('glob.glob', + return_value=['/sys/class/hwmon/hwmon2/fan1']): + fan = psutil.sensors_fans()['name'][0] + self.assertEqual(fan.label, 'label') + self.assertEqual(fan.current, 2000) # ===================================================================== -# test process +# --- test process # ===================================================================== -@unittest.skipUnless(LINUX, "LINUX only") +@unittest.skipIf(not LINUX, "LINUX only") class TestProcess(unittest.TestCase): def setUp(self): @@ -1002,49 +1461,6 @@ 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. - p = psutil.Process() - with psutil._psplatform.open_text('/proc/%s/status' % p.pid) as f: - for line in f: - line = line.strip() - if line.startswith('Name:'): - name = line.split()[1] - # Name is truncated to 15 chars - self.assertEqual(p.name()[:15], name[:15]) - elif line.startswith('State:'): - status = line[line.find('(') + 1:line.rfind(')')] - status = status.replace(' ', '-') - self.assertEqual(p.status(), status) - elif line.startswith('PPid:'): - ppid = int(line.split()[1]) - self.assertEqual(p.ppid(), ppid) - # The ones below internally are determined by reading - # 'status' file but we use a re to extract the info - # so it makes sense to check them. - elif line.startswith('Threads:'): - num_threads = int(line.split()[1]) - self.assertEqual(p.num_threads(), num_threads) - elif line.startswith('Uid:'): - uids = tuple(map(int, line.split()[1:4])) - self.assertEqual(tuple(p.uids()), uids) - elif line.startswith('Gid:'): - gids = tuple(map(int, line.split()[1:4])) - self.assertEqual(tuple(p.gids()), gids) - elif line.startswith('voluntary_ctxt_switches:'): - vol = int(line.split()[1]) - self.assertAlmostEqual( - p.num_ctx_switches().voluntary, vol, delta=2) - elif line.startswith('nonvoluntary_ctxt_switches:'): - invol = int(line.split()[1]) - self.assertAlmostEqual( - p.num_ctx_switches().involuntary, invol, - delta=2) - def test_memory_full_info(self): src = textwrap.dedent(""" import time @@ -1123,6 +1539,22 @@ def test_open_files_file_gone(self): self.assertEqual(p.open_files(), []) assert m.called + def test_open_files_fd_gone(self): + # Simulate a case where /proc/{pid}/fdinfo/{fd} disappears + # while iterating through fds. + # https://travis-ci.org/giampaolo/psutil/jobs/225694530 + p = psutil.Process() + files = p.open_files() + with tempfile.NamedTemporaryFile(): + # give the kernel some time to see the new file + call_until(p.open_files, "len(ret) != %i" % len(files)) + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, + side_effect=IOError(errno.ENOENT, "")) as m: + files = p.open_files() + assert not files + assert m.called + # --- mocked tests def test_terminal_mocked(self): @@ -1145,19 +1577,26 @@ def test_cmdline_mocked(self): fake_file = io.StringIO(u('foo\x00bar\x00')) with mock.patch('psutil._pslinux.open', return_value=fake_file, create=True) as m: - p.cmdline() == ['foo', 'bar'] + self.assertEqual(p.cmdline(), ['foo', 'bar']) assert m.called fake_file = io.StringIO(u('foo\x00bar\x00\x00')) with mock.patch('psutil._pslinux.open', return_value=fake_file, create=True) as m: - p.cmdline() == ['foo', 'bar', ''] + self.assertEqual(p.cmdline(), ['foo', 'bar', '']) assert m.called - def test_io_counters_mocked(self): - with mock.patch('psutil._pslinux.open', create=True) as m: - self.assertRaises( - NotImplementedError, - psutil._pslinux.Process(os.getpid()).io_counters) + def test_cmdline_spaces_mocked(self): + # see: https://github.com/giampaolo/psutil/issues/1179 + p = psutil.Process() + fake_file = io.StringIO(u('foo bar ')) + with mock.patch('psutil._pslinux.open', + return_value=fake_file, create=True) as m: + self.assertEqual(p.cmdline(), ['foo', 'bar']) + assert m.called + fake_file = io.StringIO(u('foo bar ')) + with mock.patch('psutil._pslinux.open', + return_value=fake_file, create=True) as m: + self.assertEqual(p.cmdline(), ['foo', 'bar', '']) assert m.called def test_readlink_path_deleted_mocked(self): @@ -1195,25 +1634,276 @@ def open_mock(name, *args, **kwargs): with mock.patch(patch_point, side_effect=open_mock): self.assertRaises(psutil.AccessDenied, psutil.Process().threads) - # not sure why (doesn't fail locally) - # https://travis-ci.org/giampaolo/psutil/jobs/108629915 - @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") def test_exe_mocked(self): - with mock.patch('psutil._pslinux.os.readlink', + with mock.patch('psutil._pslinux.readlink', + side_effect=OSError(errno.ENOENT, "")) as m1: + with mock.patch('psutil.Process.cmdline', + side_effect=psutil.AccessDenied(0, "")) as m2: + # No such file error; might be raised also if /proc/pid/exe + # path actually exists for system processes with low pids + # (about 0-20). In this case psutil is supposed to return + # an empty string. + ret = psutil.Process().exe() + assert m1.called + assert m2.called + self.assertEqual(ret, "") + + # ...but if /proc/pid no longer exist we're supposed to treat + # it as an alias for zombie process + with mock.patch('psutil._pslinux.os.path.lexists', + return_value=False): + self.assertRaises( + psutil.ZombieProcess, psutil.Process().exe) + + def test_issue_1014(self): + # Emulates a case where smaps file does not exist. In this case + # wrap_exception decorator should not raise NoSuchProcess. + def open_mock(name, *args, **kwargs): + if name.startswith('/proc/%s/smaps' % os.getpid()): + raise IOError(errno.ENOENT, "") + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock) as m: + p = psutil.Process() + with self.assertRaises(IOError) as err: + p.memory_maps() + self.assertEqual(err.exception.errno, errno.ENOENT) + assert m.called + + @unittest.skipIf(not HAS_RLIMIT, "not supported") + def test_rlimit_zombie(self): + # Emulate a case where rlimit() raises ENOSYS, which may + # happen in case of zombie process: + # https://travis-ci.org/giampaolo/psutil/jobs/51368273 + with mock.patch("psutil._pslinux.cext.linux_prlimit", + side_effect=OSError(errno.ENOSYS, "")) as m: + p = psutil.Process() + p.name() + with self.assertRaises(psutil.ZombieProcess) as exc: + p.rlimit(psutil.RLIMIT_NOFILE) + assert m.called + self.assertEqual(exc.exception.pid, p.pid) + self.assertEqual(exc.exception.name, p.name()) + + def test_cwd_zombie(self): + with mock.patch("psutil._pslinux.os.readlink", side_effect=OSError(errno.ENOENT, "")) as m: - # No such file error; might be raised also if /proc/pid/exe - # path actually exists for system processes with low pids - # (about 0-20). In this case psutil is supposed to return - # an empty string. - ret = psutil.Process().exe() + p = psutil.Process() + p.name() + with self.assertRaises(psutil.ZombieProcess) as exc: + p.cwd() + assert m.called + self.assertEqual(exc.exception.pid, p.pid) + self.assertEqual(exc.exception.name, p.name()) + + def test_stat_file_parsing(self): + from psutil._pslinux import CLOCK_TICKS + + def open_mock(name, *args, **kwargs): + if name.startswith('/proc/%s/stat' % os.getpid()): + args = [ + "0", # pid + "(cat)", # name + "Z", # status + "1", # ppid + "0", # pgrp + "0", # session + "0", # tty + "0", # tpgid + "0", # flags + "0", # minflt + "0", # cminflt + "0", # majflt + "0", # cmajflt + "2", # utime + "3", # stime + "4", # cutime + "5", # cstime + "0", # priority + "0", # nice + "0", # num_threads + "0", # itrealvalue + "6", # starttime + "0", # vsize + "0", # rss + "0", # rsslim + "0", # startcode + "0", # endcode + "0", # startstack + "0", # kstkesp + "0", # kstkeip + "0", # signal + "0", # blocked + "0", # sigignore + "0", # sigcatch + "0", # wchan + "0", # nswap + "0", # cnswap + "0", # exit_signal + "6", # processor + ] + return io.BytesIO(" ".join(args).encode()) + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock): + p = psutil.Process() + self.assertEqual(p.name(), 'cat') + self.assertEqual(p.status(), psutil.STATUS_ZOMBIE) + self.assertEqual(p.ppid(), 1) + self.assertEqual( + p.create_time(), 6 / CLOCK_TICKS + psutil.boot_time()) + cpu = p.cpu_times() + self.assertEqual(cpu.user, 2 / CLOCK_TICKS) + self.assertEqual(cpu.system, 3 / CLOCK_TICKS) + self.assertEqual(cpu.children_user, 4 / CLOCK_TICKS) + self.assertEqual(cpu.children_system, 5 / CLOCK_TICKS) + self.assertEqual(p.cpu_num(), 6) + + def test_status_file_parsing(self): + def open_mock(name, *args, **kwargs): + if name.startswith('/proc/%s/status' % os.getpid()): + return io.BytesIO(textwrap.dedent("""\ + Uid:\t1000\t1001\t1002\t1003 + Gid:\t1004\t1005\t1006\t1007 + Threads:\t66 + Cpus_allowed:\tf + Cpus_allowed_list:\t0-7 + voluntary_ctxt_switches:\t12 + nonvoluntary_ctxt_switches:\t13""").encode()) + else: + return orig_open(name, *args, **kwargs) + + orig_open = open + patch_point = 'builtins.open' if PY3 else '__builtin__.open' + with mock.patch(patch_point, side_effect=open_mock): + p = psutil.Process() + self.assertEqual(p.num_ctx_switches().voluntary, 12) + self.assertEqual(p.num_ctx_switches().involuntary, 13) + self.assertEqual(p.num_threads(), 66) + uids = p.uids() + self.assertEqual(uids.real, 1000) + self.assertEqual(uids.effective, 1001) + self.assertEqual(uids.saved, 1002) + gids = p.gids() + self.assertEqual(gids.real, 1004) + self.assertEqual(gids.effective, 1005) + self.assertEqual(gids.saved, 1006) + self.assertEqual(p._proc._get_eligible_cpus(), list(range(0, 8))) + + +@unittest.skipIf(not LINUX, "LINUX only") +class TestProcessAgainstStatus(unittest.TestCase): + """/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. + """ + + @classmethod + def setUpClass(cls): + cls.proc = psutil.Process() + + def read_status_file(self, linestart): + with psutil._psplatform.open_text( + '/proc/%s/status' % self.proc.pid) as f: + for line in f: + line = line.strip() + if line.startswith(linestart): + value = line.partition('\t')[2] + try: + return int(value) + except ValueError: + return value + raise ValueError("can't find %r" % linestart) + + def test_name(self): + value = self.read_status_file("Name:") + self.assertEqual(self.proc.name(), value) + + def test_status(self): + value = self.read_status_file("State:") + value = value[value.find('(') + 1:value.rfind(')')] + value = value.replace(' ', '-') + self.assertEqual(self.proc.status(), value) + + def test_ppid(self): + value = self.read_status_file("PPid:") + self.assertEqual(self.proc.ppid(), value) + + def test_num_threads(self): + value = self.read_status_file("Threads:") + self.assertEqual(self.proc.num_threads(), value) + + def test_uids(self): + value = self.read_status_file("Uid:") + value = tuple(map(int, value.split()[1:4])) + self.assertEqual(self.proc.uids(), value) + + def test_gids(self): + value = self.read_status_file("Gid:") + value = tuple(map(int, value.split()[1:4])) + self.assertEqual(self.proc.gids(), value) + + @retry_before_failing() + def test_num_ctx_switches(self): + value = self.read_status_file("voluntary_ctxt_switches:") + self.assertEqual(self.proc.num_ctx_switches().voluntary, value) + value = self.read_status_file("nonvoluntary_ctxt_switches:") + self.assertEqual(self.proc.num_ctx_switches().involuntary, value) + + def test_cpu_affinity(self): + value = self.read_status_file("Cpus_allowed_list:") + if '-' in str(value): + min_, max_ = map(int, value.split('-')) + self.assertEqual( + self.proc.cpu_affinity(), list(range(min_, max_ + 1))) + + def test_cpu_affinity_eligible_cpus(self): + value = self.read_status_file("Cpus_allowed_list:") + with mock.patch("psutil._pslinux.per_cpu_times") as m: + self.proc._proc._get_eligible_cpus() + if '-' in str(value): + assert not m.called + else: + assert m.called + + +# ===================================================================== +# --- test utils +# ===================================================================== + + +@unittest.skipIf(not LINUX, "LINUX only") +class TestUtils(unittest.TestCase): + + def test_open_text(self): + with psutil._psplatform.open_text(__file__) as f: + self.assertEqual(f.mode, 'rt') + + def test_open_binary(self): + with psutil._psplatform.open_binary(__file__) as f: + self.assertEqual(f.mode, 'rb') + + def test_readlink(self): + with mock.patch("os.readlink", return_value="foo (deleted)") as m: + self.assertEqual(psutil._psplatform.readlink("bar"), "foo") assert m.called - self.assertEqual(ret, "") - # ...but if /proc/pid no longer exist we're supposed to treat - # it as an alias for zombie process - with mock.patch('psutil._pslinux.os.path.lexists', - return_value=False): - self.assertRaises(psutil.ZombieProcess, psutil.Process().exe) + def test_cat(self): + fname = os.path.abspath(TESTFN) + with open(fname, "wt") as f: + f.write("foo ") + self.assertEqual(psutil._psplatform.cat(TESTFN, binary=False), "foo") + self.assertEqual(psutil._psplatform.cat(TESTFN, binary=True), b"foo") + self.assertEqual( + psutil._psplatform.cat(TESTFN + '??', fallback="bar"), "bar") if __name__ == '__main__': diff --git a/psutil/tests/test_memory_leaks.py b/psutil/tests/test_memory_leaks.py index 46186e411..680fe7803 100755 --- a/psutil/tests/test_memory_leaks.py +++ b/psutil/tests/test_memory_leaks.py @@ -13,30 +13,41 @@ for some reason). """ +from __future__ import print_function import errno import functools import gc import os -import socket +import sys import threading import time import psutil import psutil._common -from psutil import FREEBSD from psutil import LINUX from psutil import OPENBSD from psutil import OSX from psutil import POSIX from psutil import SUNOS from psutil import WINDOWS -from psutil._common import supports_ipv6 from psutil._compat import xrange +from psutil.tests import create_sockets from psutil.tests import get_test_subprocess +from psutil.tests import HAS_CPU_AFFINITY +from psutil.tests import HAS_CPU_FREQ +from psutil.tests import HAS_ENVIRON +from psutil.tests import HAS_IONICE +from psutil.tests import HAS_MEMORY_MAPS +from psutil.tests import HAS_PROC_CPU_NUM +from psutil.tests import HAS_PROC_IO_COUNTERS +from psutil.tests import HAS_RLIMIT +from psutil.tests import HAS_SENSORS_BATTERY +from psutil.tests import HAS_SENSORS_FANS +from psutil.tests import HAS_SENSORS_TEMPERATURES from psutil.tests import reap_children -from psutil.tests import RLIMIT_SUPPORT from psutil.tests import run_test_module_by_name from psutil.tests import safe_rmpath +from psutil.tests import skip_on_access_denied from psutil.tests import TESTFN from psutil.tests import TRAVIS from psutil.tests import unittest @@ -49,6 +60,7 @@ SKIP_PYTHON_IMPL = True if TRAVIS else False cext = psutil._psplatform.cext thisproc = psutil.Process() +SKIP_PYTHON_IMPL = True if TRAVIS else False # =================================================================== @@ -104,9 +116,12 @@ def call_many_times(): loops = kwargs.pop('loops_', None) or self.loops retry_for = kwargs.pop('retry_for_', None) or self.retry_for - self._call(fun, *args, **kwargs) + # warm up + for x in range(10): + self._call(fun, *args, **kwargs) self.assertEqual(gc.garbage, []) self.assertEqual(threading.active_count(), 1) + self.assertEqual(thisproc.children(), []) # Get 2 distinct memory samples, before and after having # called fun repeadetly. @@ -139,12 +154,14 @@ def call_many_times(): if mem3 > mem2: # failure - self.fail("+%s after %s calls, +%s after another %s calls" % ( - bytes2human(diff1), - loops, - bytes2human(diff2), - ncalls - )) + extra_proc_mem = bytes2human(diff1 + diff2) + print("exta proc mem: %s" % extra_proc_mem, file=sys.stderr) + msg = "+%s after %s calls, +%s after another %s calls, " + msg += "+%s extra proc mem" + msg = msg % ( + bytes2human(diff1), loops, bytes2human(diff2), ncalls, + extra_proc_mem) + self.fail(msg) def execute_w_exc(self, exc, fun, *args, **kwargs): """Convenience function which tests a callable raising @@ -179,6 +196,19 @@ class TestProcessObjectLeaks(TestMemLeak): proc = thisproc + def test_coverage(self): + skip = set(( + "pid", "as_dict", "children", "cpu_affinity", "cpu_percent", + "ionice", "is_running", "kill", "memory_info_ex", "memory_percent", + "nice", "oneshot", "parent", "rlimit", "send_signal", "suspend", + "terminate", "wait")) + for name in dir(psutil.Process): + if name.startswith('_'): + continue + if name in skip: + continue + self.assertTrue(hasattr(self, "test_" + name), msg=name) + @skip_if_linux() def test_name(self): self.execute(self.proc.name) @@ -195,12 +225,12 @@ def test_exe(self): def test_ppid(self): self.execute(self.proc.ppid) - @unittest.skipUnless(POSIX, "POSIX only") + @unittest.skipIf(not POSIX, "POSIX only") @skip_if_linux() def test_uids(self): self.execute(self.proc.uids) - @unittest.skipUnless(POSIX, "POSIX only") + @unittest.skipIf(not POSIX, "POSIX only") @skip_if_linux() def test_gids(self): self.execute(self.proc.gids) @@ -216,13 +246,11 @@ def test_nice_set(self): niceness = thisproc.nice() self.execute(self.proc.nice, niceness) - @unittest.skipUnless(hasattr(psutil.Process, 'ionice'), - "platform not supported") + @unittest.skipIf(not HAS_IONICE, "not supported") def test_ionice_get(self): self.execute(self.proc.ionice) - @unittest.skipUnless(hasattr(psutil.Process, 'ionice'), - "platform not supported") + @unittest.skipIf(not HAS_IONICE, "not supported") def test_ionice_set(self): if WINDOWS: value = thisproc.ionice() @@ -232,7 +260,7 @@ def test_ionice_set(self): fun = functools.partial(cext.proc_ioprio_set, os.getpid(), -1, 0) self.execute_w_exc(OSError, fun) - @unittest.skipIf(OSX or SUNOS, "platform not supported") + @unittest.skipIf(not HAS_PROC_IO_COUNTERS, "not supported") @skip_if_linux() def test_io_counters(self): self.execute(self.proc.io_counters) @@ -246,19 +274,25 @@ def test_create_time(self): self.execute(self.proc.create_time) @skip_if_linux() + @skip_on_access_denied(only_if=OPENBSD) def test_num_threads(self): self.execute(self.proc.num_threads) - @unittest.skipUnless(WINDOWS, "WINDOWS only") + @unittest.skipIf(not WINDOWS, "WINDOWS only") def test_num_handles(self): self.execute(self.proc.num_handles) - @unittest.skipUnless(POSIX, "POSIX only") + @unittest.skipIf(not POSIX, "POSIX only") @skip_if_linux() def test_num_fds(self): self.execute(self.proc.num_fds) @skip_if_linux() + def test_num_ctx_switches(self): + self.execute(self.proc.num_ctx_switches) + + @skip_if_linux() + @skip_on_access_denied(only_if=OPENBSD) def test_threads(self): self.execute(self.proc.threads) @@ -266,17 +300,20 @@ def test_threads(self): def test_cpu_times(self): self.execute(self.proc.cpu_times) + @skip_if_linux() + @unittest.skipIf(not HAS_PROC_CPU_NUM, "not supported") + def test_cpu_num(self): + self.execute(self.proc.cpu_num) + @skip_if_linux() def test_memory_info(self): self.execute(self.proc.memory_info) - # also available on Linux but it's pure python - @unittest.skipUnless(OSX or WINDOWS, - "platform not supported") + @skip_if_linux() def test_memory_full_info(self): self.execute(self.proc.memory_full_info) - @unittest.skipUnless(POSIX, "POSIX only") + @unittest.skipIf(not POSIX, "POSIX only") @skip_if_linux() def test_terminal(self): self.execute(self.proc.terminal) @@ -290,13 +327,11 @@ def test_resume(self): def test_cwd(self): self.execute(self.proc.cwd) - @unittest.skipUnless(WINDOWS or LINUX or FREEBSD, - "platform not supported") + @unittest.skipIf(not HAS_CPU_AFFINITY, "not supported") def test_cpu_affinity_get(self): self.execute(self.proc.cpu_affinity) - @unittest.skipUnless(WINDOWS or LINUX or FREEBSD, - "platform not supported") + @unittest.skipIf(not HAS_CPU_AFFINITY, "not supported") def test_cpu_affinity_set(self): affinity = thisproc.cpu_affinity() self.execute(self.proc.cpu_affinity, affinity) @@ -311,18 +346,18 @@ def test_open_files(self): # OSX implementation is unbelievably slow @unittest.skipIf(OSX, "too slow on OSX") - @unittest.skipIf(OPENBSD, "platform not supported") + @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") @skip_if_linux() def test_memory_maps(self): self.execute(self.proc.memory_maps) - @unittest.skipUnless(LINUX, "LINUX only") - @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, "LINUX >= 2.6.36 only") + @unittest.skipIf(not LINUX, "LINUX only") + @unittest.skipIf(not HAS_RLIMIT, "not supported") def test_rlimit_get(self): self.execute(self.proc.rlimit, psutil.RLIMIT_NOFILE) - @unittest.skipUnless(LINUX, "LINUX only") - @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, "LINUX >= 2.6.36 only") + @unittest.skipIf(not LINUX, "LINUX only") + @unittest.skipIf(not HAS_RLIMIT, "not supported") def test_rlimit_set(self): limit = thisproc.rlimit(psutil.RLIMIT_NOFILE) self.execute(self.proc.rlimit, psutil.RLIMIT_NOFILE, limit) @@ -333,43 +368,18 @@ def test_rlimit_set(self): # function (tested later). @unittest.skipIf(WINDOWS, "worthless on WINDOWS") def test_connections(self): - def create_socket(family, type): - sock = socket.socket(family, type) - sock.bind(('', 0)) - if type == socket.SOCK_STREAM: - sock.listen(1) - return sock - - socks = [] - socks.append(create_socket(socket.AF_INET, socket.SOCK_STREAM)) - socks.append(create_socket(socket.AF_INET, socket.SOCK_DGRAM)) - if supports_ipv6(): - socks.append(create_socket(socket.AF_INET6, socket.SOCK_STREAM)) - socks.append(create_socket(socket.AF_INET6, socket.SOCK_DGRAM)) - if hasattr(socket, 'AF_UNIX'): - safe_rmpath(TESTFN) - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - s.bind(TESTFN) - s.listen(1) - socks.append(s) - kind = 'all' # TODO: UNIX sockets are temporarily implemented by parsing # 'pfiles' cmd output; we don't want that part of the code to # be executed. - if SUNOS: - kind = 'inet' - try: + with create_sockets(): + kind = 'inet' if SUNOS else 'all' self.execute(self.proc.connections, kind) - finally: - for s in socks: - s.close() - @unittest.skipUnless(hasattr(psutil.Process, 'environ'), - "platform not supported") + @unittest.skipIf(not HAS_ENVIRON, "not supported") def test_environ(self): self.execute(self.proc.environ) - @unittest.skipUnless(WINDOWS, "WINDOWS only") + @unittest.skipIf(not WINDOWS, "WINDOWS only") def test_proc_info(self): self.execute(cext.proc_info, os.getpid()) @@ -437,6 +447,17 @@ def call(): class TestModuleFunctionsLeaks(TestMemLeak): """Test leaks of psutil module functions.""" + def test_coverage(self): + skip = set(( + "version_info", "__version__", "process_iter", "wait_procs", + "cpu_percent", "cpu_times_percent", "cpu_count")) + for name in psutil.__all__: + if not name.islower(): + continue + if name in skip: + continue + self.assertTrue(hasattr(self, "test_" + name), msg=name) + # --- cpu @skip_if_linux() @@ -458,6 +479,11 @@ def test_per_cpu_times(self): def test_cpu_stats(self): self.execute(psutil.cpu_stats) + @skip_if_linux() + @unittest.skipIf(not HAS_CPU_FREQ, "not supported") + def test_cpu_freq(self): + self.execute(psutil.cpu_freq) + # --- mem def test_virtual_memory(self): @@ -488,19 +514,26 @@ def test_disk_partitions(self): '/proc/diskstats not available on this Linux version') @skip_if_linux() def test_disk_io_counters(self): - self.execute(psutil.disk_io_counters) + self.execute(psutil.disk_io_counters, nowrap=False) + + # --- proc + + @skip_if_linux() + def test_pids(self): + self.execute(psutil.pids) # --- net @skip_if_linux() def test_net_io_counters(self): - self.execute(psutil.net_io_counters) + self.execute(psutil.net_io_counters, nowrap=False) @unittest.skipIf(LINUX, "worthless on Linux (pure python)") @unittest.skipIf(OSX and os.getuid() != 0, "need root access") def test_net_connections(self): - self.execute(psutil.net_connections) + with create_sockets(): + self.execute(psutil.net_connections) def test_net_if_addrs(self): # Note: verified that on Windows this was a false positive. @@ -511,6 +544,23 @@ def test_net_if_addrs(self): def test_net_if_stats(self): self.execute(psutil.net_if_stats) + # --- sensors + + @skip_if_linux() + @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported") + def test_sensors_battery(self): + self.execute(psutil.sensors_battery) + + @skip_if_linux() + @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") + def test_sensors_temperatures(self): + self.execute(psutil.sensors_temperatures) + + @skip_if_linux() + @unittest.skipIf(not HAS_SENSORS_FANS, "not supported") + def test_sensors_fans(self): + self.execute(psutil.sensors_fans) + # --- others @skip_if_linux() @@ -529,6 +579,9 @@ def test_users(self): def test_win_service_iter(self): self.execute(cext.winservice_enumerate) + def test_win_service_get(self): + pass + def test_win_service_get_config(self): name = next(psutil.win_service_iter()).name() self.execute(cext.winservice_query_config, name) diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index 6db776ee1..f67c0e4cd 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. # Use of this source code is governed by a BSD-style license that can be @@ -9,47 +10,72 @@ """ import ast +import collections +import contextlib import errno -import imp import json import os import pickle -import psutil import socket import stat -import sys from psutil import LINUX -from psutil import NETBSD -from psutil import OPENBSD -from psutil import OSX from psutil import POSIX from psutil import WINDOWS from psutil._common import memoize from psutil._common import memoize_when_activated from psutil._common import supports_ipv6 +from psutil._common import wrap_numbers +from psutil._compat import PY3 from psutil.tests import APPVEYOR +from psutil.tests import bind_socket +from psutil.tests import bind_unix_socket +from psutil.tests import call_until from psutil.tests import chdir +from psutil.tests import create_proc_children_pair +from psutil.tests import create_sockets +from psutil.tests import create_zombie_proc +from psutil.tests import DEVNULL +from psutil.tests import get_free_port from psutil.tests import get_test_subprocess -from psutil.tests import importlib +from psutil.tests import HAS_BATTERY +from psutil.tests import HAS_CONNECTIONS_UNIX +from psutil.tests import HAS_MEMORY_FULL_INFO +from psutil.tests import HAS_MEMORY_MAPS +from psutil.tests import HAS_SENSORS_BATTERY +from psutil.tests import HAS_SENSORS_FANS +from psutil.tests import HAS_SENSORS_TEMPERATURES +from psutil.tests import import_module_by_path +from psutil.tests import is_namedtuple from psutil.tests import mock +from psutil.tests import PYTHON_EXE from psutil.tests import reap_children +from psutil.tests import reload_module from psutil.tests import retry from psutil.tests import ROOT_DIR from psutil.tests import run_test_module_by_name from psutil.tests import safe_rmpath from psutil.tests import SCRIPTS_DIR from psutil.tests import sh +from psutil.tests import tcp_socketpair from psutil.tests import TESTFN from psutil.tests import TOX from psutil.tests import TRAVIS from psutil.tests import unittest +from psutil.tests import unix_socket_path +from psutil.tests import unix_socketpair from psutil.tests import wait_for_file from psutil.tests import wait_for_pid +import psutil +import psutil.tests + + +# =================================================================== +# --- Misc / generic tests. +# =================================================================== class TestMisc(unittest.TestCase): - """Misc / generic tests.""" def test_process__repr__(self, func=repr): p = psutil.Process() @@ -219,6 +245,7 @@ def foo(*args, **kwargs): def test_memoize_when_activated(self): class Foo: + @memoize_when_activated def foo(self): calls.append(None) @@ -265,27 +292,36 @@ def k(s): self.assertEqual(parse_environ_block("a=1\0b=2"), {k("a"): "1"}) def test_supports_ipv6(self): + self.addCleanup(supports_ipv6.cache_clear) if supports_ipv6(): with mock.patch('psutil._common.socket') as s: s.has_ipv6 = False + supports_ipv6.cache_clear() assert not supports_ipv6() + + supports_ipv6.cache_clear() with mock.patch('psutil._common.socket.socket', side_effect=socket.error) as s: assert not supports_ipv6() assert s.called + + supports_ipv6.cache_clear() with mock.patch('psutil._common.socket.socket', side_effect=socket.gaierror) as s: assert not supports_ipv6() + supports_ipv6.cache_clear() assert s.called + + supports_ipv6.cache_clear() with mock.patch('psutil._common.socket.socket.bind', side_effect=socket.gaierror) as s: assert not supports_ipv6() + supports_ipv6.cache_clear() assert s.called else: - if hasattr(socket, 'AF_INET6'): - with self.assertRaises(Exception): - sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - sock.bind(("::1", 0)) + with self.assertRaises(Exception): + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + sock.bind(("::1", 0)) def test_isfile_strict(self): from psutil._common import isfile_strict @@ -329,7 +365,9 @@ def check(ret): def test_setup_script(self): setup_py = os.path.join(ROOT_DIR, 'setup.py') - module = imp.load_source('setup', setup_py) + if TRAVIS and not os.path.exists(setup_py): + return self.skipTest("can't find setup.py") + module = import_module_by_path(setup_py) self.assertRaises(SystemExit, module.setup) self.assertEqual(module.get_version(), psutil.__version__) @@ -355,25 +393,272 @@ def test_sanity_version_check(self): with mock.patch( "psutil._psplatform.cext.version", return_value="0.0.0"): with self.assertRaises(ImportError) as cm: - importlib.reload(psutil) + reload_module(psutil) self.assertIn("version conflict", str(cm.exception).lower()) +# =================================================================== +# --- Tests for wrap_numbers() function. +# =================================================================== + + +nt = collections.namedtuple('foo', 'a b c') + + +class TestWrapNumbers(unittest.TestCase): + + def setUp(self): + wrap_numbers.cache_clear() + + tearDown = setUp + + def test_first_call(self): + input = {'disk1': nt(5, 5, 5)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + + def test_input_hasnt_changed(self): + input = {'disk1': nt(5, 5, 5)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + + def test_increase_but_no_wrap(self): + input = {'disk1': nt(5, 5, 5)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + input = {'disk1': nt(10, 15, 20)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + input = {'disk1': nt(20, 25, 30)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + input = {'disk1': nt(20, 25, 30)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + + def test_wrap(self): + # let's say 100 is the threshold + input = {'disk1': nt(100, 100, 100)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + # first wrap restarts from 10 + input = {'disk1': nt(100, 100, 10)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(100, 100, 110)}) + # then it remains the same + input = {'disk1': nt(100, 100, 10)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(100, 100, 110)}) + # then it goes up + input = {'disk1': nt(100, 100, 90)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(100, 100, 190)}) + # then it wraps again + input = {'disk1': nt(100, 100, 20)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(100, 100, 210)}) + # and remains the same + input = {'disk1': nt(100, 100, 20)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(100, 100, 210)}) + # now wrap another num + input = {'disk1': nt(50, 100, 20)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(150, 100, 210)}) + # and again + input = {'disk1': nt(40, 100, 20)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(190, 100, 210)}) + # keep it the same + input = {'disk1': nt(40, 100, 20)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(190, 100, 210)}) + + def test_changing_keys(self): + # Emulate a case where the second call to disk_io() + # (or whatever) provides a new disk, then the new disk + # disappears on the third call. + input = {'disk1': nt(5, 5, 5)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + input = {'disk1': nt(5, 5, 5), + 'disk2': nt(7, 7, 7)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + input = {'disk1': nt(8, 8, 8)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + + def test_changing_keys_w_wrap(self): + input = {'disk1': nt(50, 50, 50), + 'disk2': nt(100, 100, 100)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + # disk 2 wraps + input = {'disk1': nt(50, 50, 50), + 'disk2': nt(100, 100, 10)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(50, 50, 50), + 'disk2': nt(100, 100, 110)}) + # disk 2 disappears + input = {'disk1': nt(50, 50, 50)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + + # then it appears again; the old wrap is supposed to be + # gone. + input = {'disk1': nt(50, 50, 50), + 'disk2': nt(100, 100, 100)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + # remains the same + input = {'disk1': nt(50, 50, 50), + 'disk2': nt(100, 100, 100)} + self.assertEqual(wrap_numbers(input, 'disk_io'), input) + # and then wraps again + input = {'disk1': nt(50, 50, 50), + 'disk2': nt(100, 100, 10)} + self.assertEqual(wrap_numbers(input, 'disk_io'), + {'disk1': nt(50, 50, 50), + 'disk2': nt(100, 100, 110)}) + + def test_real_data(self): + d = {'nvme0n1': (300, 508, 640, 1571, 5970, 1987, 2049, 451751, 47048), + 'nvme0n1p1': (1171, 2, 5600256, 1024, 516, 0, 0, 0, 8), + 'nvme0n1p2': (54, 54, 2396160, 5165056, 4, 24, 30, 1207, 28), + 'nvme0n1p3': (2389, 4539, 5154, 150, 4828, 1844, 2019, 398, 348)} + self.assertEqual(wrap_numbers(d, 'disk_io'), d) + self.assertEqual(wrap_numbers(d, 'disk_io'), d) + # decrease this ↓ + d = {'nvme0n1': (100, 508, 640, 1571, 5970, 1987, 2049, 451751, 47048), + 'nvme0n1p1': (1171, 2, 5600256, 1024, 516, 0, 0, 0, 8), + 'nvme0n1p2': (54, 54, 2396160, 5165056, 4, 24, 30, 1207, 28), + 'nvme0n1p3': (2389, 4539, 5154, 150, 4828, 1844, 2019, 398, 348)} + out = wrap_numbers(d, 'disk_io') + self.assertEqual(out['nvme0n1'][0], 400) + + # --- cache tests + + def test_cache_first_call(self): + input = {'disk1': nt(5, 5, 5)} + wrap_numbers(input, 'disk_io') + cache = wrap_numbers.cache_info() + self.assertEqual(cache[0], {'disk_io': input}) + self.assertEqual(cache[1], {'disk_io': {}}) + self.assertEqual(cache[2], {'disk_io': {}}) + + def test_cache_call_twice(self): + input = {'disk1': nt(5, 5, 5)} + wrap_numbers(input, 'disk_io') + input = {'disk1': nt(10, 10, 10)} + wrap_numbers(input, 'disk_io') + cache = wrap_numbers.cache_info() + self.assertEqual(cache[0], {'disk_io': input}) + self.assertEqual( + cache[1], + {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0}}) + self.assertEqual(cache[2], {'disk_io': {}}) + + def test_cache_wrap(self): + # let's say 100 is the threshold + input = {'disk1': nt(100, 100, 100)} + wrap_numbers(input, 'disk_io') + + # first wrap restarts from 10 + input = {'disk1': nt(100, 100, 10)} + wrap_numbers(input, 'disk_io') + cache = wrap_numbers.cache_info() + self.assertEqual(cache[0], {'disk_io': input}) + self.assertEqual( + cache[1], + {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 100}}) + self.assertEqual(cache[2], {'disk_io': {'disk1': set([('disk1', 2)])}}) + + def assert_(): + cache = wrap_numbers.cache_info() + self.assertEqual( + cache[1], + {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, + ('disk1', 2): 100}}) + self.assertEqual(cache[2], + {'disk_io': {'disk1': set([('disk1', 2)])}}) + + # then it remains the same + input = {'disk1': nt(100, 100, 10)} + wrap_numbers(input, 'disk_io') + cache = wrap_numbers.cache_info() + self.assertEqual(cache[0], {'disk_io': input}) + assert_() + + # then it goes up + input = {'disk1': nt(100, 100, 90)} + wrap_numbers(input, 'disk_io') + cache = wrap_numbers.cache_info() + self.assertEqual(cache[0], {'disk_io': input}) + assert_() + + # then it wraps again + input = {'disk1': nt(100, 100, 20)} + wrap_numbers(input, 'disk_io') + cache = wrap_numbers.cache_info() + self.assertEqual(cache[0], {'disk_io': input}) + self.assertEqual( + cache[1], + {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 190}}) + self.assertEqual(cache[2], {'disk_io': {'disk1': set([('disk1', 2)])}}) + + def test_cache_changing_keys(self): + input = {'disk1': nt(5, 5, 5)} + wrap_numbers(input, 'disk_io') + input = {'disk1': nt(5, 5, 5), + 'disk2': nt(7, 7, 7)} + wrap_numbers(input, 'disk_io') + cache = wrap_numbers.cache_info() + self.assertEqual(cache[0], {'disk_io': input}) + self.assertEqual( + cache[1], + {'disk_io': {('disk1', 0): 0, ('disk1', 1): 0, ('disk1', 2): 0}}) + self.assertEqual(cache[2], {'disk_io': {}}) + + def test_cache_clear(self): + input = {'disk1': nt(5, 5, 5)} + wrap_numbers(input, 'disk_io') + wrap_numbers(input, 'disk_io') + wrap_numbers.cache_clear('disk_io') + self.assertEqual(wrap_numbers.cache_info(), ({}, {}, {})) + wrap_numbers.cache_clear('disk_io') + wrap_numbers.cache_clear('?!?') + + @unittest.skipIf( + not psutil.disk_io_counters() or not psutil.net_io_counters(), + "no disks or NICs available") + def test_cache_clear_public_apis(self): + psutil.disk_io_counters() + psutil.net_io_counters() + caches = wrap_numbers.cache_info() + for cache in caches: + self.assertIn('psutil.disk_io_counters', cache) + self.assertIn('psutil.net_io_counters', cache) + + psutil.disk_io_counters.cache_clear() + caches = wrap_numbers.cache_info() + for cache in caches: + self.assertIn('psutil.net_io_counters', cache) + self.assertNotIn('psutil.disk_io_counters', cache) + + psutil.net_io_counters.cache_clear() + caches = wrap_numbers.cache_info() + self.assertEqual(caches, ({}, {}, {})) + + # =================================================================== # --- Example script tests # =================================================================== @unittest.skipIf(TOX, "can't test on TOX") +# See: https://travis-ci.org/giampaolo/psutil/jobs/295224806 +@unittest.skipIf(TRAVIS and not os.path.exists(SCRIPTS_DIR), + "can't locate scripts directory") class TestScripts(unittest.TestCase): """Tests for scripts in the "scripts" directory.""" - def assert_stdout(self, exe, args=None): - exe = '"%s"' % os.path.join(SCRIPTS_DIR, exe) - if args: - exe = exe + ' ' + args + @staticmethod + def assert_stdout(exe, *args, **kwargs): + exe = '%s' % os.path.join(SCRIPTS_DIR, exe) + cmd = [PYTHON_EXE, exe] + for arg in args: + cmd.append(arg) try: - out = sh(sys.executable + ' ' + exe).strip() + out = sh(cmd, **kwargs).strip() except RuntimeError as err: if 'AccessDenied' in str(err): return str(err) @@ -382,13 +667,18 @@ def assert_stdout(self, exe, args=None): assert out, out return out - def assert_syntax(self, exe, args=None): + @staticmethod + def assert_syntax(exe, args=None): exe = os.path.join(SCRIPTS_DIR, exe) - with open(exe, 'r') as f: + if PY3: + f = open(exe, 'rt', encoding='utf8') + else: + f = open(exe, 'rt') + with f: src = f.read() ast.parse(src) - def test_check_presence(self): + def test_coverage(self): # make sure all example scripts have a test method defined meths = dir(self) for name in os.listdir(SCRIPTS_DIR): @@ -398,7 +688,7 @@ def test_check_presence(self): self.fail('no test defined for %r script' % os.path.join(SCRIPTS_DIR, name)) - @unittest.skipUnless(POSIX, "POSIX only") + @unittest.skipIf(not POSIX, "POSIX only") def test_executable(self): for name in os.listdir(SCRIPTS_DIR): if name.endswith('.py'): @@ -416,7 +706,7 @@ def test_meminfo(self): self.assert_stdout('meminfo.py') def test_procinfo(self): - self.assert_stdout('procinfo.py', args=str(os.getpid())) + self.assert_stdout('procinfo.py', str(os.getpid())) # can't find users on APPVEYOR or TRAVIS @unittest.skipIf(APPVEYOR or TRAVIS and not psutil.users(), @@ -438,13 +728,13 @@ def test_netstat(self): def test_ifconfig(self): self.assert_stdout('ifconfig.py') - @unittest.skipIf(OPENBSD or NETBSD, "platform not supported") + @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") def test_pmap(self): - self.assert_stdout('pmap.py', args=str(os.getpid())) + self.assert_stdout('pmap.py', str(os.getpid())) - @unittest.skipUnless(OSX or WINDOWS or LINUX, "platform not supported") + @unittest.skipIf(not HAS_MEMORY_FULL_INFO, "not supported") def test_procsmem(self): - self.assert_stdout('procsmem.py') + self.assert_stdout('procsmem.py', stderr=DEVNULL) def test_killall(self): self.assert_syntax('killall.py') @@ -459,13 +749,34 @@ def test_iotop(self): self.assert_syntax('iotop.py') def test_pidof(self): - output = self.assert_stdout('pidof.py', args=psutil.Process().name()) + output = self.assert_stdout('pidof.py', psutil.Process().name()) self.assertIn(str(os.getpid()), output) - @unittest.skipUnless(WINDOWS, "WINDOWS only") + @unittest.skipIf(not WINDOWS, "WINDOWS only") def test_winservices(self): self.assert_stdout('winservices.py') + def test_cpu_distribution(self): + self.assert_syntax('cpu_distribution.py') + + @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") + @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") + def test_temperatures(self): + self.assert_stdout('temperatures.py') + + @unittest.skipIf(not HAS_SENSORS_FANS, "not supported") + @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") + def test_fans(self): + self.assert_stdout('fans.py') + + @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported") + @unittest.skipIf(not HAS_BATTERY, "no battery") + def test_battery(self): + self.assert_stdout('battery.py') + + def test_sensors(self): + self.assert_stdout('sensors.py') + # =================================================================== # --- Unit tests for test utilities. @@ -569,9 +880,13 @@ def test_wait_for_file_no_file(self): def test_wait_for_file_no_delete(self): with open(TESTFN, 'w') as f: f.write('foo') - wait_for_file(TESTFN, delete_file=False) + wait_for_file(TESTFN, delete=False) assert os.path.exists(TESTFN) + def test_call_until(self): + ret = call_until(lambda: 1, "ret == 1") + self.assertEqual(ret, 1) + class TestFSTestUtils(unittest.TestCase): @@ -606,7 +921,7 @@ def test_chdir(self): self.assertEqual(os.getcwd(), base) -class TestTestUtils(unittest.TestCase): +class TestProcessUtils(unittest.TestCase): def test_reap_children(self): subp = get_test_subprocess() @@ -614,6 +929,110 @@ def test_reap_children(self): assert p.is_running() reap_children() assert not p.is_running() + assert not psutil.tests._pids_started + assert not psutil.tests._subprocesses_started + + def test_create_proc_children_pair(self): + p1, p2 = create_proc_children_pair() + self.assertNotEqual(p1.pid, p2.pid) + assert p1.is_running() + assert p2.is_running() + children = psutil.Process().children(recursive=True) + self.assertEqual(len(children), 2) + self.assertIn(p1, children) + self.assertIn(p2, children) + self.assertEqual(p1.ppid(), os.getpid()) + self.assertEqual(p2.ppid(), p1.pid) + + # make sure both of them are cleaned up + reap_children() + assert not p1.is_running() + assert not p2.is_running() + assert not psutil.tests._pids_started + assert not psutil.tests._subprocesses_started + + @unittest.skipIf(not POSIX, "POSIX only") + def test_create_zombie_proc(self): + zpid = create_zombie_proc() + self.addCleanup(reap_children, recursive=True) + p = psutil.Process(zpid) + self.assertEqual(p.status(), psutil.STATUS_ZOMBIE) + + +class TestNetUtils(unittest.TestCase): + + def bind_socket(self): + port = get_free_port() + with contextlib.closing(bind_socket(addr=('', port))) as s: + self.assertEqual(s.getsockname()[1], port) + + @unittest.skipIf(not POSIX, "POSIX only") + def test_bind_unix_socket(self): + with unix_socket_path() as name: + sock = bind_unix_socket(name) + with contextlib.closing(sock): + self.assertEqual(sock.family, socket.AF_UNIX) + self.assertEqual(sock.type, socket.SOCK_STREAM) + self.assertEqual(sock.getsockname(), name) + assert os.path.exists(name) + assert stat.S_ISSOCK(os.stat(name).st_mode) + # UDP + with unix_socket_path() as name: + sock = bind_unix_socket(name, type=socket.SOCK_DGRAM) + with contextlib.closing(sock): + self.assertEqual(sock.type, socket.SOCK_DGRAM) + + def tcp_tcp_socketpair(self): + addr = ("127.0.0.1", get_free_port()) + server, client = tcp_socketpair(socket.AF_INET, addr=addr) + with contextlib.closing(server): + with contextlib.closing(client): + # Ensure they are connected and the positions are + # correct. + self.assertEqual(server.getsockname(), addr) + self.assertEqual(client.getpeername(), addr) + self.assertNotEqual(client.getsockname(), addr) + + @unittest.skipIf(not POSIX, "POSIX only") + def test_unix_socketpair(self): + p = psutil.Process() + num_fds = p.num_fds() + assert not p.connections(kind='unix') + with unix_socket_path() as name: + server, client = unix_socketpair(name) + try: + assert os.path.exists(name) + assert stat.S_ISSOCK(os.stat(name).st_mode) + self.assertEqual(p.num_fds() - num_fds, 2) + self.assertEqual(len(p.connections(kind='unix')), 2) + self.assertEqual(server.getsockname(), name) + self.assertEqual(client.getpeername(), name) + finally: + client.close() + server.close() + + def test_create_sockets(self): + with create_sockets() as socks: + fams = collections.defaultdict(int) + types = collections.defaultdict(int) + for s in socks: + fams[s.family] += 1 + # work around http://bugs.python.org/issue30204 + types[s.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE)] += 1 + self.assertGreaterEqual(fams[socket.AF_INET], 2) + if supports_ipv6(): + self.assertGreaterEqual(fams[socket.AF_INET6], 2) + if POSIX and HAS_CONNECTIONS_UNIX: + self.assertGreaterEqual(fams[socket.AF_UNIX], 2) + self.assertGreaterEqual(types[socket.SOCK_STREAM], 2) + self.assertGreaterEqual(types[socket.SOCK_DGRAM], 2) + + +class TestOtherUtils(unittest.TestCase): + + def test_is_namedtuple(self): + assert is_namedtuple(collections.namedtuple('foo', 'a b c')(1, 2, 3)) + assert not is_namedtuple(tuple()) if __name__ == '__main__': diff --git a/psutil/tests/test_osx.py b/psutil/tests/test_osx.py index 7b61bc74a..bcb2ba4e1 100755 --- a/psutil/tests/test_osx.py +++ b/psutil/tests/test_osx.py @@ -8,14 +8,13 @@ import os import re -import subprocess -import sys import time import psutil from psutil import OSX -from psutil._compat import PY3 +from psutil.tests import create_zombie_proc from psutil.tests import get_test_subprocess +from psutil.tests import HAS_BATTERY from psutil.tests import MEMORY_TOLERANCE from psutil.tests import reap_children from psutil.tests import retry_before_failing @@ -31,10 +30,8 @@ def sysctl(cmdline): """Expects a sysctl command with an argument and parse the result returning only the value of interest. """ - p = subprocess.Popen(cmdline, shell=1, stdout=subprocess.PIPE) - result = p.communicate()[0].strip().split()[1] - if PY3: - result = str(result, sys.stdout.encoding) + out = sh(cmdline) + result = out.split()[1] try: return int(result) except ValueError: @@ -49,7 +46,7 @@ def vm_stat(field): break else: raise ValueError("line not found") - return int(re.search('\d+', line).group(0)) * PAGESIZE + return int(re.search(r'\d+', line).group(0)) * PAGESIZE # http://code.activestate.com/recipes/578019/ @@ -79,7 +76,7 @@ def human2bytes(s): return int(num * prefix[letter]) -@unittest.skipUnless(OSX, "OSX only") +@unittest.skipIf(not OSX, "OSX only") class TestProcess(unittest.TestCase): @classmethod @@ -91,11 +88,7 @@ def tearDownClass(cls): reap_children() def test_process_create_time(self): - cmdline = "ps -o lstart -p %s" % self.pid - p = subprocess.Popen(cmdline, shell=1, stdout=subprocess.PIPE) - output = p.communicate()[0] - if PY3: - output = str(output, sys.stdout.encoding) + output = sh("ps -o lstart -p %s" % self.pid) start_ps = output.replace('STARTED', '').strip() hhmmss = start_ps.split(' ')[-2] year = start_ps.split(' ')[-1] @@ -108,9 +101,72 @@ def test_process_create_time(self): time.strftime("%Y", time.localtime(start_psutil))) -@unittest.skipUnless(OSX, "OSX only") +@unittest.skipIf(not OSX, "OSX only") +class TestZombieProcessAPIs(unittest.TestCase): + + @classmethod + def setUpClass(cls): + zpid = create_zombie_proc() + cls.p = psutil.Process(zpid) + + @classmethod + def tearDownClass(cls): + reap_children(recursive=True) + + def test_pidtask_info(self): + self.assertEqual(self.p.status(), psutil.STATUS_ZOMBIE) + self.p.ppid() + self.p.uids() + self.p.gids() + self.p.terminal() + self.p.create_time() + + def test_exe(self): + self.assertRaises(psutil.ZombieProcess, self.p.exe) + + def test_cmdline(self): + self.assertRaises(psutil.ZombieProcess, self.p.cmdline) + + def test_environ(self): + self.assertRaises(psutil.ZombieProcess, self.p.environ) + + def test_cwd(self): + self.assertRaises(psutil.ZombieProcess, self.p.cwd) + + def test_memory_full_info(self): + self.assertRaises(psutil.ZombieProcess, self.p.memory_full_info) + + def test_cpu_times(self): + self.assertRaises(psutil.ZombieProcess, self.p.cpu_times) + + def test_num_ctx_switches(self): + self.assertRaises(psutil.ZombieProcess, self.p.num_ctx_switches) + + def test_num_threads(self): + self.assertRaises(psutil.ZombieProcess, self.p.num_threads) + + def test_open_files(self): + self.assertRaises(psutil.ZombieProcess, self.p.open_files) + + def test_connections(self): + self.assertRaises(psutil.ZombieProcess, self.p.connections) + + def test_num_fds(self): + self.assertRaises(psutil.ZombieProcess, self.p.num_fds) + + def test_threads(self): + self.assertRaises((psutil.ZombieProcess, psutil.AccessDenied), + self.p.threads) + + def test_memory_maps(self): + self.assertRaises(psutil.ZombieProcess, self.p.memory_maps) + + +@unittest.skipIf(not OSX, "OSX only") class TestSystemAPIs(unittest.TestCase): + # --- disk + def test_disks(self): # test psutil.disk_usage() and psutil.disk_partitions() # against "df -a" @@ -138,6 +194,8 @@ def df(path): if abs(usage.used - used) > 10 * 1024 * 1024: self.fail("psutil=%s, df=%s" % usage.used, used) + # --- cpu + def test_cpu_count_logical(self): num = sysctl("sysctl hw.logicalcpu") self.assertEqual(num, psutil.cpu_count(logical=True)) @@ -146,6 +204,15 @@ def test_cpu_count_physical(self): num = sysctl("sysctl hw.physicalcpu") self.assertEqual(num, psutil.cpu_count(logical=False)) + def test_cpu_freq(self): + freq = psutil.cpu_freq() + self.assertEqual( + freq.current * 1000 * 1000, sysctl("sysctl hw.cpufrequency")) + self.assertEqual( + freq.min * 1000 * 1000, sysctl("sysctl hw.cpufrequency_min")) + self.assertEqual( + freq.max * 1000 * 1000, sysctl("sysctl hw.cpufrequency_max")) + # --- virtual mem def test_vmem_total(self): @@ -206,6 +273,8 @@ def test_swapmem_sout(self): # self.assertEqual(psutil_smem.used, human2bytes(used)) # self.assertEqual(psutil_smem.free, human2bytes(free)) + # --- network + def test_net_if_stats(self): for name, stats in psutil.net_if_stats().items(): try: @@ -215,7 +284,19 @@ def test_net_if_stats(self): else: self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) self.assertEqual(stats.mtu, - int(re.findall('mtu (\d+)', out)[0])) + int(re.findall(r'mtu (\d+)', out)[0])) + + # --- sensors_battery + + @unittest.skipIf(not HAS_BATTERY, "no battery") + def test_sensors_battery(self): + out = sh("pmset -g batt") + percent = re.search("(\d+)%", out).group(1) + drawing_from = re.search("Now drawing from '([^']+)'", out).group(1) + power_plugged = drawing_from == "AC Power" + psutil_result = psutil.sensors_battery() + self.assertEqual(psutil_result.power_plugged, power_plugged) + self.assertEqual(psutil_result.percent, int(percent)) if __name__ == '__main__': diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py index 16d1eb7e6..c59f9a1c7 100755 --- a/psutil/tests/test_posix.py +++ b/psutil/tests/test_posix.py @@ -10,13 +10,16 @@ import datetime import errno import os +import re import subprocess import sys import time import psutil +from psutil import AIX from psutil import BSD from psutil import LINUX +from psutil import OPENBSD from psutil import OSX from psutil import POSIX from psutil import SUNOS @@ -26,7 +29,7 @@ from psutil.tests import get_kernel_version from psutil.tests import get_test_subprocess from psutil.tests import mock -from psutil.tests import PYTHON +from psutil.tests import PYTHON_EXE from psutil.tests import reap_children from psutil.tests import retry_before_failing from psutil.tests import run_test_module_by_name @@ -35,6 +38,7 @@ from psutil.tests import TRAVIS from psutil.tests import unittest from psutil.tests import wait_for_pid +from psutil.tests import which def ps(cmd): @@ -44,12 +48,10 @@ def ps(cmd): if not LINUX: cmd = cmd.replace(" --no-headers ", " ") if SUNOS: - cmd = cmd.replace("-o command", "-o comm") cmd = cmd.replace("-o start", "-o stime") - p = subprocess.Popen(cmd, shell=1, stdout=subprocess.PIPE) - output = p.communicate()[0].strip() - if PY3: - output = str(output, sys.stdout.encoding) + if AIX: + cmd = cmd.replace("-o rss", "-o rssize") + output = sh(cmd) if not LINUX: output = output.split('\n')[1].strip() try: @@ -57,14 +59,39 @@ def ps(cmd): except ValueError: return output +# ps "-o" field names differ wildly between platforms. +# "comm" means "only executable name" but is not available on BSD platforms. +# "args" means "command with all its arguments", and is also not available +# on BSD platforms. +# "command" is like "args" on most platforms, but like "comm" on AIX, +# and not available on SUNOS. +# so for the executable name we can use "comm" on Solaris and split "command" +# on other platforms. +# to get the cmdline (with args) we have to use "args" on AIX and +# Solaris, and can use "command" on all others. -@unittest.skipUnless(POSIX, "POSIX only") + +def ps_name(pid): + field = "command" + if SUNOS: + field = "comm" + return ps("ps --no-headers -o %s -p %s" % (field, pid)).split(' ')[0] + + +def ps_args(pid): + field = "command" + if AIX or SUNOS: + field = "args" + return ps("ps --no-headers -o %s -p %s" % (field, pid)) + + +@unittest.skipIf(not POSIX, "POSIX only") class TestProcess(unittest.TestCase): """Compare psutil results against 'ps' command line utility (mainly).""" @classmethod def setUpClass(cls): - cls.pid = get_test_subprocess([PYTHON, "-E", "-O"], + cls.pid = get_test_subprocess([PYTHON_EXE, "-E", "-O"], stdin=subprocess.PIPE).pid wait_for_pid(cls.pid) @@ -72,8 +99,6 @@ def setUpClass(cls): def tearDownClass(cls): reap_children() - # for ps -o arguments see: http://unixhelp.ed.ac.uk/CGI/man-cgi?ps - def test_ppid(self): ppid_ps = ps("ps --no-headers -o ppid -p %s" % self.pid) ppid_psutil = psutil.Process(self.pid).ppid() @@ -94,6 +119,15 @@ def test_username(self): username_psutil = psutil.Process(self.pid).username() self.assertEqual(username_ps, username_psutil) + def test_username_no_resolution(self): + # Emulate a case where the system can't resolve the uid to + # a username in which case psutil is supposed to return + # the stringified uid. + p = psutil.Process() + with mock.patch("psutil.pwd.getpwuid", side_effect=KeyError) as fun: + self.assertEqual(p.username(), str(p.uids().real)) + assert fun.called + @skip_on_access_denied() @retry_before_failing() def test_rss_memory(self): @@ -115,14 +149,52 @@ def test_vsz_memory(self): self.assertEqual(vsz_ps, vsz_psutil) def test_name(self): - # use command + arg since "comm" keyword not supported on all platforms - name_ps = ps("ps --no-headers -o command -p %s" % ( - self.pid)).split(' ')[0] + name_ps = ps_name(self.pid) # remove path if there is any, from the command name_ps = os.path.basename(name_ps).lower() name_psutil = psutil.Process(self.pid).name().lower() + # ...because of how we calculate PYTHON_EXE; on OSX this may + # be "pythonX.Y". + name_ps = re.sub(r"\d.\d", "", name_ps) + name_psutil = re.sub(r"\d.\d", "", name_psutil) self.assertEqual(name_ps, name_psutil) + def test_name_long(self): + # On UNIX the kernel truncates the name to the first 15 + # characters. In such a case psutil tries to determine the + # full name from the cmdline. + name = "long-program-name" + cmdline = ["long-program-name-extended", "foo", "bar"] + with mock.patch("psutil._psplatform.Process.name", + return_value=name): + with mock.patch("psutil._psplatform.Process.cmdline", + return_value=cmdline): + p = psutil.Process() + self.assertEqual(p.name(), "long-program-name-extended") + + def test_name_long_cmdline_ad_exc(self): + # Same as above but emulates a case where cmdline() raises + # AccessDenied in which case psutil is supposed to return + # the truncated name instead of crashing. + name = "long-program-name" + with mock.patch("psutil._psplatform.Process.name", + return_value=name): + with mock.patch("psutil._psplatform.Process.cmdline", + side_effect=psutil.AccessDenied(0, "")): + p = psutil.Process() + self.assertEqual(p.name(), "long-program-name") + + def test_name_long_cmdline_nsp_exc(self): + # Same as above but emulates a case where cmdline() raises NSP + # which is supposed to propagate. + name = "long-program-name" + with mock.patch("psutil._psplatform.Process.name", + return_value=name): + with mock.patch("psutil._psplatform.Process.cmdline", + side_effect=psutil.NoSuchProcess(0, "")): + p = psutil.Process() + self.assertRaises(psutil.NoSuchProcess, p.name) + @unittest.skipIf(OSX or BSD, 'ps -o start not available') def test_create_time(self): time_ps = ps("ps --no-headers -o start -p %s" % self.pid).split(' ')[0] @@ -137,8 +209,7 @@ def test_create_time(self): self.assertIn(time_ps, [time_psutil_tstamp, round_time_psutil_tstamp]) def test_exe(self): - ps_pathname = ps("ps --no-headers -o command -p %s" % - self.pid).split(' ')[0] + ps_pathname = ps_name(self.pid) psutil_pathname = psutil.Process(self.pid).exe() try: self.assertEqual(ps_pathname, psutil_pathname) @@ -153,13 +224,17 @@ def test_exe(self): self.assertEqual(ps_pathname, adjusted_ps_pathname) def test_cmdline(self): - ps_cmdline = ps("ps --no-headers -o command -p %s" % self.pid) + ps_cmdline = ps_args(self.pid) psutil_cmdline = " ".join(psutil.Process(self.pid).cmdline()) - if SUNOS: - # ps on Solaris only shows the first part of the cmdline - psutil_cmdline = psutil_cmdline.split(" ")[0] self.assertEqual(ps_cmdline, psutil_cmdline) + # On SUNOS "ps" reads niceness /proc/pid/psinfo which returns an + # incorrect value (20); the real deal is getpriority(2) which + # returns 0; psutil relies on it, see: + # https://github.com/giampaolo/psutil/issues/1082 + # AIX has the same issue + @unittest.skipIf(SUNOS, "not reliable on SUNOS") + @unittest.skipIf(AIX, "not reliable on AIX") def test_nice(self): ps_nice = ps("ps --no-headers -o nice -p %s" % self.pid) psutil_nice = psutil.Process().nice() @@ -181,7 +256,8 @@ def call(p, attr): p = psutil.Process(os.getpid()) failures = [] ignored_names = ['terminate', 'kill', 'suspend', 'resume', 'nice', - 'send_signal', 'wait', 'children', 'as_dict'] + 'send_signal', 'wait', 'children', 'as_dict', + 'memory_info_ex'] if LINUX and get_kernel_version() < (2, 6, 36): ignored_names.append('rlimit') if LINUX and get_kernel_version() < (2, 6, 23): @@ -205,14 +281,8 @@ def call(p, attr): if failures: self.fail('\n' + '\n'.join(failures)) - @unittest.skipUnless(os.path.islink("/proc/%s/cwd" % os.getpid()), - "/proc fs not available") - def test_cwd(self): - self.assertEqual(os.readlink("/proc/%s/cwd" % os.getpid()), - psutil.Process().cwd()) - -@unittest.skipUnless(POSIX, "POSIX only") +@unittest.skipIf(not POSIX, "POSIX only") class TestSystemAPIs(unittest.TestCase): """Test some system APIs.""" @@ -220,7 +290,7 @@ class TestSystemAPIs(unittest.TestCase): def test_pids(self): # Note: this test might fail if the OS is starting/killing # other processes in the meantime - if SUNOS: + if SUNOS or AIX: cmd = ["ps", "-A", "-o", "pid"] else: cmd = ["ps", "ax", "-o", "pid"] @@ -240,26 +310,18 @@ def test_pids(self): pids_ps.sort() pids_psutil.sort() - # on OSX ps doesn't show pid 0 - if OSX and 0 not in pids_ps: + # on OSX and OPENBSD ps doesn't show pid 0 + if OSX or OPENBSD and 0 not in pids_ps: pids_ps.insert(0, 0) - - if pids_ps != pids_psutil: - difference = [x for x in pids_psutil if x not in pids_ps] + \ - [x for x in pids_ps if x not in pids_psutil] - self.fail("difference: " + str(difference)) + self.assertEqual(pids_ps, pids_psutil) # for some reason ifconfig -a does not report all interfaces # returned by psutil @unittest.skipIf(SUNOS, "unreliable on SUNOS") @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") + @unittest.skipIf(not which('ifconfig'), "no ifconfig cmd") def test_nic_names(self): - p = subprocess.Popen("ifconfig -a", shell=1, stdout=subprocess.PIPE) - output = p.communicate()[0].strip() - if p.returncode != 0: - raise unittest.SkipTest('ifconfig returned no output') - if PY3: - output = str(output, sys.stdout.encoding) + output = sh("ifconfig -a") for nic in psutil.net_io_counters(pernic=True).keys(): for line in output.split(): if line.startswith(nic): @@ -277,11 +339,11 @@ def test_users(self): out = sh("who") lines = out.split('\n') users = [x.split()[0] for x in lines] - self.assertEqual(len(users), len(psutil.users())) terminals = [x.split()[1] for x in lines] + self.assertEqual(len(users), len(psutil.users())) for u in psutil.users(): - self.assertTrue(u.name in users, u.name) - self.assertTrue(u.terminal in terminals, u.terminal) + self.assertIn(u.name, users) + self.assertIn(u.terminal, terminals) def test_pid_exists_let_raise(self): # According to "man 2 kill" possible error values for kill @@ -317,6 +379,8 @@ def test_os_waitpid_bad_ret_status(self): psutil._psposix.wait_pid, os.getpid()) assert m.called + # AIX can return '-' in df output instead of numbers, e.g. for /proc + @unittest.skipIf(AIX, "unreliable on AIX") def test_disk_usage(self): def df(device): out = sh("df -k %s" % device).strip() @@ -337,8 +401,10 @@ def df(device): # see: # https://travis-ci.org/giampaolo/psutil/jobs/138338464 # https://travis-ci.org/giampaolo/psutil/jobs/138343361 - if "no such file or directory" in str(err).lower() or \ - "raw devices not supported" in str(err).lower(): + err = str(err).lower() + if "no such file or directory" in err or \ + "raw devices not supported" in err or \ + "permission denied" in err: continue else: raise diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index b01760839..a629cae52 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. # Use of this source code is governed by a BSD-style license that can be @@ -8,28 +7,23 @@ """Tests for psutil.Process class.""" import collections -import contextlib import errno +import getpass +import itertools import os -import select import signal import socket -import stat import subprocess import sys import tempfile import textwrap import time -import traceback import types -from socket import AF_INET -from socket import SOCK_DGRAM -from socket import SOCK_STREAM import psutil +from psutil import AIX from psutil import BSD -from psutil import FREEBSD from psutil import LINUX from psutil import NETBSD from psutil import OPENBSD @@ -37,29 +31,30 @@ from psutil import POSIX from psutil import SUNOS from psutil import WINDOWS -from psutil._common import supports_ipv6 -from psutil._compat import callable from psutil._compat import long from psutil._compat import PY3 -from psutil._compat import unicode -from psutil.tests import AF_INET6 -from psutil.tests import AF_UNIX from psutil.tests import APPVEYOR from psutil.tests import call_until -from psutil.tests import chdir -from psutil.tests import check_connection_ntuple +from psutil.tests import copyload_shared_lib from psutil.tests import create_exe +from psutil.tests import create_proc_children_pair +from psutil.tests import create_zombie_proc from psutil.tests import enum from psutil.tests import get_test_subprocess from psutil.tests import get_winver -from psutil.tests import GLOBAL_TIMEOUT +from psutil.tests import HAS_CPU_AFFINITY +from psutil.tests import HAS_ENVIRON +from psutil.tests import HAS_IONICE +from psutil.tests import HAS_MEMORY_MAPS +from psutil.tests import HAS_PROC_CPU_NUM +from psutil.tests import HAS_PROC_IO_COUNTERS +from psutil.tests import HAS_RLIMIT +from psutil.tests import HAS_THREADS from psutil.tests import mock from psutil.tests import PYPY -from psutil.tests import pyrun -from psutil.tests import PYTHON +from psutil.tests import PYTHON_EXE from psutil.tests import reap_children from psutil.tests import retry_before_failing -from psutil.tests import RLIMIT_SUPPORT from psutil.tests import run_test_module_by_name from psutil.tests import safe_rmpath from psutil.tests import sh @@ -68,13 +63,9 @@ from psutil.tests import TESTFILE_PREFIX from psutil.tests import TESTFN from psutil.tests import ThreadTask -from psutil.tests import TOX from psutil.tests import TRAVIS from psutil.tests import unittest -from psutil.tests import VALID_PROC_STATUSES -from psutil.tests import wait_for_file from psutil.tests import wait_for_pid -from psutil.tests import warn from psutil.tests import WIN_VISTA @@ -92,9 +83,12 @@ def tearDown(self): reap_children() def test_pid(self): - self.assertEqual(psutil.Process().pid, os.getpid()) + p = psutil.Process() + self.assertEqual(p.pid, os.getpid()) sproc = get_test_subprocess() self.assertEqual(psutil.Process(sproc.pid).pid, sproc.pid) + with self.assertRaises(AttributeError): + p.pid = 33 def test_kill(self): sproc = get_test_subprocess() @@ -104,7 +98,7 @@ def test_kill(self): sig = p.wait() self.assertFalse(psutil.pid_exists(test_pid)) if POSIX: - self.assertEqual(sig, signal.SIGKILL) + self.assertEqual(sig, -signal.SIGKILL) def test_terminate(self): sproc = get_test_subprocess() @@ -114,7 +108,7 @@ def test_terminate(self): sig = p.wait() self.assertFalse(psutil.pid_exists(test_pid)) if POSIX: - self.assertEqual(sig, signal.SIGTERM) + self.assertEqual(sig, -signal.SIGTERM) def test_send_signal(self): sig = signal.SIGKILL if POSIX else signal.SIGTERM @@ -124,7 +118,7 @@ def test_send_signal(self): exit_sig = p.wait() self.assertFalse(psutil.pid_exists(p.pid)) if POSIX: - self.assertEqual(exit_sig, sig) + self.assertEqual(exit_sig, -sig) # sproc = get_test_subprocess() p = psutil.Process(sproc.pid) @@ -155,9 +149,9 @@ def test_wait(self): p.kill() code = p.wait() if POSIX: - self.assertEqual(code, signal.SIGKILL) + self.assertEqual(code, -signal.SIGKILL) else: - self.assertEqual(code, 0) + self.assertEqual(code, signal.SIGTERM) self.assertFalse(p.is_running()) sproc = get_test_subprocess() @@ -165,14 +159,14 @@ def test_wait(self): p.terminate() code = p.wait() if POSIX: - self.assertEqual(code, signal.SIGTERM) + self.assertEqual(code, -signal.SIGTERM) else: - self.assertEqual(code, 0) + self.assertEqual(code, signal.SIGTERM) self.assertFalse(p.is_running()) # check sys.exit() code code = "import time, sys; time.sleep(0.01); sys.exit(5);" - sproc = get_test_subprocess([PYTHON, "-c", code]) + sproc = get_test_subprocess([PYTHON_EXE, "-c", code]) p = psutil.Process(sproc.pid) self.assertEqual(p.wait(), 5) self.assertFalse(p.is_running()) @@ -181,7 +175,7 @@ def test_wait(self): # It is not supposed to raise NSP when the process is gone. # On UNIX this should return None, on Windows it should keep # returning the exit code. - sproc = get_test_subprocess([PYTHON, "-c", code]) + sproc = get_test_subprocess([PYTHON_EXE, "-c", code]) p = psutil.Process(sproc.pid) self.assertEqual(p.wait(), 5) self.assertIn(p.wait(), (5, None)) @@ -195,26 +189,26 @@ def test_wait(self): # timeout < 0 not allowed self.assertRaises(ValueError, p.wait, -1) - # XXX why is this skipped on Windows? - @unittest.skipUnless(POSIX, 'skipped on Windows') def test_wait_non_children(self): - # test wait() against processes which are not our children - code = "import sys;" - code += "from subprocess import Popen, PIPE;" - code += "cmd = ['%s', '-c', 'import time; time.sleep(60)'];" % PYTHON - code += "sp = Popen(cmd, stdout=PIPE);" - code += "sys.stdout.write(str(sp.pid));" - sproc = get_test_subprocess([PYTHON, "-c", code], - stdout=subprocess.PIPE) - grandson_pid = int(sproc.stdout.read()) - grandson_proc = psutil.Process(grandson_pid) - try: - self.assertRaises(psutil.TimeoutExpired, grandson_proc.wait, 0.01) - grandson_proc.kill() - ret = grandson_proc.wait() - self.assertEqual(ret, None) - finally: - reap_children(recursive=True) + # Test wait() against a process which is not our direct + # child. + p1, p2 = create_proc_children_pair() + self.assertRaises(psutil.TimeoutExpired, p1.wait, 0.01) + self.assertRaises(psutil.TimeoutExpired, p2.wait, 0.01) + # We also terminate the direct child otherwise the + # grandchild will hang until the parent is gone. + p1.terminate() + p2.terminate() + ret1 = p1.wait() + ret2 = p2.wait() + if POSIX: + self.assertEqual(ret1, -signal.SIGTERM) + # For processes which are not our children we're supposed + # to get None. + self.assertEqual(ret2, None) + else: + self.assertEqual(ret1, signal.SIGTERM) + self.assertEqual(ret1, signal.SIGTERM) def test_wait_timeout_0(self): sproc = get_test_subprocess() @@ -231,9 +225,9 @@ def test_wait_timeout_0(self): else: break if POSIX: - self.assertEqual(code, signal.SIGKILL) + self.assertEqual(code, -signal.SIGKILL) else: - self.assertEqual(code, 0) + self.assertEqual(code, signal.SIGTERM) self.assertFalse(p.is_running()) def test_cpu_percent(self): @@ -251,6 +245,12 @@ def test_cpu_percent(self): with self.assertRaises(ValueError): p.cpu_percent(interval=-1) + def test_cpu_percent_numcpus_none(self): + # See: https://github.com/giampaolo/psutil/issues/1087 + with mock.patch('psutil.cpu_count', return_value=None) as m: + psutil.Process().cpu_percent() + assert m.called + def test_cpu_times(self): times = psutil.Process().cpu_times() assert (times.user > 0.0) or (times.system > 0.0), times @@ -260,14 +260,6 @@ def test_cpu_times(self): for name in times._fields: time.strftime("%H:%M:%S", time.localtime(getattr(times, name))) - # Test Process.cpu_times() against os.times() - # os.times() is broken on Python 2.6 - # http://bugs.python.org/issue1040026 - # XXX fails on OSX: not sure if it's for os.times(). We should - # try this with Python 2.7 and re-enable the test. - - @unittest.skipUnless(sys.version_info > (2, 6, 1) and not OSX, - 'os.times() broken on OSX + PY2.6.1') def test_cpu_times_2(self): user_time, kernel_time = psutil.Process().cpu_times()[:2] utime, ktime = os.times()[:2] @@ -281,6 +273,15 @@ def test_cpu_times_2(self): if (max([kernel_time, ktime]) - min([kernel_time, ktime])) > 0.1: self.fail("expected: %s, found: %s" % (ktime, kernel_time)) + @unittest.skipIf(not HAS_PROC_CPU_NUM, "not supported") + def test_cpu_num(self): + p = psutil.Process() + num = p.cpu_num() + self.assertGreaterEqual(num, 0) + if psutil.cpu_count() == 1: + self.assertEqual(num, 0) + self.assertIn(p.cpu_num(), range(psutil.cpu_count())) + def test_create_time(self): sproc = get_test_subprocess() now = time.time() @@ -298,31 +299,36 @@ def test_create_time(self): # make sure returned value can be pretty printed with strftime time.strftime("%Y %m %d %H:%M:%S", time.localtime(p.create_time())) - @unittest.skipUnless(POSIX, 'POSIX only') + @unittest.skipIf(not POSIX, 'POSIX only') @unittest.skipIf(TRAVIS, 'not reliable on TRAVIS') def test_terminal(self): terminal = psutil.Process().terminal() - if sys.stdin.isatty(): + if sys.stdin.isatty() or sys.stdout.isatty(): tty = os.path.realpath(sh('tty')) self.assertEqual(terminal, tty) else: self.assertIsNone(terminal) - @unittest.skipUnless(LINUX or BSD or WINDOWS, - 'platform not supported') + @unittest.skipIf(not HAS_PROC_IO_COUNTERS, 'not supported') @skip_on_not_implemented(only_if=LINUX) def test_io_counters(self): p = psutil.Process() + # test reads io1 = p.io_counters() - with open(PYTHON, 'rb') as f: + with open(PYTHON_EXE, 'rb') as f: f.read() io2 = p.io_counters() - if not BSD: - assert io2.read_count > io1.read_count, (io1, io2) + if not BSD and not AIX: + self.assertGreater(io2.read_count, io1.read_count) self.assertEqual(io2.write_count, io1.write_count) - assert io2.read_bytes >= io1.read_bytes, (io1, io2) - assert io2.write_bytes >= io1.write_bytes, (io1, io2) + if LINUX: + self.assertGreater(io2.read_chars, io1.read_chars) + self.assertEqual(io2.write_chars, io1.write_chars) + else: + self.assertGreaterEqual(io2.read_bytes, io1.read_bytes) + self.assertGreaterEqual(io2.write_bytes, io1.write_bytes) + # test writes io1 = p.io_counters() with tempfile.TemporaryFile(prefix=TESTFILE_PREFIX) as f: @@ -331,14 +337,24 @@ def test_io_counters(self): else: f.write("x" * 1000000) io2 = p.io_counters() - assert io2.write_count >= io1.write_count, (io1, io2) - assert io2.write_bytes >= io1.write_bytes, (io1, io2) - assert io2.read_count >= io1.read_count, (io1, io2) - assert io2.read_bytes >= io1.read_bytes, (io1, io2) - - @unittest.skipUnless(LINUX or (WINDOWS and get_winver() >= WIN_VISTA), - 'platform not supported') - @unittest.skipIf(LINUX and TRAVIS, "unknown failure on travis") + self.assertGreaterEqual(io2.write_count, io1.write_count) + self.assertGreaterEqual(io2.write_bytes, io1.write_bytes) + self.assertGreaterEqual(io2.read_count, io1.read_count) + self.assertGreaterEqual(io2.read_bytes, io1.read_bytes) + if LINUX: + self.assertGreater(io2.write_chars, io1.write_chars) + self.assertGreaterEqual(io2.read_chars, io1.read_chars) + + # sanity check + for i in range(len(io2)): + if BSD and i >= 2: + # On BSD read_bytes and write_bytes are always set to -1. + continue + self.assertGreaterEqual(io2[i], 0) + self.assertGreaterEqual(io2[i], 0) + + @unittest.skipIf(not HAS_IONICE, "not supported") + @unittest.skipIf(WINDOWS and get_winver() < WIN_VISTA, 'not supported') def test_ionice(self): if LINUX: from psutil import (IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, @@ -369,20 +385,6 @@ def test_ionice(self): ioclass, value = p.ionice() self.assertEqual(ioclass, 2) self.assertEqual(value, 7) - # - self.assertRaises(ValueError, p.ionice, 2, 10) - self.assertRaises(ValueError, p.ionice, 2, -1) - self.assertRaises(ValueError, p.ionice, 4) - self.assertRaises(TypeError, p.ionice, 2, "foo") - self.assertRaisesRegex( - ValueError, "can't specify value with IOPRIO_CLASS_NONE", - p.ionice, psutil.IOPRIO_CLASS_NONE, 1) - self.assertRaisesRegex( - ValueError, "can't specify value with IOPRIO_CLASS_IDLE", - p.ionice, psutil.IOPRIO_CLASS_IDLE, 1) - self.assertRaisesRegex( - ValueError, "'ioclass' argument must be specified", - p.ionice, value=1) finally: p.ionice(IOPRIO_CLASS_NONE) else: @@ -397,11 +399,31 @@ def test_ionice(self): self.assertEqual(p.ionice(), value) finally: p.ionice(original) - # + + @unittest.skipIf(not HAS_IONICE, "not supported") + @unittest.skipIf(WINDOWS and get_winver() < WIN_VISTA, 'not supported') + def test_ionice_errs(self): + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + if LINUX: + self.assertRaises(ValueError, p.ionice, 2, 10) + self.assertRaises(ValueError, p.ionice, 2, -1) + self.assertRaises(ValueError, p.ionice, 4) + self.assertRaises(TypeError, p.ionice, 2, "foo") + self.assertRaisesRegex( + ValueError, "can't specify value with IOPRIO_CLASS_NONE", + p.ionice, psutil.IOPRIO_CLASS_NONE, 1) + self.assertRaisesRegex( + ValueError, "can't specify value with IOPRIO_CLASS_IDLE", + p.ionice, psutil.IOPRIO_CLASS_IDLE, 1) + self.assertRaisesRegex( + ValueError, "'ioclass' argument must be specified", + p.ionice, value=1) + else: self.assertRaises(ValueError, p.ionice, 3) self.assertRaises(TypeError, p.ionice, 2, 1) - @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, "LINUX >= 2.6.36 only") + @unittest.skipIf(not HAS_RLIMIT, "not supported") def test_rlimit_get(self): import resource p = psutil.Process(os.getpid()) @@ -424,7 +446,7 @@ def test_rlimit_get(self): self.assertGreaterEqual(ret[0], -1) self.assertGreaterEqual(ret[1], -1) - @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, "LINUX >= 2.6.36 only") + @unittest.skipIf(not HAS_RLIMIT, "not supported") def test_rlimit_set(self): sproc = get_test_subprocess() p = psutil.Process(sproc.pid) @@ -437,7 +459,7 @@ def test_rlimit_set(self): with self.assertRaises(ValueError): p.rlimit(psutil.RLIMIT_NOFILE, (5, 5, 5)) - @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, "LINUX >= 2.6.36 only") + @unittest.skipIf(not HAS_RLIMIT, "not supported") def test_rlimit(self): p = psutil.Process() soft, hard = p.rlimit(psutil.RLIMIT_FSIZE) @@ -456,7 +478,7 @@ def test_rlimit(self): p.rlimit(psutil.RLIMIT_FSIZE, (soft, hard)) self.assertEqual(p.rlimit(psutil.RLIMIT_FSIZE), (soft, hard)) - @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, "LINUX >= 2.6.36 only") + @unittest.skipIf(not HAS_RLIMIT, "not supported") def test_rlimit_infinity(self): # First set a limit, then re-set it by specifying INFINITY # and assume we overridden the previous limit. @@ -471,7 +493,7 @@ def test_rlimit_infinity(self): p.rlimit(psutil.RLIMIT_FSIZE, (soft, hard)) self.assertEqual(p.rlimit(psutil.RLIMIT_FSIZE), (soft, hard)) - @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, "LINUX >= 2.6.36 only") + @unittest.skipIf(not HAS_RLIMIT, "not supported") def test_rlimit_infinity_value(self): # RLIMIT_FSIZE should be RLIM_INFINITY, which will be a really # big number on a platform with large file support. On these @@ -496,22 +518,17 @@ def test_num_threads(self): else: step1 = p.num_threads() - thread = ThreadTask() - thread.start() - try: + with ThreadTask(): step2 = p.num_threads() self.assertEqual(step2, step1 + 1) - thread.stop() - finally: - if thread._running: - thread.stop() - @unittest.skipUnless(WINDOWS, 'WINDOWS only') + @unittest.skipIf(not WINDOWS, 'WINDOWS only') def test_num_handles(self): # a better test is done later into test/_windows.py p = psutil.Process() self.assertGreater(p.num_handles(), 0) + @unittest.skipIf(not HAS_THREADS, 'not supported') def test_threads(self): p = psutil.Process() if OPENBSD: @@ -522,10 +539,7 @@ def test_threads(self): else: step1 = p.threads() - thread = ThreadTask() - thread.start() - - try: + with ThreadTask(): step2 = p.threads() self.assertEqual(len(step2), len(step1) + 1) # on Linux, first thread id is supposed to be this process @@ -536,16 +550,10 @@ def test_threads(self): self.assertEqual(athread.id, athread[0]) self.assertEqual(athread.user_time, athread[1]) self.assertEqual(athread.system_time, athread[2]) - # test num threads - thread.stop() - finally: - if thread._running: - thread.stop() @retry_before_failing() - # see: https://travis-ci.org/giampaolo/psutil/jobs/111842553 - @unittest.skipIf(OSX and TRAVIS, "fails on TRAVIS + OSX") @skip_on_access_denied(only_if=OSX) + @unittest.skipIf(not HAS_THREADS, 'not supported') def test_threads_2(self): sproc = get_test_subprocess() p = psutil.Process(sproc.pid) @@ -600,12 +608,12 @@ def test_memory_full_info(self): self.assertGreaterEqual(value, 0, msg=(name, value)) self.assertLessEqual(value, total, msg=(name, value, total)) if LINUX or WINDOWS or OSX: - mem.uss + self.assertGreaterEqual(mem.uss, 0) if LINUX: - mem.pss - self.assertGreater(mem.pss, mem.uss) + self.assertGreaterEqual(mem.pss, 0) + self.assertGreaterEqual(mem.swap, 0) - @unittest.skipIf(OPENBSD or NETBSD, "platfform not supported") + @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") def test_memory_maps(self): p = psutil.Process() maps = p.memory_maps() @@ -646,6 +654,16 @@ def test_memory_maps(self): self.assertIsInstance(value, (int, long)) assert value >= 0, value + @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") + def test_memory_maps_lists_lib(self): + # Make sure a newly loaded shared lib is listed. + with copyload_shared_lib() as path: + def normpath(p): + return os.path.realpath(os.path.normcase(p)) + libpaths = [normpath(x.path) + for x in psutil.Process().memory_maps()] + self.assertIn(normpath(path), libpaths) + def test_memory_percent(self): p = psutil.Process() ret = p.memory_percent() @@ -673,12 +691,12 @@ def test_exe(self): sproc = get_test_subprocess() exe = psutil.Process(sproc.pid).exe() try: - self.assertEqual(exe, PYTHON) + self.assertEqual(exe, PYTHON_EXE) except AssertionError: - if WINDOWS and len(exe) == len(PYTHON): + if WINDOWS and len(exe) == len(PYTHON_EXE): # on Windows we don't care about case sensitivity normcase = os.path.normcase - self.assertEqual(normcase(exe), normcase(PYTHON)) + self.assertEqual(normcase(exe), normcase(PYTHON_EXE)) else: # certain platforms such as BSD are more accurate returning: # "/usr/local/bin/python2.7" @@ -689,18 +707,16 @@ def test_exe(self): ver = "%s.%s" % (sys.version_info[0], sys.version_info[1]) try: self.assertEqual(exe.replace(ver, ''), - PYTHON.replace(ver, '')) + PYTHON_EXE.replace(ver, '')) except AssertionError: # Tipically OSX. Really not sure what to do here. pass - subp = subprocess.Popen([exe, '-c', 'import os; print("hey")'], - stdout=subprocess.PIPE) - out, _ = subp.communicate() - self.assertEqual(out.strip(), b'hey') + out = sh([exe, "-c", "import os; print('hey')"]) + self.assertEqual(out, 'hey') def test_cmdline(self): - cmdline = [PYTHON, "-c", "import time; time.sleep(60)"] + cmdline = [PYTHON_EXE, "-c", "import time; time.sleep(60)"] sproc = get_test_subprocess(cmdline) try: self.assertEqual(' '.join(psutil.Process(sproc.pid).cmdline()), @@ -710,27 +726,39 @@ def test_cmdline(self): # and Open BSD returns a truncated string. # Also /proc/pid/cmdline behaves the same so it looks # like this is a kernel bug. - if NETBSD or OPENBSD: + # XXX - AIX truncates long arguments in /proc/pid/cmdline + if NETBSD or OPENBSD or AIX: self.assertEqual( - psutil.Process(sproc.pid).cmdline()[0], PYTHON) + psutil.Process(sproc.pid).cmdline()[0], PYTHON_EXE) else: raise def test_name(self): - sproc = get_test_subprocess(PYTHON) + sproc = get_test_subprocess(PYTHON_EXE) name = psutil.Process(sproc.pid).name().lower() pyexe = os.path.basename(os.path.realpath(sys.executable)).lower() assert pyexe.startswith(name), (pyexe, name) # XXX @unittest.skipIf(SUNOS, "broken on SUNOS") + @unittest.skipIf(AIX, "broken on AIX") def test_prog_w_funky_name(self): # Test that name(), exe() and cmdline() correctly handle programs # with funky chars such as spaces and ")", see: # https://github.com/giampaolo/psutil/issues/628 + + def rm(): + # Try to limit occasional failures on Appveyor: + # https://ci.appveyor.com/project/giampaolo/psutil/build/1350/ + # job/lbo3bkju55le850n + try: + safe_rmpath(funky_path) + except OSError: + pass + funky_path = TESTFN + 'foo bar )' create_exe(funky_path) - self.addCleanup(safe_rmpath, funky_path) + self.addCleanup(rm) cmdline = [funky_path, "-c", "import time; [time.sleep(0.01) for x in range(3000)];" "arg1", "arg2", "", "arg3", ""] @@ -744,7 +772,7 @@ def test_prog_w_funky_name(self): self.assertEqual(os.path.normcase(p.exe()), os.path.normcase(funky_path)) - @unittest.skipUnless(POSIX, 'POSIX only') + @unittest.skipIf(not POSIX, 'POSIX only') def test_uids(self): p = psutil.Process() real, effective, saved = p.uids() @@ -752,12 +780,13 @@ def test_uids(self): self.assertEqual(real, os.getuid()) # os.geteuid() refers to "effective" uid self.assertEqual(effective, os.geteuid()) - # no such thing as os.getsuid() ("saved" uid), but starting - # from python 2.7 we have os.getresuid()[2] + # No such thing as os.getsuid() ("saved" uid), but starting + # from python 2.7 we have os.getresuid() which returns all + # of them. if hasattr(os, "getresuid"): - self.assertEqual(saved, os.getresuid()[2]) + self.assertEqual(os.getresuid(), p.uids()) - @unittest.skipUnless(POSIX, 'POSIX only') + @unittest.skipIf(not POSIX, 'POSIX only') def test_gids(self): p = psutil.Process() real, effective, saved = p.gids() @@ -765,10 +794,11 @@ def test_gids(self): self.assertEqual(real, os.getgid()) # os.geteuid() refers to "effective" uid self.assertEqual(effective, os.getegid()) - # no such thing as os.getsgid() ("saved" gid), but starting - # from python 2.7 we have os.getresgid()[2] + # No such thing as os.getsgid() ("saved" gid), but starting + # from python 2.7 we have os.getresgid() which returns all + # of them. if hasattr(os, "getresuid"): - self.assertEqual(saved, os.getresgid()[2]) + self.assertEqual(os.getresgid(), p.gids()) def test_nice(self): p = psutil.Process() @@ -788,11 +818,17 @@ def test_nice(self): finally: p.nice(psutil.NORMAL_PRIORITY_CLASS) else: + first_nice = p.nice() try: - first_nice = p.nice() + if hasattr(os, "getpriority"): + self.assertEqual( + os.getpriority(os.PRIO_PROCESS, os.getpid()), p.nice()) p.nice(1) self.assertEqual(p.nice(), 1) - # going back to previous nice value raises + if hasattr(os, "getpriority"): + self.assertEqual( + os.getpriority(os.PRIO_PROCESS, os.getpid()), p.nice()) + # XXX - going back to previous nice value raises # AccessDenied on OSX if not OSX: p.nice(0) @@ -812,22 +848,14 @@ def test_status(self): def test_username(self): sproc = get_test_subprocess() p = psutil.Process(sproc.pid) - if POSIX: - import pwd - self.assertEqual(p.username(), pwd.getpwuid(os.getuid()).pw_name) - with mock.patch("psutil.pwd.getpwuid", - side_effect=KeyError) as fun: - p.username() == str(p.uids().real) - assert fun.called - - elif WINDOWS and 'USERNAME' in os.environ: - expected_username = os.environ['USERNAME'] - expected_domain = os.environ['USERDOMAIN'] - domain, username = p.username().split('\\') - self.assertEqual(domain, expected_domain) - self.assertEqual(username, expected_username) + username = p.username() + if WINDOWS: + domain, username = username.split('\\') + self.assertEqual(username, getpass.getuser()) + if 'USERDOMAIN' in os.environ: + self.assertEqual(domain, os.environ['USERDOMAIN']) else: - p.username() + self.assertEqual(username, getpass.getuser()) def test_cwd(self): sproc = get_test_subprocess() @@ -835,32 +863,44 @@ def test_cwd(self): self.assertEqual(p.cwd(), os.getcwd()) def test_cwd_2(self): - cmd = [PYTHON, "-c", "import os, time; os.chdir('..'); time.sleep(60)"] + cmd = [PYTHON_EXE, "-c", + "import os, time; os.chdir('..'); time.sleep(60)"] sproc = get_test_subprocess(cmd) p = psutil.Process(sproc.pid) call_until(p.cwd, "ret == os.path.dirname(os.getcwd())") - @unittest.skipUnless(WINDOWS or LINUX or FREEBSD, 'platform not supported') - @unittest.skipIf(LINUX and TRAVIS, "unreliable on TRAVIS") + @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported') def test_cpu_affinity(self): p = psutil.Process() initial = p.cpu_affinity() + assert initial, initial + self.addCleanup(p.cpu_affinity, initial) + if hasattr(os, "sched_getaffinity"): self.assertEqual(initial, list(os.sched_getaffinity(p.pid))) self.assertEqual(len(initial), len(set(initial))) + all_cpus = list(range(len(psutil.cpu_percent(percpu=True)))) - # setting on travis doesn't seem to work (always return all - # CPUs on get): - # AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, ... != [0] - for n in all_cpus: + # Work around travis failure: + # https://travis-ci.org/giampaolo/psutil/builds/284173194 + for n in all_cpus if not TRAVIS else initial: p.cpu_affinity([n]) self.assertEqual(p.cpu_affinity(), [n]) if hasattr(os, "sched_getaffinity"): self.assertEqual(p.cpu_affinity(), list(os.sched_getaffinity(p.pid))) - # - p.cpu_affinity(all_cpus) - self.assertEqual(p.cpu_affinity(), all_cpus) + # also test num_cpu() + if hasattr(p, "num_cpu"): + self.assertEqual(p.cpu_affinity()[0], p.num_cpu()) + + # [] is an alias for "all eligible CPUs"; on Linux this may + # not be equal to all available CPUs, see: + # https://github.com/giampaolo/psutil/issues/956 + p.cpu_affinity([]) + if LINUX: + self.assertEqual(p.cpu_affinity(), p._proc._get_eligible_cpus()) + else: + self.assertEqual(p.cpu_affinity(), all_cpus) if hasattr(os, "sched_getaffinity"): self.assertEqual(p.cpu_affinity(), list(os.sched_getaffinity(p.pid))) @@ -870,12 +910,35 @@ def test_cpu_affinity(self): # it should work with all iterables, not only lists p.cpu_affinity(set(all_cpus)) p.cpu_affinity(tuple(all_cpus)) + + @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported') + def test_cpu_affinity_errs(self): + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) invalid_cpu = [len(psutil.cpu_times(percpu=True)) + 10] self.assertRaises(ValueError, p.cpu_affinity, invalid_cpu) self.assertRaises(ValueError, p.cpu_affinity, range(10000, 11000)) self.assertRaises(TypeError, p.cpu_affinity, [0, "1"]) self.assertRaises(ValueError, p.cpu_affinity, [0, -1]) + @unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported') + def test_cpu_affinity_all_combinations(self): + p = psutil.Process() + initial = p.cpu_affinity() + assert initial, initial + self.addCleanup(p.cpu_affinity, initial) + + # All possible CPU set combinations. + combos = [] + for l in range(0, len(initial) + 1): + for subset in itertools.combinations(initial, l): + if subset: + combos.append(list(subset)) + + for combo in combos: + p.cpu_affinity(combo) + self.assertEqual(p.cpu_affinity(), combo) + # TODO: #595 @unittest.skipIf(BSD, "broken on BSD") # can't find any process file on Appveyor @@ -902,7 +965,7 @@ def test_open_files(self): # another process cmdline = "import time; f = open(r'%s', 'r'); time.sleep(60);" % TESTFN - sproc = get_test_subprocess([PYTHON, "-c", cmdline]) + sproc = get_test_subprocess([PYTHON_EXE, "-c", cmdline]) p = psutil.Process(sproc.pid) for x in range(100): @@ -938,175 +1001,9 @@ def test_open_files_2(self): self.assertEqual(ntuple[0], ntuple.path) self.assertEqual(ntuple[1], ntuple.fd) # test file is gone - self.assertTrue(fileobj.name not in p.open_files()) - - def compare_proc_sys_cons(self, pid, proc_cons): - from psutil._common import pconn - sys_cons = [c[:-1] for c in psutil.net_connections(kind='all') - if c.pid == pid] - if FREEBSD: - # on FreeBSD all fds are set to -1 - proc_cons = [pconn(*[-1] + list(x[1:])) for x in proc_cons] - self.assertEqual(sorted(proc_cons), sorted(sys_cons)) + self.assertNotIn(fileobj.name, p.open_files()) - @skip_on_access_denied(only_if=OSX) - def test_connections(self): - def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): - all_kinds = ("all", "inet", "inet4", "inet6", "tcp", "tcp4", - "tcp6", "udp", "udp4", "udp6") - check_connection_ntuple(conn) - self.assertEqual(conn.family, family) - self.assertEqual(conn.type, type) - self.assertEqual(conn.laddr, laddr) - self.assertEqual(conn.raddr, raddr) - self.assertEqual(conn.status, status) - for kind in all_kinds: - cons = proc.connections(kind=kind) - if kind in kinds: - self.assertNotEqual(cons, []) - else: - self.assertEqual(cons, []) - # compare against system-wide connections - # XXX Solaris can't retrieve system-wide UNIX - # sockets. - if not SUNOS: - self.compare_proc_sys_cons(proc.pid, [conn]) - - tcp_template = textwrap.dedent(""" - import socket, time - s = socket.socket($family, socket.SOCK_STREAM) - s.bind(('$addr', 0)) - s.listen(1) - with open('$testfn', 'w') as f: - f.write(str(s.getsockname()[:2])) - time.sleep(60) - """) - - udp_template = textwrap.dedent(""" - import socket, time - s = socket.socket($family, socket.SOCK_DGRAM) - s.bind(('$addr', 0)) - with open('$testfn', 'w') as f: - f.write(str(s.getsockname()[:2])) - time.sleep(60) - """) - - from string import Template - testfile = os.path.basename(TESTFN) - tcp4_template = Template(tcp_template).substitute( - family=int(AF_INET), addr="127.0.0.1", testfn=testfile) - udp4_template = Template(udp_template).substitute( - family=int(AF_INET), addr="127.0.0.1", testfn=testfile) - tcp6_template = Template(tcp_template).substitute( - family=int(AF_INET6), addr="::1", testfn=testfile) - udp6_template = Template(udp_template).substitute( - family=int(AF_INET6), addr="::1", testfn=testfile) - - # launch various subprocess instantiating a socket of various - # families and types to enrich psutil results - tcp4_proc = pyrun(tcp4_template) - tcp4_addr = eval(wait_for_file(testfile)) - udp4_proc = pyrun(udp4_template) - udp4_addr = eval(wait_for_file(testfile)) - if supports_ipv6(): - tcp6_proc = pyrun(tcp6_template) - tcp6_addr = eval(wait_for_file(testfile)) - udp6_proc = pyrun(udp6_template) - udp6_addr = eval(wait_for_file(testfile)) - else: - tcp6_proc = None - udp6_proc = None - tcp6_addr = None - udp6_addr = None - - for p in psutil.Process().children(): - cons = p.connections() - self.assertEqual(len(cons), 1) - for conn in cons: - # TCP v4 - if p.pid == tcp4_proc.pid: - check_conn(p, conn, AF_INET, SOCK_STREAM, tcp4_addr, (), - psutil.CONN_LISTEN, - ("all", "inet", "inet4", "tcp", "tcp4")) - # UDP v4 - elif p.pid == udp4_proc.pid: - check_conn(p, conn, AF_INET, SOCK_DGRAM, udp4_addr, (), - psutil.CONN_NONE, - ("all", "inet", "inet4", "udp", "udp4")) - # TCP v6 - elif p.pid == getattr(tcp6_proc, "pid", None): - check_conn(p, conn, AF_INET6, SOCK_STREAM, tcp6_addr, (), - psutil.CONN_LISTEN, - ("all", "inet", "inet6", "tcp", "tcp6")) - # UDP v6 - elif p.pid == getattr(udp6_proc, "pid", None): - check_conn(p, conn, AF_INET6, SOCK_DGRAM, udp6_addr, (), - psutil.CONN_NONE, - ("all", "inet", "inet6", "udp", "udp6")) - - @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'AF_UNIX not supported') - @skip_on_access_denied(only_if=OSX) - def test_connections_unix(self): - def check(type): - safe_rmpath(TESTFN) - tfile = tempfile.mktemp(prefix=TESTFILE_PREFIX) if OSX else TESTFN - sock = socket.socket(AF_UNIX, type) - with contextlib.closing(sock): - sock.bind(tfile) - cons = psutil.Process().connections(kind='unix') - conn = cons[0] - check_connection_ntuple(conn) - if conn.fd != -1: # != sunos and windows - self.assertEqual(conn.fd, sock.fileno()) - self.assertEqual(conn.family, AF_UNIX) - self.assertEqual(conn.type, type) - self.assertEqual(conn.laddr, tfile) - if not SUNOS: - # XXX Solaris can't retrieve system-wide UNIX - # sockets. - self.compare_proc_sys_cons(os.getpid(), cons) - - check(SOCK_STREAM) - check(SOCK_DGRAM) - - @unittest.skipUnless(hasattr(socket, "fromfd"), - 'socket.fromfd() not supported') - @unittest.skipIf(WINDOWS or SUNOS, - 'connection fd not available on this platform') - def test_connection_fromfd(self): - with contextlib.closing(socket.socket()) as sock: - sock.bind(('localhost', 0)) - sock.listen(1) - p = psutil.Process() - for conn in p.connections(): - if conn.fd == sock.fileno(): - break - else: - self.fail("couldn't find socket fd") - dupsock = socket.fromfd(conn.fd, conn.family, conn.type) - with contextlib.closing(dupsock): - self.assertEqual(dupsock.getsockname(), conn.laddr) - self.assertNotEqual(sock.fileno(), dupsock.fileno()) - - def test_connection_constants(self): - ints = [] - strs = [] - for name in dir(psutil): - if name.startswith('CONN_'): - num = getattr(psutil, name) - str_ = str(num) - assert str_.isupper(), str_ - assert str_ not in strs, str_ - assert num not in ints, num - ints.append(num) - strs.append(str_) - if SUNOS: - psutil.CONN_IDLE - psutil.CONN_BOUND - if WINDOWS: - psutil.CONN_DELETE_TCB - - @unittest.skipUnless(POSIX, 'POSIX only') + @unittest.skipIf(not POSIX, 'POSIX only') def test_num_fds(self): p = psutil.Process() start = p.num_fds() @@ -1131,24 +1028,48 @@ def test_num_ctx_switches(self): return self.fail("num ctx switches still the same after 50.000 iterations") - def test_parent_ppid(self): + def test_ppid(self): + if hasattr(os, 'getppid'): + self.assertEqual(psutil.Process().ppid(), os.getppid()) this_parent = os.getpid() sproc = get_test_subprocess() p = psutil.Process(sproc.pid) self.assertEqual(p.ppid(), this_parent) - self.assertEqual(p.parent().pid, this_parent) # no other process is supposed to have us as parent reap_children(recursive=True) + if APPVEYOR: + # Occasional failures, see: + # https://ci.appveyor.com/project/giampaolo/psutil/build/ + # job/0hs623nenj7w4m33 + return for p in psutil.process_iter(): if p.pid == sproc.pid: continue - self.assertNotEqual(p.ppid(), this_parent) + # XXX: sometimes this fails on Windows; not sure why. + self.assertNotEqual(p.ppid(), this_parent, msg=p) + + def test_parent(self): + this_parent = os.getpid() + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + self.assertEqual(p.parent().pid, this_parent) + + def test_parent_disappeared(self): + # Emulate a case where the parent process disappeared. + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + with mock.patch("psutil.Process", + side_effect=psutil.NoSuchProcess(0, 'foo')): + self.assertIsNone(p.parent()) def test_children(self): p = psutil.Process() self.assertEqual(p.children(), []) self.assertEqual(p.children(recursive=True), []) - sproc = get_test_subprocess() + # On Windows we set the flag to 0 in order to cancel out the + # CREATE_NO_WINDOW flag (enabled by default) which creates + # an extra "conhost.exe" child. + sproc = get_test_subprocess(creationflags=0) children1 = p.children() children2 = p.children(recursive=True) for children in (children1, children2): @@ -1157,25 +1078,17 @@ def test_children(self): self.assertEqual(children[0].ppid(), os.getpid()) def test_children_recursive(self): - # here we create a subprocess which creates another one as in: - # A (parent) -> B (child) -> C (grandchild) - s = "import subprocess, os, sys, time;" - s += "PYTHON = os.path.realpath(sys.executable);" - s += "cmd = [PYTHON, '-c', 'import time; time.sleep(60);'];" - s += "subprocess.Popen(cmd);" - s += "time.sleep(60);" - get_test_subprocess(cmd=[PYTHON, "-c", s]) + # Test children() against two sub processes, p1 and p2, where + # p1 (our child) spawned p2 (our grandchild). + p1, p2 = create_proc_children_pair() p = psutil.Process() - self.assertEqual(len(p.children(recursive=False)), 1) - # give the grandchild some time to start - stop_at = time.time() + GLOBAL_TIMEOUT - while time.time() < stop_at: - children = p.children(recursive=True) - if len(children) > 1: - break - self.assertEqual(len(children), 2) - self.assertEqual(children[0].ppid(), os.getpid()) - self.assertEqual(children[1].ppid(), children[0].pid) + self.assertEqual(p.children(), [p1]) + self.assertEqual(p.children(recursive=True), [p1, p2]) + # If the intermediate process is gone there's no way for + # children() to recursively find it. + p1.terminate() + p1.wait() + self.assertEqual(p.children(recursive=True), []) def test_children_duplicates(self): # find the process which has the highest number of children @@ -1220,15 +1133,34 @@ def test_as_dict(self): if not isinstance(d['connections'], list): self.assertEqual(d['connections'], 'foo') - with mock.patch('psutil.Process.name', create=True, + # Test ad_value is set on AccessDenied. + with mock.patch('psutil.Process.nice', create=True, + side_effect=psutil.AccessDenied): + self.assertEqual( + p.as_dict(attrs=["nice"], ad_value=1), {"nice": 1}) + + # Test that NoSuchProcess bubbles up. + with mock.patch('psutil.Process.nice', create=True, + side_effect=psutil.NoSuchProcess(p.pid, "name")): + self.assertRaises( + psutil.NoSuchProcess, p.as_dict, attrs=["nice"]) + + # Test that ZombieProcess is swallowed. + with mock.patch('psutil.Process.nice', create=True, + side_effect=psutil.ZombieProcess(p.pid, "name")): + self.assertEqual( + p.as_dict(attrs=["nice"], ad_value="foo"), {"nice": "foo"}) + + # By default APIs raising NotImplementedError are + # supposed to be skipped. + with mock.patch('psutil.Process.nice', create=True, side_effect=NotImplementedError): - # By default APIs raising NotImplementedError are - # supposed to be skipped. d = p.as_dict() - self.assertNotIn('name', list(d.keys())) + self.assertNotIn('nice', list(d.keys())) # ...unless the user explicitly asked for some attr. with self.assertRaises(NotImplementedError): - p.as_dict(attrs=["name"]) + p.as_dict(attrs=["nice"]) + # errors with self.assertRaises(TypeError): p.as_dict('name') @@ -1290,7 +1222,7 @@ def test_halfway_terminated_process(self): excluded_names = ['pid', 'is_running', 'wait', 'create_time', 'oneshot', 'memory_info_ex'] - if LINUX and not RLIMIT_SUPPORT: + if LINUX and not HAS_RLIMIT: excluded_names.append('rlimit') for name in dir(p): if (name.startswith('_') or @@ -1334,7 +1266,7 @@ def test_halfway_terminated_process(self): "NoSuchProcess exception not raised for %r, retval=%s" % ( name, ret)) - @unittest.skipUnless(POSIX, 'POSIX only') + @unittest.skipIf(not POSIX, 'POSIX only') def test_zombie_process(self): def succeed_or_zombie_p_exc(fun, *args, **kwargs): try: @@ -1342,92 +1274,90 @@ def succeed_or_zombie_p_exc(fun, *args, **kwargs): except (psutil.ZombieProcess, psutil.AccessDenied): pass - # Note: in this test we'll be creating two sub processes. - # Both of them are supposed to be freed / killed by - # reap_children() as they are attributable to 'us' - # (os.getpid()) via children(recursive=True). - unix_file = tempfile.mktemp(prefix=TESTFILE_PREFIX) if OSX else TESTFN - src = textwrap.dedent("""\ - import os, sys, time, socket, contextlib - child_pid = os.fork() - if child_pid > 0: - time.sleep(3000) - else: - # this is the zombie process - s = socket.socket(socket.AF_UNIX) - with contextlib.closing(s): - s.connect('%s') - if sys.version_info < (3, ): - pid = str(os.getpid()) - else: - pid = bytes(str(os.getpid()), 'ascii') - s.sendall(pid) - """ % unix_file) - with contextlib.closing(socket.socket(socket.AF_UNIX)) as sock: + zpid = create_zombie_proc() + self.addCleanup(reap_children, recursive=True) + # A zombie process should always be instantiable + zproc = psutil.Process(zpid) + # ...and at least its status always be querable + self.assertEqual(zproc.status(), psutil.STATUS_ZOMBIE) + # ...and it should be considered 'running' + self.assertTrue(zproc.is_running()) + # ...and as_dict() shouldn't crash + zproc.as_dict() + # if cmdline succeeds it should be an empty list + ret = succeed_or_zombie_p_exc(zproc.suspend) + if ret is not None: + self.assertEqual(ret, []) + + if hasattr(zproc, "rlimit"): + succeed_or_zombie_p_exc(zproc.rlimit, psutil.RLIMIT_NOFILE) + succeed_or_zombie_p_exc(zproc.rlimit, psutil.RLIMIT_NOFILE, + (5, 5)) + # set methods + succeed_or_zombie_p_exc(zproc.parent) + if hasattr(zproc, 'cpu_affinity'): try: - sock.settimeout(GLOBAL_TIMEOUT) - sock.bind(unix_file) - sock.listen(1) - pyrun(src) - conn, _ = sock.accept() - self.addCleanup(conn.close) - select.select([conn.fileno()], [], [], GLOBAL_TIMEOUT) - zpid = int(conn.recv(1024)) - zproc = psutil.Process(zpid) - call_until(lambda: zproc.status(), - "ret == psutil.STATUS_ZOMBIE") - # A zombie process should always be instantiable - zproc = psutil.Process(zpid) - # ...and at least its status always be querable - self.assertEqual(zproc.status(), psutil.STATUS_ZOMBIE) - # ...and it should be considered 'running' - self.assertTrue(zproc.is_running()) - # ...and as_dict() shouldn't crash - zproc.as_dict() - # if cmdline succeeds it should be an empty list - ret = succeed_or_zombie_p_exc(zproc.suspend) - if ret is not None: - self.assertEqual(ret, []) - - if hasattr(zproc, "rlimit"): - succeed_or_zombie_p_exc(zproc.rlimit, psutil.RLIMIT_NOFILE) - succeed_or_zombie_p_exc(zproc.rlimit, psutil.RLIMIT_NOFILE, - (5, 5)) - # set methods - succeed_or_zombie_p_exc(zproc.parent) - if hasattr(zproc, 'cpu_affinity'): - succeed_or_zombie_p_exc(zproc.cpu_affinity, [0]) - succeed_or_zombie_p_exc(zproc.nice, 0) - if hasattr(zproc, 'ionice'): - if LINUX: - succeed_or_zombie_p_exc(zproc.ionice, 2, 0) - else: - succeed_or_zombie_p_exc(zproc.ionice, 0) # Windows - if hasattr(zproc, 'rlimit'): - succeed_or_zombie_p_exc(zproc.rlimit, - psutil.RLIMIT_NOFILE, (5, 5)) - succeed_or_zombie_p_exc(zproc.suspend) - succeed_or_zombie_p_exc(zproc.resume) - succeed_or_zombie_p_exc(zproc.terminate) - succeed_or_zombie_p_exc(zproc.kill) - - # ...its parent should 'see' it - # edit: not true on BSD and OSX - # descendants = [x.pid for x in psutil.Process().children( - # recursive=True)] - # self.assertIn(zpid, descendants) - # XXX should we also assume ppid be usable? Note: this - # would be an important use case as the only way to get - # rid of a zombie is to kill its parent. - # self.assertEqual(zpid.ppid(), os.getpid()) - # ...and all other APIs should be able to deal with it - self.assertTrue(psutil.pid_exists(zpid)) - self.assertIn(zpid, psutil.pids()) - self.assertIn(zpid, [x.pid for x in psutil.process_iter()]) - psutil._pmap = {} - self.assertIn(zpid, [x.pid for x in psutil.process_iter()]) - finally: - reap_children(recursive=True) + succeed_or_zombie_p_exc(zproc.cpu_affinity, [0]) + except ValueError as err: + if TRAVIS and LINUX and "not eligible" in str(err): + # https://travis-ci.org/giampaolo/psutil/jobs/279890461 + pass + else: + raise + + succeed_or_zombie_p_exc(zproc.nice, 0) + if hasattr(zproc, 'ionice'): + if LINUX: + succeed_or_zombie_p_exc(zproc.ionice, 2, 0) + else: + succeed_or_zombie_p_exc(zproc.ionice, 0) # Windows + if hasattr(zproc, 'rlimit'): + succeed_or_zombie_p_exc(zproc.rlimit, + psutil.RLIMIT_NOFILE, (5, 5)) + succeed_or_zombie_p_exc(zproc.suspend) + succeed_or_zombie_p_exc(zproc.resume) + succeed_or_zombie_p_exc(zproc.terminate) + succeed_or_zombie_p_exc(zproc.kill) + + # ...its parent should 'see' it + # edit: not true on BSD and OSX + # descendants = [x.pid for x in psutil.Process().children( + # recursive=True)] + # self.assertIn(zpid, descendants) + # XXX should we also assume ppid be usable? Note: this + # would be an important use case as the only way to get + # rid of a zombie is to kill its parent. + # self.assertEqual(zpid.ppid(), os.getpid()) + # ...and all other APIs should be able to deal with it + self.assertTrue(psutil.pid_exists(zpid)) + if not TRAVIS and OSX: + # For some reason this started failing all of the sudden. + # Maybe they upgraded OSX version? + # https://travis-ci.org/giampaolo/psutil/jobs/310896404 + self.assertIn(zpid, psutil.pids()) + self.assertIn(zpid, [x.pid for x in psutil.process_iter()]) + psutil._pmap = {} + self.assertIn(zpid, [x.pid for x in psutil.process_iter()]) + + @unittest.skipIf(not POSIX, 'POSIX only') + def test_zombie_process_is_running_w_exc(self): + # Emulate a case where internally is_running() raises + # ZombieProcess. + p = psutil.Process() + with mock.patch("psutil.Process", + side_effect=psutil.ZombieProcess(0)) as m: + assert p.is_running() + assert m.called + + @unittest.skipIf(not POSIX, 'POSIX only') + def test_zombie_process_status_w_exc(self): + # Emulate a case where internally status() raises + # ZombieProcess. + p = psutil.Process() + with mock.patch("psutil._psplatform.Process.status", + side_effect=psutil.ZombieProcess(0)) as m: + self.assertEqual(p.status(), psutil.STATUS_ZOMBIE) + assert m.called def test_pid_0(self): # Process(0) is supposed to work on all platforms except Linux @@ -1468,75 +1398,43 @@ def test_pid_0(self): self.assertIn(0, psutil.pids()) self.assertTrue(psutil.pid_exists(0)) - def test_Popen(self): - # XXX this test causes a ResourceWarning on Python 3 because - # psutil.__subproc instance doesn't get propertly freed. - # Not sure what to do though. - cmd = [PYTHON, "-c", "import time; time.sleep(60);"] - proc = psutil.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - try: - proc.name() - proc.cpu_times() - proc.stdin - self.assertTrue(dir(proc)) - self.assertRaises(AttributeError, getattr, proc, 'foo') - finally: - proc.kill() - proc.wait() - - def test_Popen_ctx_manager(self): - with psutil.Popen([PYTHON, "-V"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=subprocess.PIPE) as proc: - pass - assert proc.stdout.closed - assert proc.stderr.closed - assert proc.stdin.closed - - @unittest.skipUnless(hasattr(psutil.Process, "environ"), - "platform not supported") + @unittest.skipIf(not HAS_ENVIRON, "not supported") def test_environ(self): + def clean_dict(d): + # Most of these are problematic on Travis. + d.pop("PSUTIL_TESTING", None) + d.pop("PLAT", None) + d.pop("HOME", None) + if OSX: + d.pop("__CF_USER_TEXT_ENCODING", None) + d.pop("VERSIONER_PYTHON_PREFER_32_BIT", None) + d.pop("VERSIONER_PYTHON_VERSION", None) + return dict( + [(k.rstrip("\r\n"), v.rstrip("\r\n")) for k, v in d.items()]) + self.maxDiff = None p = psutil.Process() - d = p.environ() - d2 = os.environ.copy() - - removes = [] - if OSX: - removes.extend([ - "__CF_USER_TEXT_ENCODING", - "VERSIONER_PYTHON_PREFER_32_BIT", - "VERSIONER_PYTHON_VERSION"]) - if LINUX or OSX: - removes.extend(['PLAT']) - if TOX: - removes.extend(['HOME']) - for key in removes: - d.pop(key, None) - d2.pop(key, None) - - self.assertEqual(d, d2) - - @unittest.skipUnless(hasattr(psutil.Process, "environ"), - "platform not supported") - @unittest.skipUnless(POSIX, "posix only") + d1 = clean_dict(p.environ()) + d2 = clean_dict(os.environ.copy()) + self.assertEqual(d1, d2) + + @unittest.skipIf(not HAS_ENVIRON, "not supported") + @unittest.skipIf(not POSIX, "POSIX only") def test_weird_environ(self): # environment variables can contain values without an equals sign code = textwrap.dedent(""" - #include - #include - char * const argv[] = {"cat", 0}; - char * const envp[] = {"A=1", "X", "C=3", 0}; - int main(void) { - /* Close stderr on exec so parent can wait for the execve to - * finish. */ - if (fcntl(2, F_SETFD, FD_CLOEXEC) != 0) - return 0; - return execve("/bin/cat", argv, envp); - } - """) + #include + #include + char * const argv[] = {"cat", 0}; + char * const envp[] = {"A=1", "X", "C=3", 0}; + int main(void) { + /* Close stderr on exec so parent can wait for the execve to + * finish. */ + if (fcntl(2, F_SETFD, FD_CLOEXEC) != 0) + return 0; + return execve("/bin/cat", argv, envp); + } + """) path = TESTFN create_exe(path, c_code=code) self.addCleanup(safe_rmpath, path) @@ -1553,300 +1451,6 @@ def test_weird_environ(self): self.assertEqual(sproc.returncode, 0) -# =================================================================== -# --- Featch all processes test -# =================================================================== - -class TestFetchAllProcesses(unittest.TestCase): - """Test which iterates over all running processes and performs - some sanity checks against Process API's returned values. - """ - - def setUp(self): - if POSIX: - import pwd - import grp - users = pwd.getpwall() - groups = grp.getgrall() - self.all_uids = set([x.pw_uid for x in users]) - self.all_usernames = set([x.pw_name for x in users]) - self.all_gids = set([x.gr_gid for x in groups]) - - def test_fetch_all(self): - valid_procs = 0 - excluded_names = set([ - 'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', - 'as_dict', 'cpu_percent', 'parent', 'children', 'pid', - 'memory_info_ex', 'oneshot', - ]) - if LINUX and not RLIMIT_SUPPORT: - excluded_names.add('rlimit') - attrs = [] - for name in dir(psutil.Process): - if name.startswith("_"): - continue - if name in excluded_names: - continue - attrs.append(name) - - default = object() - failures = [] - for p in psutil.process_iter(): - with p.oneshot(): - for name in attrs: - ret = default - try: - args = () - attr = getattr(p, name, None) - if attr is not None and callable(attr): - if name == 'rlimit': - args = (psutil.RLIMIT_NOFILE,) - ret = attr(*args) - else: - ret = attr - valid_procs += 1 - except NotImplementedError: - msg = "%r was skipped because not implemented" % ( - self.__class__.__name__ + '.test_' + name) - warn(msg) - except (psutil.NoSuchProcess, psutil.AccessDenied) as err: - self.assertEqual(err.pid, p.pid) - if err.name: - # make sure exception's name attr is set - # with the actual process name - self.assertEqual(err.name, p.name()) - self.assertTrue(str(err)) - self.assertTrue(err.msg) - except Exception as err: - s = '\n' + '=' * 70 + '\n' - s += "FAIL: test_%s (proc=%s" % (name, p) - if ret != default: - s += ", ret=%s)" % repr(ret) - s += ')\n' - s += '-' * 70 - s += "\n%s" % traceback.format_exc() - s = "\n".join((" " * 4) + i for i in s.splitlines()) - s += '\n' - failures.append(s) - break - else: - if ret not in (0, 0.0, [], None, '', {}): - assert ret, ret - meth = getattr(self, name) - meth(ret, p) - - if failures: - self.fail(''.join(failures)) - - # we should always have a non-empty list, not including PID 0 etc. - # special cases. - self.assertTrue(valid_procs > 0) - - def cmdline(self, ret, proc): - pass - - def exe(self, ret, proc): - if not ret: - self.assertEqual(ret, '') - else: - assert os.path.isabs(ret), ret - # Note: os.stat() may return False even if the file is there - # hence we skip the test, see: - # http://stackoverflow.com/questions/3112546/os-path-exists-lies - if POSIX and os.path.isfile(ret): - if hasattr(os, 'access') and hasattr(os, "X_OK"): - # XXX may fail on OSX - self.assertTrue(os.access(ret, os.X_OK)) - - def ppid(self, ret, proc): - self.assertTrue(ret >= 0) - - def name(self, ret, proc): - self.assertIsInstance(ret, (str, unicode)) - self.assertTrue(ret) - - def create_time(self, ret, proc): - try: - self.assertGreaterEqual(ret, 0) - except AssertionError: - if OPENBSD and proc.status == psutil.STATUS_ZOMBIE: - pass - else: - raise - # this can't be taken for granted on all platforms - # self.assertGreaterEqual(ret, psutil.boot_time()) - # make sure returned value can be pretty printed - # with strftime - time.strftime("%Y %m %d %H:%M:%S", time.localtime(ret)) - - def uids(self, ret, proc): - for uid in ret: - self.assertGreaterEqual(uid, 0) - self.assertIn(uid, self.all_uids) - - def gids(self, ret, proc): - # note: testing all gids as above seems not to be reliable for - # gid == 30 (nodoby); not sure why. - for gid in ret: - if not OSX and not NETBSD: - self.assertGreaterEqual(gid, 0) - self.assertIn(gid, self.all_gids) - - def username(self, ret, proc): - self.assertTrue(ret) - if POSIX: - self.assertIn(ret, self.all_usernames) - - def status(self, ret, proc): - self.assertTrue(ret != "") - self.assertTrue(ret != '?') - self.assertIn(ret, VALID_PROC_STATUSES) - - def io_counters(self, ret, proc): - for field in ret: - if field != -1: - self.assertTrue(field >= 0) - - def ionice(self, ret, proc): - if LINUX: - self.assertTrue(ret.ioclass >= 0) - self.assertTrue(ret.value >= 0) - else: - self.assertTrue(ret >= 0) - self.assertIn(ret, (0, 1, 2)) - - def num_threads(self, ret, proc): - self.assertTrue(ret >= 1) - - def threads(self, ret, proc): - for t in ret: - self.assertTrue(t.id >= 0) - self.assertTrue(t.user_time >= 0) - self.assertTrue(t.system_time >= 0) - - def cpu_times(self, ret, proc): - self.assertTrue(ret.user >= 0) - self.assertTrue(ret.system >= 0) - - def memory_info(self, ret, proc): - for name in ret._fields: - self.assertGreaterEqual(getattr(ret, name), 0) - if POSIX and ret.vms != 0: - # VMS is always supposed to be the highest - for name in ret._fields: - if name != 'vms': - value = getattr(ret, name) - assert ret.vms > value, ret - elif WINDOWS: - assert ret.peak_wset >= ret.wset, ret - assert ret.peak_paged_pool >= ret.paged_pool, ret - assert ret.peak_nonpaged_pool >= ret.nonpaged_pool, ret - assert ret.peak_pagefile >= ret.pagefile, ret - - def memory_full_info(self, ret, proc): - total = psutil.virtual_memory().total - for name in ret._fields: - value = getattr(ret, name) - self.assertGreaterEqual(value, 0, msg=(name, value)) - self.assertLessEqual(value, total, msg=(name, value, total)) - - if LINUX: - self.assertGreaterEqual(ret.pss, ret.uss) - - def open_files(self, ret, proc): - for f in ret: - if WINDOWS: - 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 - assert os.path.isabs(f.path), f - assert os.path.isfile(f.path), f - - def num_fds(self, ret, proc): - self.assertTrue(ret >= 0) - - def connections(self, ret, proc): - self.assertEqual(len(ret), len(set(ret))) - for conn in ret: - check_connection_ntuple(conn) - - def cwd(self, ret, proc): - if ret is not None: # BSD may return None - assert os.path.isabs(ret), ret - try: - st = os.stat(ret) - except OSError as err: - # directory has been removed in mean time - if err.errno != errno.ENOENT: - raise - else: - self.assertTrue(stat.S_ISDIR(st.st_mode)) - - def memory_percent(self, ret, proc): - assert 0 <= ret <= 100, ret - - def is_running(self, ret, proc): - self.assertTrue(ret) - - def cpu_affinity(self, ret, proc): - assert ret != [], ret - - def terminal(self, ret, proc): - if ret is not None: - assert os.path.isabs(ret), ret - assert os.path.exists(ret), ret - - def memory_maps(self, ret, proc): - for nt in ret: - for fname in nt._fields: - value = getattr(nt, fname) - if fname == 'path': - if not value.startswith('['): - assert os.path.isabs(nt.path), nt.path - # commented as on Linux we might get - # '/foo/bar (deleted)' - # assert os.path.exists(nt.path), nt.path - elif fname in ('addr', 'perms'): - self.assertTrue(value) - else: - self.assertIsInstance(value, (int, long)) - assert value >= 0, value - - def num_handles(self, ret, proc): - if WINDOWS: - self.assertGreaterEqual(ret, 0) - else: - self.assertGreaterEqual(ret, 0) - - def nice(self, ret, proc): - if POSIX: - assert -20 <= ret <= 20, ret - else: - priorities = [getattr(psutil, x) for x in dir(psutil) - if x.endswith('_PRIORITY_CLASS')] - self.assertIn(ret, priorities) - - def num_ctx_switches(self, ret, proc): - self.assertGreaterEqual(ret.voluntary, 0) - self.assertGreaterEqual(ret.involuntary, 0) - - def rlimit(self, ret, proc): - self.assertEqual(len(ret), 2) - self.assertGreaterEqual(ret[0], -1) - self.assertGreaterEqual(ret[1], -1) - - def environ(self, ret, proc): - self.assertIsInstance(ret, dict) - - # =================================================================== # --- Limited user tests # =================================================================== @@ -1902,120 +1506,59 @@ def test_zombie_process(self): # =================================================================== -# --- Unicode tests +# --- psutil.Popen tests # =================================================================== -class TestUnicode(unittest.TestCase): - """ - Make sure that APIs returning a string are able to handle unicode, - see: https://github.com/giampaolo/psutil/issues/655 - """ - uexe = TESTFN + 'èfile' - udir = TESTFN + 'èdir' - - @classmethod - def setUpClass(cls): - safe_rmpath(cls.uexe) - safe_rmpath(cls.udir) - create_exe(cls.uexe) - os.mkdir(cls.udir) - - @classmethod - def tearDownClass(cls): - if not APPVEYOR: - safe_rmpath(cls.uexe) - safe_rmpath(cls.udir) +class TestPopen(unittest.TestCase): + """Tests for psutil.Popen class.""" - def setUp(self): + def tearDown(self): reap_children() - tearDown = setUp - - def test_proc_exe(self): - subp = get_test_subprocess(cmd=[self.uexe]) - p = psutil.Process(subp.pid) - self.assertIsInstance(p.name(), str) - if not OSX and TRAVIS: - self.assertEqual(p.exe(), self.uexe) - else: - p.exe() - - def test_proc_name(self): - subp = get_test_subprocess(cmd=[self.uexe]) - if WINDOWS: - # XXX: why is this like this? - from psutil._pswindows import py2_strencode - name = py2_strencode(psutil._psplatform.cext.proc_name(subp.pid)) - else: - name = psutil.Process(subp.pid).name() - if not OSX and TRAVIS: - self.assertEqual(name, os.path.basename(self.uexe)) - - def test_proc_cmdline(self): - subp = get_test_subprocess(cmd=[self.uexe]) - p = psutil.Process(subp.pid) - self.assertIsInstance("".join(p.cmdline()), str) - if not OSX and TRAVIS: - self.assertEqual(p.cmdline(), [self.uexe]) - else: - p.cmdline() - - def test_proc_cwd(self): - with chdir(self.udir): - p = psutil.Process() - self.assertIsInstance(p.cwd(), str) - if not OSX and TRAVIS: - self.assertEqual(p.cwd(), self.udir) - else: - p.cwd() + def test_misc(self): + # XXX this test causes a ResourceWarning on Python 3 because + # psutil.__subproc instance doesn't get propertly freed. + # Not sure what to do though. + cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"] + with psutil.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as proc: + proc.name() + proc.cpu_times() + proc.stdin + self.assertTrue(dir(proc)) + self.assertRaises(AttributeError, getattr, proc, 'foo') + proc.terminate() - # @unittest.skipIf(APPVEYOR, "unreliable on APPVEYOR") - def test_proc_open_files(self): - p = psutil.Process() - start = set(p.open_files()) - with open(self.uexe, 'rb'): - new = set(p.open_files()) - path = (new - start).pop().path - if BSD and not path: - # XXX - # see https://github.com/giampaolo/psutil/issues/595 - self.skipTest("open_files on BSD is broken") - self.assertIsInstance(path, str) - if not OSX and TRAVIS: - self.assertEqual(os.path.normcase(path), - os.path.normcase(self.uexe)) - - @unittest.skipUnless(hasattr(psutil.Process, "environ"), - "platform not supported") - def test_proc_environ(self): - env = os.environ.copy() - env['FUNNY_ARG'] = self.uexe - sproc = get_test_subprocess(env=env) - p = psutil.Process(sproc.pid) - if WINDOWS and not PY3: - uexe = self.uexe.decode(sys.getfilesystemencoding()) - else: - uexe = self.uexe - if not OSX and TRAVIS: - self.assertEqual(p.environ()['FUNNY_ARG'], uexe) - else: - p.environ() - - def test_disk_usage(self): - psutil.disk_usage(self.udir) - - -class TestInvalidUnicode(TestUnicode): - """Test handling of invalid utf8 data.""" - if PY3: - uexe = (TESTFN.encode('utf8') + b"f\xc0\x80").decode( - 'utf8', 'surrogateescape') - udir = (TESTFN.encode('utf8') + b"d\xc0\x80").decode( - 'utf8', 'surrogateescape') - else: - uexe = TESTFN + b"f\xc0\x80" - udir = TESTFN + b"d\xc0\x80" + def test_ctx_manager(self): + with psutil.Popen([PYTHON_EXE, "-V"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE) as proc: + proc.communicate() + assert proc.stdout.closed + assert proc.stderr.closed + assert proc.stdin.closed + self.assertEqual(proc.returncode, 0) + + def test_kill_terminate(self): + # subprocess.Popen()'s terminate(), kill() and send_signal() do + # not raise exception after the process is gone. psutil.Popen + # diverges from that. + cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"] + with psutil.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as proc: + proc.terminate() + proc.wait() + self.assertRaises(psutil.NoSuchProcess, proc.terminate) + self.assertRaises(psutil.NoSuchProcess, proc.kill) + self.assertRaises(psutil.NoSuchProcess, proc.send_signal, + signal.SIGTERM) + if WINDOWS and sys.version_info >= (2, 7): + self.assertRaises(psutil.NoSuchProcess, proc.send_signal, + signal.CTRL_C_EVENT) + self.assertRaises(psutil.NoSuchProcess, proc.send_signal, + signal.CTRL_BREAK_EVENT) if __name__ == '__main__': diff --git a/psutil/tests/test_sunos.py b/psutil/tests/test_sunos.py index 9694b22b5..ea9afcde0 100755 --- a/psutil/tests/test_sunos.py +++ b/psutil/tests/test_sunos.py @@ -15,7 +15,7 @@ from psutil.tests import unittest -@unittest.skipUnless(SUNOS, "SUNOS only") +@unittest.skipIf(not SUNOS, "SUNOS only") class SunOSSpecificTestCase(unittest.TestCase): def test_swap_memory(self): @@ -36,6 +36,10 @@ def test_swap_memory(self): self.assertEqual(psutil_swap.used, used) self.assertEqual(psutil_swap.free, free) + def test_cpu_count(self): + out = sh("/usr/sbin/psrinfo") + self.assertEqual(psutil.cpu_count(), len(out.split('\n'))) + if __name__ == '__main__': run_test_module_by_name(__file__) diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index e2220be49..20b132a91 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -19,6 +19,7 @@ import time import psutil +from psutil import AIX from psutil import BSD from psutil import FREEBSD from psutil import LINUX @@ -29,18 +30,22 @@ from psutil import SUNOS from psutil import WINDOWS from psutil._compat import long -from psutil.tests import AF_INET6 from psutil.tests import APPVEYOR +from psutil.tests import ASCII_FS from psutil.tests import check_net_address from psutil.tests import DEVNULL from psutil.tests import enum from psutil.tests import get_test_subprocess +from psutil.tests import HAS_BATTERY +from psutil.tests import HAS_CPU_FREQ +from psutil.tests import HAS_SENSORS_BATTERY +from psutil.tests import HAS_SENSORS_FANS +from psutil.tests import HAS_SENSORS_TEMPERATURES from psutil.tests import mock 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_rmpath -from psutil.tests import skip_on_access_denied from psutil.tests import TESTFN from psutil.tests import TESTFN_UNICODE from psutil.tests import TRAVIS @@ -78,11 +83,31 @@ def test_process_iter(self): with self.assertRaises(psutil.AccessDenied): list(psutil.process_iter()) + def test_prcess_iter_w_params(self): + for p in psutil.process_iter(attrs=['pid']): + self.assertEqual(list(p.info.keys()), ['pid']) + with self.assertRaises(ValueError): + list(psutil.process_iter(attrs=['foo'])) + with mock.patch("psutil._psplatform.Process.cpu_times", + side_effect=psutil.AccessDenied(0, "")) as m: + for p in psutil.process_iter(attrs=["pid", "cpu_times"]): + self.assertIsNone(p.info['cpu_times']) + self.assertGreaterEqual(p.info['pid'], 0) + assert m.called + with mock.patch("psutil._psplatform.Process.cpu_times", + side_effect=psutil.AccessDenied(0, "")) as m: + flag = object() + for p in psutil.process_iter( + attrs=["pid", "cpu_times"], ad_value=flag): + self.assertIs(p.info['cpu_times'], flag) + self.assertGreaterEqual(p.info['pid'], 0) + assert m.called + def test_wait_procs(self): def callback(p): - l.append(p.pid) + pids.append(p.pid) - l = [] + pids = [] sproc1 = get_test_subprocess() sproc2 = get_test_subprocess() sproc3 = get_test_subprocess() @@ -95,7 +120,7 @@ def callback(p): self.assertLess(time.time() - t, 0.5) self.assertEqual(gone, []) self.assertEqual(len(alive), 3) - self.assertEqual(l, []) + self.assertEqual(pids, []) for p in alive: self.assertFalse(hasattr(p, 'returncode')) @@ -111,10 +136,10 @@ def test(procs, callback): gone, alive = test(procs, callback) self.assertIn(sproc3.pid, [x.pid for x in gone]) if POSIX: - self.assertEqual(gone.pop().returncode, signal.SIGTERM) + self.assertEqual(gone.pop().returncode, -signal.SIGTERM) else: self.assertEqual(gone.pop().returncode, 1) - self.assertEqual(l, [sproc3.pid]) + self.assertEqual(pids, [sproc3.pid]) for p in alive: self.assertFalse(hasattr(p, 'returncode')) @@ -129,7 +154,7 @@ def test(procs, callback): sproc1.terminate() sproc2.terminate() gone, alive = test(procs, callback) - self.assertEqual(set(l), set([sproc1.pid, sproc2.pid, sproc3.pid])) + self.assertEqual(set(pids), set([sproc1.pid, sproc2.pid, sproc3.pid])) for p in gone: self.assertTrue(hasattr(p, 'returncode')) @@ -148,7 +173,7 @@ def test_boot_time(self): self.assertGreater(bt, 0) self.assertLess(bt, time.time()) - @unittest.skipUnless(POSIX, 'POSIX only') + @unittest.skipIf(not POSIX, 'POSIX only') def test_PAGESIZE(self): # pagesize is used internally to perform different calculations # and it's determined by using SC_PAGE_SIZE; make sure @@ -176,6 +201,9 @@ def test_virtual_memory(self): def test_swap_memory(self): mem = psutil.swap_memory() + self.assertEqual( + mem._fields, ('total', 'used', 'free', 'percent', 'sin', 'sout')) + assert mem.total >= 0, mem assert mem.used >= 0, mem if mem.total > 0: @@ -196,8 +224,6 @@ def test_pid_exists(self): self.assertFalse(psutil.pid_exists(sproc.pid)) self.assertFalse(psutil.pid_exists(-1)) self.assertEqual(psutil.pid_exists(0), 0 in psutil.pids()) - # pid 0 - psutil.pid_exists(0) == 0 in psutil.pids() def test_pid_exists_2(self): reap_children() @@ -245,6 +271,18 @@ def test_cpu_count(self): self.assertGreaterEqual(physical, 1) self.assertGreaterEqual(logical, physical) + def test_cpu_count_none(self): + # https://github.com/giampaolo/psutil/issues/1085 + for val in (-1, 0, None): + with mock.patch('psutil._psplatform.cpu_count_logical', + return_value=val) as m: + self.assertIsNone(psutil.cpu_count()) + assert m.called + with mock.patch('psutil._psplatform.cpu_count_physical', + return_value=val) as m: + self.assertIsNone(psutil.cpu_count(logical=False)) + assert m.called + def test_cpu_times(self): # Check type, value >= 0, str(). total = 0 @@ -402,10 +440,10 @@ def test_per_cpu_times_percent_negative(self): for percent in cpu: self._test_cpu_percent(percent, None, None) - @unittest.skipIf(POSIX and not hasattr(os, 'statvfs'), - "os.statvfs() not available") def test_disk_usage(self): usage = psutil.disk_usage(os.getcwd()) + self.assertEqual(usage._fields, ('total', 'used', 'free', 'percent')) + assert usage.total > 0, usage assert usage.used > 0, usage assert usage.free > 0, usage @@ -425,26 +463,19 @@ def test_disk_usage(self): # if path does not exist OSError ENOENT is expected across # all platforms fname = tempfile.mktemp() - try: + with self.assertRaises(OSError) as exc: psutil.disk_usage(fname) - except OSError as err: - if err.args[0] != errno.ENOENT: - raise - else: - self.fail("OSError not raised") + self.assertEqual(exc.exception.errno, errno.ENOENT) - @unittest.skipIf(POSIX and not hasattr(os, 'statvfs'), - "os.statvfs() not available") def test_disk_usage_unicode(self): - # see: https://github.com/giampaolo/psutil/issues/416 - safe_rmpath(TESTFN_UNICODE) - self.addCleanup(safe_rmpath, TESTFN_UNICODE) - os.mkdir(TESTFN_UNICODE) - psutil.disk_usage(TESTFN_UNICODE) - - @unittest.skipIf(POSIX and not hasattr(os, 'statvfs'), - "os.statvfs() not available") - @unittest.skipIf(LINUX and TRAVIS, "unknown failure on travis") + # See: https://github.com/giampaolo/psutil/issues/416 + if ASCII_FS: + with self.assertRaises(UnicodeEncodeError): + psutil.disk_usage(TESTFN_UNICODE) + + def test_disk_usage_bytes(self): + psutil.disk_usage(b'.') + def test_disk_partitions(self): # all = False ls = psutil.disk_partitions(all=False) @@ -453,6 +484,10 @@ def test_disk_partitions(self): # AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7,... != [0] self.assertTrue(ls, msg=ls) for disk in ls: + self.assertIsInstance(disk.device, str) + self.assertIsInstance(disk.mountpoint, str) + self.assertIsInstance(disk.fstype, str) + self.assertIsInstance(disk.opts, str) if WINDOWS and 'cdrom' in disk.opts: continue if not POSIX: @@ -461,13 +496,12 @@ def test_disk_partitions(self): # we cannot make any assumption about this, see: # http://goo.gl/p9c43 disk.device - if SUNOS: + if SUNOS or TRAVIS: # on solaris apparently mount points can also be files assert os.path.exists(disk.mountpoint), disk else: assert os.path.isdir(disk.mountpoint), disk assert disk.fstype, disk - self.assertIsInstance(disk.opts, str) # all = True ls = psutil.disk_partitions(all=True) @@ -484,7 +518,7 @@ def test_disk_partitions(self): if err.errno not in (errno.EPERM, errno.EACCES): raise else: - if SUNOS: + if SUNOS or TRAVIS: # on solaris apparently mount points can also be files assert os.path.exists(disk.mountpoint), disk else: @@ -504,23 +538,6 @@ def find_mount_point(path): self.assertIn(mount, mounts) psutil.disk_usage(mount) - @skip_on_access_denied() - def test_net_connections(self): - def check(cons, families, types_): - for conn in cons: - self.assertIn(conn.family, families, msg=conn) - if conn.family != getattr(socket, 'AF_UNIX', object()): - self.assertIn(conn.type, types_, msg=conn) - - from psutil._common import conn_tmap - for kind, groups in conn_tmap.items(): - if SUNOS and kind == 'unix': - continue - families, types_ = groups - cons = psutil.net_connections(kind) - self.assertEqual(len(cons), len(set(cons))) - check(cons, families, types_) - def test_net_io_counters(self): def check_ntuple(nt): self.assertEqual(nt[0], nt.bytes_sent) @@ -546,8 +563,18 @@ def check_ntuple(nt): self.assertNotEqual(ret, []) for key in ret: self.assertTrue(key) + self.assertIsInstance(key, str) check_ntuple(ret[key]) + def test_net_io_counters_no_nics(self): + # Emulate a case where no NICs are installed, see: + # https://github.com/giampaolo/psutil/issues/1062 + with mock.patch('psutil._psplatform.net_io_counters', + return_value={}) as m: + self.assertIsNone(psutil.net_io_counters(pernic=False)) + self.assertEqual(psutil.net_io_counters(pernic=True), {}) + assert m.called + def test_net_if_addrs(self): nics = psutil.net_if_addrs() assert nics, nics @@ -559,8 +586,9 @@ def test_net_if_addrs(self): # self.assertEqual(sorted(nics.keys()), # sorted(psutil.net_io_counters(pernic=True).keys())) - families = set([socket.AF_INET, AF_INET6, psutil.AF_LINK]) + families = set([socket.AF_INET, socket.AF_INET6, psutil.AF_LINK]) for nic, addrs in nics.items(): + self.assertIsInstance(nic, str) self.assertEqual(len(set(addrs)), len(addrs)) for addr in addrs: self.assertIsInstance(addr.family, int) @@ -591,7 +619,7 @@ def test_net_if_addrs(self): # TODO: skip AF_INET6 for now because I get: # AddressValueError: Only hex digits permitted in # u'c6f3%lxcbr0' in u'fe80::c8e0:fff:fe54:c6f3%lxcbr0' - if addr.family != AF_INET6: + if addr.family != socket.AF_INET6: check_net_address(ip, addr.family) # broadcast and ptp addresses are mutually exclusive if addr.broadcast: @@ -631,7 +659,8 @@ def test_net_if_stats(self): all_duplexes = (psutil.NIC_DUPLEX_FULL, psutil.NIC_DUPLEX_HALF, psutil.NIC_DUPLEX_UNKNOWN) - for nic, stats in nics.items(): + for name, stats in nics.items(): + self.assertIsInstance(name, str) isup, duplex, speed, mtu = stats self.assertIsInstance(isup, bool) self.assertIn(duplex, all_duplexes) @@ -641,7 +670,8 @@ def test_net_if_stats(self): @unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'), '/proc/diskstats not available on this linux version') - @unittest.skipIf(APPVEYOR, "unreliable on APPVEYOR") # no visible disks + @unittest.skipIf(APPVEYOR and psutil.disk_io_counters() is None, + "unreliable on APPVEYOR") # no visible disks def test_disk_io_counters(self): def check_ntuple(nt): self.assertEqual(nt[0], nt.read_count) @@ -661,6 +691,7 @@ def check_ntuple(nt): assert getattr(nt, name) >= 0, nt ret = psutil.disk_io_counters(perdisk=False) + assert ret is not None, "no disks on this system?" check_ntuple(ret) ret = psutil.disk_io_counters(perdisk=True) # make sure there are no duplicates @@ -675,6 +706,15 @@ def check_ntuple(nt): key = key[:-1] self.assertNotIn(key, ret.keys()) + def test_disk_io_counters_no_disks(self): + # Emulate a case where no disks are installed, see: + # https://github.com/giampaolo/psutil/issues/1062 + with mock.patch('psutil._psplatform.disk_io_counters', + return_value={}) as m: + self.assertIsNone(psutil.disk_io_counters(perdisk=False)) + self.assertEqual(psutil.disk_io_counters(perdisk=True), {}) + assert m.called + # can't find users on APPVEYOR or TRAVIS @unittest.skipIf(APPVEYOR or TRAVIS and not psutil.users(), "unreliable on APPVEYOR or TRAVIS") @@ -683,20 +723,53 @@ def test_users(self): self.assertNotEqual(users, []) for user in users: assert user.name, user + self.assertIsInstance(user.name, str) + self.assertIsInstance(user.terminal, (str, type(None))) + if user.host is not None: + self.assertIsInstance(user.host, (str, type(None))) user.terminal user.host assert user.started > 0.0, user datetime.datetime.fromtimestamp(user.started) + if WINDOWS or OPENBSD: + self.assertIsNone(user.pid) + else: + psutil.Process(user.pid) def test_cpu_stats(self): # Tested more extensively in per-platform test modules. infos = psutil.cpu_stats() + self.assertEqual( + infos._fields, + ('ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls')) for name in infos._fields: value = getattr(infos, name) self.assertGreaterEqual(value, 0) - if name in ('ctx_switches', 'interrupts'): + # on AIX, ctx_switches is always 0 + if not AIX and name in ('ctx_switches', 'interrupts'): self.assertGreater(value, 0) + @unittest.skipIf(not HAS_CPU_FREQ, "not suported") + def test_cpu_freq(self): + def check_ls(ls): + for nt in ls: + self.assertEqual(nt._fields, ('current', 'min', 'max')) + self.assertLessEqual(nt.current, nt.max) + for name in nt._fields: + value = getattr(nt, name) + self.assertIsInstance(value, (int, long, float)) + self.assertGreaterEqual(value, 0) + + ls = psutil.cpu_freq(percpu=True) + if TRAVIS and not ls: + return + + assert ls, ls + check_ls([psutil.cpu_freq(percpu=False)]) + + if LINUX: + self.assertEqual(len(ls), psutil.cpu_count()) + def test_os_constants(self): names = ["POSIX", "WINDOWS", "LINUX", "OSX", "FREEBSD", "OPENBSD", "NETBSD", "BSD", "SUNOS"] @@ -734,6 +807,56 @@ def test_os_constants(self): for name in names: self.assertIs(getattr(psutil, name), False, msg=name) + @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") + def test_sensors_temperatures(self): + temps = psutil.sensors_temperatures() + for name, entries in temps.items(): + self.assertIsInstance(name, str) + for entry in entries: + self.assertIsInstance(entry.label, str) + if entry.current is not None: + self.assertGreaterEqual(entry.current, 0) + if entry.high is not None: + self.assertGreaterEqual(entry.high, 0) + if entry.critical is not None: + self.assertGreaterEqual(entry.critical, 0) + + @unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported") + def test_sensors_temperatures_fahreneit(self): + d = {'coretemp': [('label', 50.0, 60.0, 70.0)]} + with mock.patch("psutil._psplatform.sensors_temperatures", + return_value=d) as m: + temps = psutil.sensors_temperatures( + fahrenheit=True)['coretemp'][0] + assert m.called + self.assertEqual(temps.current, 122.0) + self.assertEqual(temps.high, 140.0) + self.assertEqual(temps.critical, 158.0) + + @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported") + @unittest.skipIf(not HAS_BATTERY, "no battery") + def test_sensors_battery(self): + ret = psutil.sensors_battery() + self.assertGreaterEqual(ret.percent, 0) + self.assertLessEqual(ret.percent, 100) + if ret.secsleft not in (psutil.POWER_TIME_UNKNOWN, + psutil.POWER_TIME_UNLIMITED): + self.assertGreaterEqual(ret.secsleft, 0) + else: + if ret.secsleft == psutil.POWER_TIME_UNLIMITED: + self.assertTrue(ret.power_plugged) + self.assertIsInstance(ret.power_plugged, bool) + + @unittest.skipIf(not HAS_SENSORS_FANS, "not supported") + def test_sensors_fans(self): + fans = psutil.sensors_fans() + for name, entries in fans.items(): + self.assertIsInstance(name, str) + for entry in entries: + self.assertIsInstance(entry.label, str) + self.assertIsInstance(entry.current, (int, long)) + self.assertGreaterEqual(entry.current, 0) + if __name__ == '__main__': run_test_module_by_name(__file__) diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py new file mode 100755 index 000000000..c2a2f8479 --- /dev/null +++ b/psutil/tests/test_unicode.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Notes about unicode handling in psutil +====================================== + +In psutil these are the APIs returning or dealing with a string +('not tested' means they are not tested to deal with non-ASCII strings): + +* Process.cmdline() +* Process.connections('unix') +* Process.cwd() +* Process.environ() +* Process.exe() +* Process.memory_maps() +* Process.name() +* Process.open_files() +* Process.username() (not tested) + +* disk_io_counters() (not tested) +* disk_partitions() (not tested) +* disk_usage(str) +* net_connections('unix') +* net_if_addrs() (not tested) +* net_if_stats() (not tested) +* net_io_counters() (not tested) +* sensors_fans() (not tested) +* sensors_temperatures() (not tested) +* users() (not tested) + +* WindowsService.binpath() (not tested) +* WindowsService.description() (not tested) +* WindowsService.display_name() (not tested) +* WindowsService.name() (not tested) +* WindowsService.status() (not tested) +* WindowsService.username() (not tested) + +In here we create a unicode path with a funky non-ASCII name and (where +possible) make psutil return it back (e.g. on name(), exe(), open_files(), +etc.) and make sure that: + +* psutil never crashes with UnicodeDecodeError +* the returned path matches + +For a detailed explanation of how psutil handles unicode see: +- https://github.com/giampaolo/psutil/issues/1040 +- http://psutil.readthedocs.io/#unicode +""" + +import os +import traceback +import warnings +from contextlib import closing + +from psutil import BSD +from psutil import OPENBSD +from psutil import OSX +from psutil import POSIX +from psutil import WINDOWS +from psutil._compat import PY3 +from psutil._compat import u +from psutil.tests import APPVEYOR +from psutil.tests import ASCII_FS +from psutil.tests import bind_unix_socket +from psutil.tests import chdir +from psutil.tests import copyload_shared_lib +from psutil.tests import create_exe +from psutil.tests import get_test_subprocess +from psutil.tests import HAS_CONNECTIONS_UNIX +from psutil.tests import HAS_ENVIRON +from psutil.tests import HAS_MEMORY_MAPS +from psutil.tests import mock +from psutil.tests import reap_children +from psutil.tests import run_test_module_by_name +from psutil.tests import safe_mkdir +from psutil.tests import safe_rmpath as _safe_rmpath +from psutil.tests import skip_on_access_denied +from psutil.tests import TESTFILE_PREFIX +from psutil.tests import TESTFN +from psutil.tests import TESTFN_UNICODE +from psutil.tests import TRAVIS +from psutil.tests import unittest +from psutil.tests import unix_socket_path +import psutil +import psutil.tests + + +def safe_rmpath(path): + if APPVEYOR: + # TODO - this is quite random and I'm not sure why it happens, + # nor I can reproduce it locally: + # https://ci.appveyor.com/project/giampaolo/psutil/build/job/ + # jiq2cgd6stsbtn60 + # safe_rmpath() happens after reap_children() so this is weird + # Perhaps wait_procs() on Windows is broken? Maybe because + # of STILL_ACTIVE? + # https://github.com/giampaolo/psutil/blob/ + # 68c7a70728a31d8b8b58f4be6c4c0baa2f449eda/psutil/arch/ + # windows/process_info.c#L146 + try: + return _safe_rmpath(path) + except WindowsError: + traceback.print_exc() + else: + return _safe_rmpath(path) + + +def subprocess_supports_unicode(name): + """Return True if both the fs and the subprocess module can + deal with a unicode file name. + """ + if PY3: + return True + try: + safe_rmpath(name) + create_exe(name) + get_test_subprocess(cmd=[name]) + except UnicodeEncodeError: + return False + else: + return True + finally: + reap_children() + + +# An invalid unicode string. +if PY3: + INVALID_NAME = (TESTFN.encode('utf8') + b"f\xc0\x80").decode( + 'utf8', 'surrogateescape') +else: + INVALID_NAME = TESTFN + "f\xc0\x80" + + +# =================================================================== +# FS APIs +# =================================================================== + + +class _BaseFSAPIsTests(object): + funky_name = None + + @classmethod + def setUpClass(cls): + safe_rmpath(cls.funky_name) + create_exe(cls.funky_name) + + @classmethod + def tearDownClass(cls): + reap_children() + safe_rmpath(cls.funky_name) + + def tearDown(self): + reap_children() + + def expect_exact_path_match(self): + raise NotImplementedError("must be implemented in subclass") + + def test_proc_exe(self): + subp = get_test_subprocess(cmd=[self.funky_name]) + p = psutil.Process(subp.pid) + exe = p.exe() + self.assertIsInstance(exe, str) + if self.expect_exact_path_match(): + self.assertEqual(exe, self.funky_name) + + def test_proc_name(self): + subp = get_test_subprocess(cmd=[self.funky_name]) + if WINDOWS: + # On Windows name() is determined from exe() first, because + # it's faster; we want to overcome the internal optimization + # and test name() instead of exe(). + with mock.patch("psutil._psplatform.cext.proc_exe", + side_effect=psutil.AccessDenied(os.getpid())) as m: + name = psutil.Process(subp.pid).name() + assert m.called + else: + name = psutil.Process(subp.pid).name() + self.assertIsInstance(name, str) + if self.expect_exact_path_match(): + self.assertEqual(name, os.path.basename(self.funky_name)) + + def test_proc_cmdline(self): + subp = get_test_subprocess(cmd=[self.funky_name]) + p = psutil.Process(subp.pid) + cmdline = p.cmdline() + for part in cmdline: + self.assertIsInstance(part, str) + if self.expect_exact_path_match(): + self.assertEqual(cmdline, [self.funky_name]) + + def test_proc_cwd(self): + dname = self.funky_name + "2" + self.addCleanup(safe_rmpath, dname) + safe_mkdir(dname) + with chdir(dname): + p = psutil.Process() + cwd = p.cwd() + self.assertIsInstance(p.cwd(), str) + if self.expect_exact_path_match(): + self.assertEqual(cwd, dname) + + def test_proc_open_files(self): + p = psutil.Process() + start = set(p.open_files()) + with open(self.funky_name, 'rb'): + new = set(p.open_files()) + path = (new - start).pop().path + self.assertIsInstance(path, str) + if BSD and not path: + # XXX - see https://github.com/giampaolo/psutil/issues/595 + return self.skipTest("open_files on BSD is broken") + if self.expect_exact_path_match(): + self.assertEqual(os.path.normcase(path), + os.path.normcase(self.funky_name)) + + @unittest.skipIf(not POSIX, "POSIX only") + def test_proc_connections(self): + suffix = os.path.basename(self.funky_name) + with unix_socket_path(suffix=suffix) as name: + try: + sock = bind_unix_socket(name) + except UnicodeEncodeError: + if PY3: + raise + else: + raise unittest.SkipTest("not supported") + with closing(sock): + conn = psutil.Process().connections('unix')[0] + self.assertIsInstance(conn.laddr, str) + # AF_UNIX addr not set on OpenBSD + if not OPENBSD: + self.assertEqual(conn.laddr, name) + + @unittest.skipIf(not POSIX, "POSIX only") + @unittest.skipIf(not HAS_CONNECTIONS_UNIX, "can't list UNIX sockets") + @skip_on_access_denied() + def test_net_connections(self): + def find_sock(cons): + for conn in cons: + if os.path.basename(conn.laddr).startswith(TESTFILE_PREFIX): + return conn + raise ValueError("connection not found") + + suffix = os.path.basename(self.funky_name) + with unix_socket_path(suffix=suffix) as name: + try: + sock = bind_unix_socket(name) + except UnicodeEncodeError: + if PY3: + raise + else: + raise unittest.SkipTest("not supported") + with closing(sock): + cons = psutil.net_connections(kind='unix') + # AF_UNIX addr not set on OpenBSD + if not OPENBSD: + conn = find_sock(cons) + self.assertIsInstance(conn.laddr, str) + self.assertEqual(conn.laddr, name) + + def test_disk_usage(self): + dname = self.funky_name + "2" + self.addCleanup(safe_rmpath, dname) + safe_mkdir(dname) + psutil.disk_usage(dname) + + @unittest.skipIf(not HAS_MEMORY_MAPS, "not supported") + @unittest.skipIf(not PY3, "ctypes does not support unicode on PY2") + def test_memory_maps(self): + # XXX: on Python 2, using ctypes.CDLL with a unicode path + # opens a message box which blocks the test run. + with copyload_shared_lib(dst_prefix=self.funky_name) as funky_path: + def normpath(p): + return os.path.realpath(os.path.normcase(p)) + libpaths = [normpath(x.path) + for x in psutil.Process().memory_maps()] + # ...just to have a clearer msg in case of failure + libpaths = [x for x in libpaths if TESTFILE_PREFIX in x] + self.assertIn(normpath(funky_path), libpaths) + for path in libpaths: + self.assertIsInstance(path, str) + + +@unittest.skipIf(OSX and TRAVIS, "unreliable on TRAVIS") # TODO +@unittest.skipIf(ASCII_FS, "ASCII fs") +@unittest.skipIf(not subprocess_supports_unicode(TESTFN_UNICODE), + "subprocess can't deal with unicode") +class TestFSAPIs(_BaseFSAPIsTests, unittest.TestCase): + """Test FS APIs with a funky, valid, UTF8 path name.""" + funky_name = TESTFN_UNICODE + + @classmethod + def expect_exact_path_match(cls): + # Do not expect psutil to correctly handle unicode paths on + # Python 2 if os.listdir() is not able either. + if PY3: + return True + else: + here = '.' if isinstance(cls.funky_name, str) else u('.') + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + return cls.funky_name in os.listdir(here) + + +@unittest.skipIf(OSX and TRAVIS, "unreliable on TRAVIS") # TODO +@unittest.skipIf(not subprocess_supports_unicode(INVALID_NAME), + "subprocess can't deal with invalid unicode") +class TestFSAPIsWithInvalidPath(_BaseFSAPIsTests, unittest.TestCase): + """Test FS APIs with a funky, invalid path name.""" + funky_name = INVALID_NAME + + @classmethod + def expect_exact_path_match(cls): + # Invalid unicode names are supposed to work on Python 2. + return True + + +@unittest.skipIf(not WINDOWS, "WINDOWS only") +class TestWinProcessName(unittest.TestCase): + + def test_name_type(self): + # On Windows name() is determined from exe() first, because + # it's faster; we want to overcome the internal optimization + # and test name() instead of exe(). + with mock.patch("psutil._psplatform.cext.proc_exe", + side_effect=psutil.AccessDenied(os.getpid())) as m: + self.assertIsInstance(psutil.Process().name(), str) + assert m.called + + +# =================================================================== +# Non fs APIs +# =================================================================== + + +class TestNonFSAPIS(unittest.TestCase): + """Unicode tests for non fs-related APIs.""" + + def tearDown(self): + reap_children() + + @unittest.skipIf(not HAS_ENVIRON, "not supported") + def test_proc_environ(self): + # Note: differently from others, this test does not deal + # with fs paths. On Python 2 subprocess module is broken as + # it's not able to handle with non-ASCII env vars, so + # we use "è", which is part of the extended ASCII table + # (unicode point <= 255). + env = os.environ.copy() + funky_str = TESTFN_UNICODE if PY3 else 'è' + env['FUNNY_ARG'] = funky_str + sproc = get_test_subprocess(env=env) + p = psutil.Process(sproc.pid) + env = p.environ() + for k, v in env.items(): + self.assertIsInstance(k, str) + self.assertIsInstance(v, str) + self.assertEqual(env['FUNNY_ARG'], funky_str) + + +if __name__ == '__main__': + run_test_module_by_name(__file__) diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py index 802242b55..e4a719ea4 100755 --- a/psutil/tests/test_windows.py +++ b/psutil/tests/test_windows.py @@ -7,36 +7,42 @@ """Windows specific tests.""" +import datetime import errno import glob import os import platform +import re import signal import subprocess import sys import time - -try: - import win32api # requires "pip install pypiwin32" / "make setup-dev-env" - import win32con - import wmi # requires "pip install wmi" / "make setup-dev-env" -except ImportError: - if os.name == 'nt': - raise +import warnings import psutil from psutil import WINDOWS -from psutil._compat import basestring from psutil._compat import callable -from psutil._compat import PY3 from psutil.tests import APPVEYOR from psutil.tests import get_test_subprocess +from psutil.tests import HAS_BATTERY from psutil.tests import mock 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 sh from psutil.tests import unittest +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + try: + import win32api # requires "pip install pypiwin32" + import win32con + import win32process + import wmi # requires "pip install wmi" / "make setup-dev-env" + except ImportError: + if os.name == 'nt': + raise + cext = psutil._psplatform.cext @@ -63,14 +69,11 @@ def wrapper(self, *args, **kwargs): # =================================================================== -@unittest.skipUnless(WINDOWS, "WINDOWS only") +@unittest.skipIf(not WINDOWS, "WINDOWS only") class TestSystemAPIs(unittest.TestCase): def test_nic_names(self): - p = subprocess.Popen(['ipconfig', '/all'], stdout=subprocess.PIPE) - out = p.communicate()[0] - if PY3: - out = str(out, sys.stdout.encoding or sys.getfilesystemencoding()) + out = sh('ipconfig /all') nics = psutil.net_io_counters(pernic=True).keys() for nic in nics: if "pseudo-interface" in nic.replace(' ', '-').lower(): @@ -79,12 +82,23 @@ def test_nic_names(self): self.fail( "%r nic wasn't found in 'ipconfig /all' output" % nic) - @unittest.skipUnless('NUMBER_OF_PROCESSORS' in os.environ, - 'NUMBER_OF_PROCESSORS env var is not available') + @unittest.skipIf('NUMBER_OF_PROCESSORS' not in os.environ, + 'NUMBER_OF_PROCESSORS env var is not available') def test_cpu_count(self): num_cpus = int(os.environ['NUMBER_OF_PROCESSORS']) self.assertEqual(num_cpus, psutil.cpu_count()) + def test_cpu_count_2(self): + sys_value = win32api.GetSystemInfo()[5] + psutil_value = psutil.cpu_count() + self.assertEqual(sys_value, psutil_value) + + def test_cpu_freq(self): + w = wmi.WMI() + proc = w.Win32_Processor()[0] + self.assertEqual(proc.CurrentClockSpeed, psutil.cpu_freq().current) + self.assertEqual(proc.MaxClockSpeed, psutil.cpu_freq().max) + def test_total_phymem(self): w = wmi.WMI().Win32_ComputerSystem()[0] self.assertEqual(int(w.TotalPhysicalMemory), @@ -141,6 +155,24 @@ def test_disks(self): else: self.fail("can't find partition %s" % repr(ps_part)) + def test_disk_usage(self): + for disk in psutil.disk_partitions(): + sys_value = win32api.GetDiskFreeSpaceEx(disk.mountpoint) + psutil_value = psutil.disk_usage(disk.mountpoint) + self.assertAlmostEqual(sys_value[0], psutil_value.free, + delta=1024 * 1024) + self.assertAlmostEqual(sys_value[1], psutil_value.total, + delta=1024 * 1024) + self.assertEqual(psutil_value.used, + psutil_value.total - psutil_value.free) + + def test_disk_partitions(self): + sys_value = [ + x + '\\' for x in win32api.GetLogicalDriveStrings().split("\\\x00") + if x and not x.startswith('A:')] + psutil_value = [x.mountpoint for x in psutil.disk_partitions(all=True)] + self.assertEqual(sys_value, psutil_value) + def test_net_if_stats(self): ps_names = set(cext.net_if_stats()) wmi_adapters = wmi.WMI().Win32_NetworkAdapter() @@ -151,13 +183,95 @@ def test_net_if_stats(self): self.assertTrue(ps_names & wmi_names, "no common entries in %s, %s" % (ps_names, wmi_names)) + def test_boot_time(self): + wmi_os = wmi.WMI().Win32_OperatingSystem() + wmi_btime_str = wmi_os[0].LastBootUpTime.split('.')[0] + wmi_btime_dt = datetime.datetime.strptime( + wmi_btime_str, "%Y%m%d%H%M%S") + psutil_dt = datetime.datetime.fromtimestamp(psutil.boot_time()) + diff = abs((wmi_btime_dt - psutil_dt).total_seconds()) + # Wmic time is 2-3 secs lower for some reason; that's OK. + self.assertLessEqual(diff, 3) + + def test_boot_time_fluctuation(self): + # https://github.com/giampaolo/psutil/issues/1007 + with mock.patch('psutil._pswindows.cext.boot_time', return_value=5): + self.assertEqual(psutil.boot_time(), 5) + with mock.patch('psutil._pswindows.cext.boot_time', return_value=4): + self.assertEqual(psutil.boot_time(), 5) + with mock.patch('psutil._pswindows.cext.boot_time', return_value=6): + self.assertEqual(psutil.boot_time(), 5) + with mock.patch('psutil._pswindows.cext.boot_time', return_value=333): + self.assertEqual(psutil.boot_time(), 333) + + +# =================================================================== +# sensors_battery() +# =================================================================== + + +@unittest.skipIf(not WINDOWS, "WINDOWS only") +class TestSensorsBattery(unittest.TestCase): + + def test_has_battery(self): + if win32api.GetPwrCapabilities()['SystemBatteriesPresent']: + self.assertIsNotNone(psutil.sensors_battery()) + else: + self.assertIsNone(psutil.sensors_battery()) + + @unittest.skipIf(not HAS_BATTERY, "no battery") + def test_percent(self): + w = wmi.WMI() + battery_wmi = w.query('select * from Win32_Battery')[0] + battery_psutil = psutil.sensors_battery() + self.assertAlmostEqual( + battery_psutil.percent, battery_wmi.EstimatedChargeRemaining, + delta=1) + + @unittest.skipIf(not HAS_BATTERY, "no battery") + def test_power_plugged(self): + w = wmi.WMI() + battery_wmi = w.query('select * from Win32_Battery')[0] + battery_psutil = psutil.sensors_battery() + # Status codes: + # https://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx + self.assertEqual(battery_psutil.power_plugged, + battery_wmi.BatteryStatus == 2) + + def test_emulate_no_battery(self): + with mock.patch("psutil._pswindows.cext.sensors_battery", + return_value=(0, 128, 0, 0)) as m: + self.assertIsNone(psutil.sensors_battery()) + assert m.called + + def test_emulate_power_connected(self): + with mock.patch("psutil._pswindows.cext.sensors_battery", + return_value=(1, 0, 0, 0)) as m: + self.assertEqual(psutil.sensors_battery().secsleft, + psutil.POWER_TIME_UNLIMITED) + assert m.called + + def test_emulate_power_charging(self): + with mock.patch("psutil._pswindows.cext.sensors_battery", + return_value=(0, 8, 0, 0)) as m: + self.assertEqual(psutil.sensors_battery().secsleft, + psutil.POWER_TIME_UNLIMITED) + assert m.called + + def test_emulate_secs_left_unknown(self): + with mock.patch("psutil._pswindows.cext.sensors_battery", + return_value=(0, 0, 0, -1)) as m: + self.assertEqual(psutil.sensors_battery().secsleft, + psutil.POWER_TIME_UNKNOWN) + assert m.called + # =================================================================== # Process APIs # =================================================================== -@unittest.skipUnless(WINDOWS, "WINDOWS only") +@unittest.skipIf(not WINDOWS, "WINDOWS only") class TestProcess(unittest.TestCase): @classmethod @@ -200,7 +314,7 @@ def test_exe(self): except psutil.Error: pass - def test_num_handles(self): + def test_num_handles_increment(self): p = psutil.Process(os.getpid()) before = p.num_handles() handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, @@ -227,7 +341,7 @@ def call(p, attr): if name.startswith('_') \ or name in ('terminate', 'kill', 'suspend', 'resume', 'nice', 'send_signal', 'wait', 'children', - 'as_dict'): + 'as_dict', 'memory_info_ex'): continue else: try: @@ -255,8 +369,8 @@ def test_name_always_available(self): except psutil.NoSuchProcess: pass - @unittest.skipUnless(sys.version_info >= (2, 7), - "CTRL_* signals not supported") + @unittest.skipIf(not sys.version_info >= (2, 7), + "CTRL_* signals not supported") def test_ctrl_signals(self): p = psutil.Process(get_test_subprocess().pid) p.send_signal(signal.CTRL_C_EVENT) @@ -278,8 +392,123 @@ def test_compare_name_exe(self): else: self.assertEqual(a, b) + def test_username(self): + self.assertEqual(psutil.Process().username(), + win32api.GetUserNameEx(win32con.NameSamCompatible)) + + def test_cmdline(self): + sys_value = re.sub(' +', ' ', win32api.GetCommandLine()).strip() + psutil_value = ' '.join(psutil.Process().cmdline()) + self.assertEqual(sys_value, psutil_value) + + # XXX - occasional failures + + # def test_cpu_times(self): + # handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, + # win32con.FALSE, os.getpid()) + # self.addCleanup(win32api.CloseHandle, handle) + # sys_value = win32process.GetProcessTimes(handle) + # psutil_value = psutil.Process().cpu_times() + # self.assertAlmostEqual( + # psutil_value.user, sys_value['UserTime'] / 10000000.0, + # delta=0.2) + # self.assertAlmostEqual( + # psutil_value.user, sys_value['KernelTime'] / 10000000.0, + # delta=0.2) + + def test_nice(self): + handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, + win32con.FALSE, os.getpid()) + self.addCleanup(win32api.CloseHandle, handle) + sys_value = win32process.GetPriorityClass(handle) + psutil_value = psutil.Process().nice() + self.assertEqual(psutil_value, sys_value) + + def test_memory_info(self): + handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, + win32con.FALSE, self.pid) + self.addCleanup(win32api.CloseHandle, handle) + sys_value = win32process.GetProcessMemoryInfo(handle) + psutil_value = psutil.Process(self.pid).memory_info() + self.assertEqual( + sys_value['PeakWorkingSetSize'], psutil_value.peak_wset) + self.assertEqual( + sys_value['WorkingSetSize'], psutil_value.wset) + self.assertEqual( + sys_value['QuotaPeakPagedPoolUsage'], psutil_value.peak_paged_pool) + self.assertEqual( + sys_value['QuotaPagedPoolUsage'], psutil_value.paged_pool) + self.assertEqual( + sys_value['QuotaPeakNonPagedPoolUsage'], + psutil_value.peak_nonpaged_pool) + self.assertEqual( + sys_value['QuotaNonPagedPoolUsage'], psutil_value.nonpaged_pool) + self.assertEqual( + sys_value['PagefileUsage'], psutil_value.pagefile) + self.assertEqual( + sys_value['PeakPagefileUsage'], psutil_value.peak_pagefile) + + self.assertEqual(psutil_value.rss, psutil_value.wset) + self.assertEqual(psutil_value.vms, psutil_value.pagefile) + + def test_wait(self): + handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, + win32con.FALSE, self.pid) + self.addCleanup(win32api.CloseHandle, handle) + p = psutil.Process(self.pid) + p.terminate() + psutil_value = p.wait() + sys_value = win32process.GetExitCodeProcess(handle) + self.assertEqual(psutil_value, sys_value) -@unittest.skipUnless(WINDOWS, "WINDOWS only") + def test_cpu_affinity(self): + def from_bitmask(x): + return [i for i in range(64) if (1 << i) & x] + + handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, + win32con.FALSE, self.pid) + self.addCleanup(win32api.CloseHandle, handle) + sys_value = from_bitmask( + win32process.GetProcessAffinityMask(handle)[0]) + psutil_value = psutil.Process(self.pid).cpu_affinity() + self.assertEqual(psutil_value, sys_value) + + def test_io_counters(self): + handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, + win32con.FALSE, os.getpid()) + self.addCleanup(win32api.CloseHandle, handle) + sys_value = win32process.GetProcessIoCounters(handle) + psutil_value = psutil.Process().io_counters() + self.assertEqual( + psutil_value.read_count, sys_value['ReadOperationCount']) + self.assertEqual( + psutil_value.write_count, sys_value['WriteOperationCount']) + self.assertEqual( + psutil_value.read_bytes, sys_value['ReadTransferCount']) + self.assertEqual( + psutil_value.write_bytes, sys_value['WriteTransferCount']) + self.assertEqual( + psutil_value.other_count, sys_value['OtherOperationCount']) + self.assertEqual( + psutil_value.other_bytes, sys_value['OtherTransferCount']) + + def test_num_handles(self): + import ctypes + import ctypes.wintypes + PROCESS_QUERY_INFORMATION = 0x400 + handle = ctypes.windll.kernel32.OpenProcess( + PROCESS_QUERY_INFORMATION, 0, os.getpid()) + self.addCleanup(ctypes.windll.kernel32.CloseHandle, handle) + hndcnt = ctypes.wintypes.DWORD() + ctypes.windll.kernel32.GetProcessHandleCount( + handle, ctypes.byref(hndcnt)) + sys_value = hndcnt.value + psutil_value = psutil.Process().num_handles() + ctypes.windll.kernel32.CloseHandle(handle) + self.assertEqual(psutil_value, sys_value + 1) + + +@unittest.skipIf(not WINDOWS, "WINDOWS only") class TestProcessWMI(unittest.TestCase): """Compare Process API results with WMI.""" @@ -345,7 +574,7 @@ def test_create_time(self): self.assertEqual(wmic_create, psutil_create) -@unittest.skipUnless(WINDOWS, "WINDOWS only") +@unittest.skipIf(not WINDOWS, "WINDOWS only") class TestDualProcessImplementation(unittest.TestCase): """ Certain APIs on Windows have 2 internal implementations, one @@ -407,14 +636,10 @@ def test_cpu_times(self): def test_io_counters(self): io_counters_1 = psutil.Process(self.pid).io_counters() - print("") - print(io_counters_1) with mock.patch("psutil._psplatform.cext.proc_io_counters", side_effect=OSError(errno.EPERM, "msg")) as fun: io_counters_2 = psutil.Process(self.pid).io_counters() for i in range(len(io_counters_1)): - self.assertGreaterEqual(io_counters_1[i], 0) - self.assertGreaterEqual(io_counters_2[i], 0) self.assertAlmostEqual( io_counters_1[i], io_counters_2[i], delta=5) assert fun.called @@ -423,11 +648,12 @@ def test_num_handles(self): num_handles = psutil.Process(self.pid).num_handles() with mock.patch("psutil._psplatform.cext.proc_num_handles", side_effect=OSError(errno.EPERM, "msg")) as fun: - psutil.Process(self.pid).num_handles() == num_handles + self.assertEqual(psutil.Process(self.pid).num_handles(), + num_handles) assert fun.called -@unittest.skipUnless(WINDOWS, "WINDOWS only") +@unittest.skipIf(not WINDOWS, "WINDOWS only") class RemoteProcessTestCase(unittest.TestCase): """Certain functions require calling ReadProcessMemory. This trivially works when called on the current process. @@ -522,7 +748,7 @@ def test_environ_64(self): # =================================================================== -@unittest.skipUnless(WINDOWS, "WINDOWS only") +@unittest.skipIf(not WINDOWS, "WINDOWS only") class TestServices(unittest.TestCase): def test_win_service_iter(self): @@ -551,19 +777,19 @@ def test_win_service_iter(self): ]) for serv in psutil.win_service_iter(): data = serv.as_dict() - self.assertIsInstance(data['name'], basestring) + self.assertIsInstance(data['name'], str) self.assertNotEqual(data['name'].strip(), "") - self.assertIsInstance(data['display_name'], basestring) - self.assertIsInstance(data['username'], basestring) + self.assertIsInstance(data['display_name'], str) + self.assertIsInstance(data['username'], str) self.assertIn(data['status'], valid_statuses) if data['pid'] is not None: psutil.Process(data['pid']) - self.assertIsInstance(data['binpath'], basestring) - self.assertIsInstance(data['username'], basestring) - self.assertIsInstance(data['start_type'], basestring) + self.assertIsInstance(data['binpath'], str) + self.assertIsInstance(data['username'], str) + self.assertIsInstance(data['start_type'], str) self.assertIn(data['start_type'], valid_start_types) self.assertIn(data['status'], valid_statuses) - self.assertIsInstance(data['description'], basestring) + self.assertIsInstance(data['description'], str) pid = serv.pid() if pid is not None: p = psutil.Process(pid) diff --git a/scripts/battery.py b/scripts/battery.py new file mode 100755 index 000000000..abbad8785 --- /dev/null +++ b/scripts/battery.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Show battery information. + +$ python scripts/battery.py +charge: 74% +left: 2:11:31 +status: discharging +plugged in: no +""" + +from __future__ import print_function +import sys + +import psutil + + +def secs2hours(secs): + mm, ss = divmod(secs, 60) + hh, mm = divmod(mm, 60) + return "%d:%02d:%02d" % (hh, mm, ss) + + +def main(): + if not hasattr(psutil, "sensors_battery"): + return sys.exit("platform not supported") + batt = psutil.sensors_battery() + if batt is None: + return sys.exit("no battery is installed") + + print("charge: %s%%" % round(batt.percent, 2)) + if batt.power_plugged: + print("status: %s" % ( + "charging" if batt.percent < 100 else "fully charged")) + print("plugged in: yes") + else: + print("left: %s" % secs2hours(batt.secsleft)) + print("status: %s" % "discharging") + print("plugged in: no") + + +if __name__ == '__main__': + main() diff --git a/scripts/cpu_distribution.py b/scripts/cpu_distribution.py new file mode 100755 index 000000000..a9f76b4e3 --- /dev/null +++ b/scripts/cpu_distribution.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Shows CPU workload split across different CPUs. + +$ python scripts/cpu_workload.py +CPU 0 CPU 1 CPU 2 CPU 3 CPU 4 CPU 5 CPU 6 CPU 7 +19.8 20.6 18.2 15.8 6.9 17.3 5.0 20.4 +gvfsd pytho kwork chrom unity kwork kwork kwork +chrom chrom indic ibus- whoop nfsd (sd-p gvfsd +ibus- cat at-sp chrom Modem nfsd4 light upsta +ibus- iprt- ibus- nacl_ cfg80 kwork nfsd bluet +chrom irqba gpg-a chrom ext4- biose nfsd dio/n +chrom acpid bamfd nvidi kwork scsi_ sshd rpc.m +upsta rsysl dbus- nfsd biose scsi_ ext4- polki +rtkit avahi upowe Netwo scsi_ biose UVM T irq/9 +light rpcbi snapd cron ipv6_ biose kwork dbus- +agett kvm-i avahi kwork biose biose scsi_ syste +nfsd syste rpc.i biose biose kbloc kthro UVM g +nfsd kwork kwork biose vmsta kwork crypt kaudi +nfsd scsi_ charg biose md ksoft kwork kwork +memca biose ksmd ecryp ksoft watch migra nvme +therm biose kcomp kswap migra cpuhp watch biose +syste biose kdevt khuge watch cpuhp biose +led_w devfr kwork write cpuhp biose +rpcio oom_r ksoft kwork syste biose +kwork kwork watch migra acpi_ +biose ksoft cpuhp watch watch +biose migra cpuhp kinte +biose watch rcu_s netns +biose cpuhp kthre kwork +cpuhp ksoft +watch migra +rcu_b cpuhp +kwork +""" + +from __future__ import print_function +import collections +import os +import sys +import time + +import psutil + + +if not hasattr(psutil.Process, "cpu_num"): + sys.exit("platform not supported") + + +def clean_screen(): + if psutil.POSIX: + os.system('clear') + else: + os.system('cls') + + +def main(): + total = psutil.cpu_count() + while True: + # header + clean_screen() + cpus_percent = psutil.cpu_percent(percpu=True) + for i in range(total): + print("CPU %-6i" % i, end="") + print() + for percent in cpus_percent: + print("%-10s" % percent, end="") + print() + + # processes + procs = collections.defaultdict(list) + for p in psutil.process_iter(attrs=['name', 'cpu_num']): + procs[p.info['cpu_num']].append(p.info['name'][:5]) + + end_marker = [[] for x in range(total)] + while True: + for num in range(total): + try: + pname = procs[num].pop() + except IndexError: + pname = "" + print("%-10s" % pname[:10], end="") + print() + if procs.values() == end_marker: + break + + time.sleep(1) + + +if __name__ == '__main__': + main() diff --git a/scripts/fans.py b/scripts/fans.py new file mode 100755 index 000000000..7a0ccf91d --- /dev/null +++ b/scripts/fans.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Show fans information. + +$ python fans.py +asus + cpu_fan 3200 RPM +""" + +from __future__ import print_function +import sys + +import psutil + + +def main(): + if not hasattr(psutil, "sensors_fans"): + return sys.exit("platform not supported") + fans = psutil.sensors_fans() + if not fans: + print("no fans detected") + return + for name, entries in fans.items(): + print(name) + for entry in entries: + print(" %-20s %s RPM" % (entry.label or name, entry.current)) + print() + + +if __name__ == '__main__': + main() diff --git a/scripts/ifconfig.py b/scripts/ifconfig.py index b823b3740..e2a9ce536 100755 --- a/scripts/ifconfig.py +++ b/scripts/ifconfig.py @@ -10,34 +10,34 @@ $ python scripts/ifconfig.py lo: stats : speed=0MB, duplex=?, mtu=65536, up=yes - incoming : bytes=6889336, pkts=84032, errs=0, drops=0 - outgoing : bytes=6889336, pkts=84032, errs=0, drops=0 + incoming : bytes=1.95M, pkts=22158, errs=0, drops=0 + outgoing : bytes=1.95M, pkts=22158, errs=0, drops=0 IPv4 address : 127.0.0.1 netmask : 255.0.0.0 IPv6 address : ::1 netmask : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff MAC address : 00:00:00:00:00:00 -vboxnet0: - stats : speed=10MB, duplex=full, mtu=1500, up=yes - incoming : bytes=0, pkts=0, errs=0, drops=0 - outgoing : bytes=1622766, pkts=9102, errs=0, drops=0 - IPv4 address : 192.168.33.1 - broadcast : 192.168.33.255 - netmask : 255.255.255.0 - IPv6 address : fe80::800:27ff:fe00:0%vboxnet0 +docker0: + stats : speed=0MB, duplex=?, mtu=1500, up=yes + incoming : bytes=3.48M, pkts=65470, errs=0, drops=0 + outgoing : bytes=164.06M, pkts=112993, errs=0, drops=0 + IPv4 address : 172.17.0.1 + broadcast : 172.17.0.1 + netmask : 255.255.0.0 + IPv6 address : fe80::42:27ff:fe5e:799e%docker0 netmask : ffff:ffff:ffff:ffff:: - MAC address : 0a:00:27:00:00:00 + MAC address : 02:42:27:5e:79:9e broadcast : ff:ff:ff:ff:ff:ff -eth0: +wlp3s0: stats : speed=0MB, duplex=?, mtu=1500, up=yes - incoming : bytes=18905596301, pkts=15178374, errs=0, drops=21 - outgoing : bytes=1913720087, pkts=9543981, errs=0, drops=0 - IPv4 address : 10.0.0.3 + incoming : bytes=7.04G, pkts=5637208, errs=0, drops=0 + outgoing : bytes=372.01M, pkts=3200026, errs=0, drops=0 + IPv4 address : 10.0.0.2 broadcast : 10.255.255.255 netmask : 255.0.0.0 - IPv6 address : fe80::7592:1dcf:bcb7:98d6%wlp3s0 + IPv6 address : fe80::ecb3:1584:5d17:937%wlp3s0 netmask : ffff:ffff:ffff:ffff:: MAC address : 48:45:20:59:a4:0c broadcast : ff:ff:ff:ff:ff:ff @@ -62,6 +62,24 @@ } +def bytes2human(n): + """ + >>> bytes2human(10000) + '9.8 K' + >>> bytes2human(100001221) + '95.4 M' + """ + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols): + prefix[s] = 1 << (i + 1) * 10 + for s in reversed(symbols): + if n >= prefix[s]: + value = float(n) / prefix[s] + return '%.2f%s' % (value, s) + return '%.2fB' % (n) + + def main(): stats = psutil.net_if_stats() io_counters = psutil.net_io_counters(pernic=True) @@ -77,10 +95,12 @@ def main(): io = io_counters[nic] print(" incoming : ", end='') print("bytes=%s, pkts=%s, errs=%s, drops=%s" % ( - io.bytes_recv, io.packets_recv, io.errin, io.dropin)) + bytes2human(io.bytes_recv), io.packets_recv, io.errin, + io.dropin)) print(" outgoing : ", end='') print("bytes=%s, pkts=%s, errs=%s, drops=%s" % ( - io.bytes_sent, io.packets_sent, io.errout, io.dropout)) + bytes2human(io.bytes_sent), io.packets_sent, io.errout, + io.dropout)) for addr in addrs: print(" %-4s" % af_map.get(addr.family, addr.family), end="") print(" address : %s" % addr.address) diff --git a/scripts/internal/bench_oneshot.py b/scripts/internal/bench_oneshot.py index cf8497f89..639e9ad76 100755 --- a/scripts/internal/bench_oneshot.py +++ b/scripts/internal/bench_oneshot.py @@ -37,10 +37,11 @@ if psutil.LINUX: names += [ - 'cpu_times', - 'gids', # 'memory_full_info', # 'memory_maps', + 'cpu_num', + 'cpu_times', + 'gids', 'name', 'num_ctx_switches', 'num_threads', @@ -63,6 +64,8 @@ 'terminal', 'uids', ] + if psutil.FREEBSD: + names.append('cpu_num') elif psutil.SUNOS: names += [ 'cmdline', diff --git a/scripts/internal/check_broken_links.py b/scripts/internal/check_broken_links.py new file mode 100755 index 000000000..7cf1e4898 --- /dev/null +++ b/scripts/internal/check_broken_links.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola', Himanshu Shekhar. +# All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +""" +Checks for broken links in file names specified as command line +parameters. + +There are a ton of a solutions available for validating URLs in string +using regex, but less for searching, of which very few are accurate. +This snippet is intended to just do the required work, and avoid +complexities. Django Validator has pretty good regex for validation, +but we have to find urls instead of validating them (REFERENCES [7]). +There's always room for improvement. + +Method: +* Match URLs using regex (REFERENCES [1]]) +* Some URLs need to be fixed, as they have < (or) > due to inefficient + regex. +* Remove duplicates (because regex is not 100% efficient as of now). +* Check validity of URL, using HEAD request. (HEAD to save bandwidth) + Uses requests module for others are painful to use. REFERENCES[9] + Handles redirects, http, https, ftp as well. + +REFERENCES: +Using [1] with some modificatons for including ftp +[1] http://stackoverflow.com/a/6883094/5163807 +[2] http://stackoverflow.com/a/31952097/5163807 +[3] http://daringfireball.net/2010/07/improved_regex_for_matching_urls +[4] https://mathiasbynens.be/demo/url-regex +[5] https://github.com/django/django/blob/master/django/core/validators.py +[6] https://data.iana.org/TLD/tlds-alpha-by-domain.txt +[7] https://codereview.stackexchange.com/questions/19663/http-url-validating +[8] https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD +[9] http://docs.python-requests.org/ + +Author: Himanshu Shekhar (2017) +""" + +from __future__ import print_function + +import concurrent.futures +import functools +import os +import re +import sys +import traceback + +import requests + + +HERE = os.path.abspath(os.path.dirname(__file__)) +REGEX = re.compile( + r'(?:http|ftp|https)?://' + r'(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+') +REQUEST_TIMEOUT = 15 +# There are some status codes sent by websites on HEAD request. +# Like 503 by Microsoft, and 401 by Apple +# They need to be sent GET request +RETRY_STATUSES = [503, 401, 403] + + +def memoize(fun): + """A memoize decorator.""" + @functools.wraps(fun) + def wrapper(*args, **kwargs): + key = (args, frozenset(sorted(kwargs.items()))) + try: + return cache[key] + except KeyError: + ret = cache[key] = fun(*args, **kwargs) + return ret + + cache = {} + return wrapper + + +def sanitize_url(url): + url = url.rstrip(',') + url = url.rstrip('.') + url = url.lstrip('(') + url = url.rstrip(')') + url = url.lstrip('[') + url = url.rstrip(']') + url = url.lstrip('<') + url = url.rstrip('>') + return url + + +def find_urls(s): + matches = REGEX.findall(s) or [] + return list(set([sanitize_url(x) for x in matches])) + + +def parse_rst(fname): + """Look for links in a .rst file.""" + with open(fname) as f: + text = f.read() + urls = find_urls(text) + # HISTORY file has a lot of dead links. + if fname == 'HISTORY.rst' and urls: + urls = [ + x for x in urls if + not x.startswith('https://github.com/giampaolo/psutil/issues')] + return urls + + +def parse_py(fname): + """Look for links in a .py file.""" + with open(fname) as f: + lines = f.readlines() + urls = set() + for i, line in enumerate(lines): + for url in find_urls(line): + # comment block + if line.lstrip().startswith('# '): + subidx = i + 1 + while True: + nextline = lines[subidx].strip() + if re.match('^# .+', nextline): + url += nextline[1:].strip() + else: + break + subidx += 1 + urls.add(url) + return list(urls) + + +def parse_c(fname): + """Look for links in a .py file.""" + with open(fname) as f: + lines = f.readlines() + urls = set() + for i, line in enumerate(lines): + for url in find_urls(line): + # comment block // + if line.lstrip().startswith('// '): + subidx = i + 1 + while True: + nextline = lines[subidx].strip() + if re.match('^// .+', nextline): + url += nextline[2:].strip() + else: + break + subidx += 1 + # comment block /* + elif line.lstrip().startswith('* '): + subidx = i + 1 + while True: + nextline = lines[subidx].strip() + if re.match('^\* .+', nextline): + url += nextline[1:].strip() + else: + break + subidx += 1 + urls.add(url) + return list(urls) + + +def parse_generic(fname): + with open(fname) as f: + text = f.read() + return find_urls(text) + + +def get_urls(fname): + """Extracts all URLs in fname and return them as a list.""" + if fname.endswith('.rst'): + return parse_rst(fname) + elif fname.endswith('.py'): + return parse_py(fname) + elif fname.endswith('.c') or fname.endswith('.h'): + return parse_c(fname) + else: + with open(fname) as f: + if f.readline().strip().startswith('#!/usr/bin/env python'): + return parse_py(fname) + return parse_generic(fname) + + +@memoize +def validate_url(url): + """Validate the URL by attempting an HTTP connection. + Makes an HTTP-HEAD request for each URL. + """ + try: + res = requests.head(url, timeout=REQUEST_TIMEOUT) + # some websites deny 503, like Microsoft + # and some send 401, like Apple, observations + if (not res.ok) and (res.status_code in RETRY_STATUSES): + res = requests.get(url, timeout=REQUEST_TIMEOUT) + return res.ok + except requests.exceptions.RequestException: + return False + + +def parallel_validator(urls): + """validates all urls in parallel + urls: tuple(filename, url) + """ + fails = [] # list of tuples (filename, url) + current = 0 + total = len(urls) + with concurrent.futures.ThreadPoolExecutor() as executor: + fut_to_url = {executor.submit(validate_url, url[1]): url + for url in urls} + for fut in concurrent.futures.as_completed(fut_to_url): + current += 1 + sys.stdout.write("\r%s / %s" % (current, total)) + sys.stdout.flush() + fname, url = fut_to_url[fut] + try: + ok = fut.result() + except Exception: + fails.append((fname, url)) + print() + print("warn: error while validating %s" % url, file=sys.stderr) + traceback.print_exc() + else: + if not ok: + fails.append((fname, url)) + + print() + return fails + + +def main(): + files = sys.argv[1:] + if not files: + print("usage: %s " % sys.argv[0], file=sys.stderr) + return sys.exit(1) + + all_urls = [] + for fname in files: + urls = get_urls(fname) + if urls: + print("%4s %s" % (len(urls), fname)) + for url in urls: + all_urls.append((fname, url)) + + fails = parallel_validator(all_urls) + if not fails: + print("all links are valid; cheers!") + else: + for fail in fails: + fname, url = fail + print("%-30s: %s " % (fname, url)) + print('-' * 20) + print("total: %s fails!" % len(fails)) + sys.exit(1) + + +if __name__ == '__main__': + try: + main() + except (KeyboardInterrupt, SystemExit): + os._exit(0) diff --git a/scripts/internal/download_exes.py b/scripts/internal/download_exes.py index 2a40168c1..1b0044288 100755 --- a/scripts/internal/download_exes.py +++ b/scripts/internal/download_exes.py @@ -7,31 +7,32 @@ """ Script which downloads exe and wheel files hosted on AppVeyor: https://ci.appveyor.com/project/giampaolo/psutil -Copied and readapted from the original recipe of Ibarra Corretge' +Readapted from the original recipe of Ibarra Corretge' : http://code.saghul.net/index.php/2015/09/09/ """ from __future__ import print_function import argparse +import concurrent.futures import errno -import multiprocessing import os import requests import shutil import sys -from concurrent.futures import ThreadPoolExecutor - from psutil import __version__ as PSUTIL_VERSION BASE_URL = 'https://ci.appveyor.com/api' -PY_VERSIONS = ['2.7', '3.3', '3.4', '3.5'] +PY_VERSIONS = ['2.7', '3.4', '3.5', '3.6'] +TIMEOUT = 30 +COLORS = True -def exit(msg): - print(hilite(msg, ok=False), file=sys.stderr) +def exit(msg=""): + if msg: + print(hilite(msg, ok=False), file=sys.stderr) sys.exit(1) @@ -47,22 +48,23 @@ def term_supports_colors(file=sys.stdout): return True -if term_supports_colors(): - def hilite(s, ok=True, bold=False): - """Return an highlighted version of 'string'.""" - attr = [] - if ok is None: # no color - pass - elif ok: # green - attr.append('32') - else: # red - attr.append('31') - if bold: - attr.append('1') - return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s) -else: - def hilite(s, *a, **k): +COLORS = term_supports_colors() + + +def hilite(s, ok=True, bold=False): + """Return an highlighted version of 'string'.""" + if not COLORS: return s + attr = [] + if ok is None: # no color + pass + elif ok: # green + attr.append('32') + else: # red + attr.append('31') + if bold: + attr.append('1') + return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s) def safe_makedirs(path): @@ -85,29 +87,49 @@ def onerror(fun, path, excinfo): shutil.rmtree(path, onerror=onerror) +def bytes2human(n): + """ + >>> bytes2human(10000) + '9.8 K' + >>> bytes2human(100001221) + '95.4 M' + """ + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols): + prefix[s] = 1 << (i + 1) * 10 + for s in reversed(symbols): + if n >= prefix[s]: + value = float(n) / prefix[s] + return '%.2f %s' % (value, s) + return '%.2f B' % (n) + + def download_file(url): local_fname = url.split('/')[-1] local_fname = os.path.join('dist', local_fname) - print(local_fname) safe_makedirs('dist') - r = requests.get(url, stream=True) + r = requests.get(url, stream=True, timeout=TIMEOUT) + tot_bytes = 0 with open(local_fname, 'wb') as f: - for chunk in r.iter_content(chunk_size=1024): + for chunk in r.iter_content(chunk_size=16384): if chunk: # filter out keep-alive new chunks f.write(chunk) + tot_bytes += len(chunk) return local_fname def get_file_urls(options): session = requests.Session() data = session.get( - BASE_URL + '/projects/' + options.user + '/' + options.project) + BASE_URL + '/projects/' + options.user + '/' + options.project, + timeout=TIMEOUT) data = data.json() urls = [] for job in (job['jobId'] for job in data['build']['jobs']): job_url = BASE_URL + '/buildjobs/' + job + '/artifacts' - data = session.get(job_url) + data = session.get(job_url, timeout=TIMEOUT) data = data.json() for item in data: file_url = job_url + '/' + item['fileName'] @@ -131,17 +153,29 @@ def rename_27_wheels(): def main(options): - files = [] safe_rmtree('dist') - with ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as e: - for url in get_file_urls(options): - fut = e.submit(download_file, url) - files.append(fut.result()) - # 2 exes (32 and 64 bit) and 2 wheels (32 and 64 bit) for each ver. - expected = len(PY_VERSIONS) * 4 - got = len(files) - if expected != got: - return exit("expected %s files, got %s" % (expected, got)) + urls = get_file_urls(options) + completed = 0 + exc = None + with concurrent.futures.ThreadPoolExecutor() as e: + fut_to_url = {e.submit(download_file, url): url for url in urls} + for fut in concurrent.futures.as_completed(fut_to_url): + url = fut_to_url[fut] + try: + local_fname = fut.result() + except Exception as _: + exc = _ + print("error while downloading %s: %s" % (url, exc)) + else: + completed += 1 + print("downloaded %-45s %s" % ( + local_fname, bytes2human(os.path.getsize(local_fname)))) + # 2 wheels (32 and 64 bit) per supported python version + expected = len(PY_VERSIONS) * 2 + if expected != completed: + return exit("expected %s files, got %s" % (expected, completed)) + if exc: + return exit() rename_27_wheels() diff --git a/scripts/internal/generate_manifest.py b/scripts/internal/generate_manifest.py new file mode 100755 index 000000000..3511b7492 --- /dev/null +++ b/scripts/internal/generate_manifest.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Generate MANIFEST.in file. +""" + +import os +import subprocess + + +IGNORED_EXTS = ('.png', '.jpg', '.jpeg') +IGNORED_FILES = ('.travis.yml', 'appveyor.yml') + + +def sh(cmd): + return subprocess.check_output( + cmd, shell=True, universal_newlines=True).strip() + + +def main(): + files = sh("git ls-files").split('\n') + for file in files: + if file.startswith('.ci/') or \ + os.path.splitext(file)[1].lower() in IGNORED_EXTS or \ + file in IGNORED_FILES: + continue + print("include " + file) + + +if __name__ == '__main__': + main() diff --git a/scripts/internal/print_announce.py b/scripts/internal/print_announce.py index e47911c24..1c2b9e113 100755 --- a/scripts/internal/print_announce.py +++ b/scripts/internal/print_announce.py @@ -19,7 +19,7 @@ PRJ_NAME = 'psutil' PRJ_URL_HOME = 'https://github.com/giampaolo/psutil' -PRJ_URL_DOC = 'http://pythonhosted.org/psutil' +PRJ_URL_DOC = 'http://psutil.readthedocs.io' PRJ_URL_DOWNLOAD = 'https://pypi.python.org/pypi/psutil' PRJ_URL_WHATSNEW = \ 'https://github.com/giampaolo/psutil/blob/master/HISTORY.rst' @@ -39,10 +39,9 @@ running processes. It implements many functionalities offered by command \ line tools such as: ps, top, lsof, netstat, ifconfig, who, df, kill, free, \ nice, ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap. It \ -currently supports Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD and \ -NetBSD, both 32-bit and 64-bit architectures, with Python versions from 2.6 \ -to 3.5 (users of Python 2.4 and 2.5 may use 2.1.3 version). PyPy is also \ -known to work. +currently supports Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD, NetBSD \ +and AIX, both 32-bit and 64-bit architectures, with Python versions from 2.6 \ +to 3.6. PyPy is also known to work. What's new ========== diff --git a/scripts/internal/print_timeline.py b/scripts/internal/print_timeline.py new file mode 100644 index 000000000..ffcb8fe85 --- /dev/null +++ b/scripts/internal/print_timeline.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Prints releases' timeline in RST format. +""" + +import subprocess + + +entry = """\ +- {date}: + `{ver} `__ - + `what's new `__ - + `diff `__""" # NOQA + + +def sh(cmd): + return subprocess.check_output( + cmd, shell=True, universal_newlines=True).strip() + + +def get_tag_date(tag): + out = sh(r"git log -1 --format=%ai {}".format(tag)) + return out.split(' ')[0] + + +def main(): + releases = [] + out = sh("git tags") + for line in out.split('\n'): + tag = line.split(' ')[0] + ver = tag.replace('release-', '') + nodotver = ver.replace('.', '') + date = get_tag_date(tag) + releases.append((tag, ver, nodotver, date)) + releases.sort(reverse=True) + + for i, rel in enumerate(releases): + tag, ver, nodotver, date = rel + try: + prevtag = releases[i + 1][0] + except IndexError: + # get first commit + prevtag = sh("git rev-list --max-parents=0 HEAD") + print(entry.format(**locals())) + + +if __name__ == '__main__': + main() diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py index bbe73e0df..548f7a8ed 100755 --- a/scripts/internal/winmake.py +++ b/scripts/internal/winmake.py @@ -11,19 +11,21 @@ that they should be deemed illegal! """ +from __future__ import print_function import errno import fnmatch import functools import os import shutil +import site import ssl import subprocess import sys import tempfile -PYTHON = sys.executable -TSCRIPT = os.environ['TSCRIPT'] +PYTHON = os.getenv('PYTHON', sys.executable) +TSCRIPT = os.getenv('TSCRIPT', 'psutil\\tests\\__main__.py') GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" PY3 = sys.version_info[0] == 3 DEPS = [ @@ -36,24 +38,49 @@ "perf", "pip", "pypiwin32", + "pyreadline", "setuptools", "unittest2", "wheel", "wmi", + "requests" ] _cmds = {} - +if PY3: + basestring = str # =================================================================== # utils # =================================================================== -def sh(cmd): - print("cmd: " + cmd) - code = os.system(cmd) - if code: - raise SystemExit +def safe_print(text, file=sys.stdout, flush=False): + """Prints a (unicode) string to the console, encoded depending on + the stdout/file encoding (eg. cp437 on Windows). This is to avoid + encoding errors in case of funky path names. + Works with Python 2 and 3. + """ + if not isinstance(text, basestring): + return print(text, file=file) + try: + file.write(text) + except UnicodeEncodeError: + bytes_string = text.encode(file.encoding, 'backslashreplace') + if hasattr(file, 'buffer'): + file.buffer.write(bytes_string) + else: + text = bytes_string.decode(file.encoding, 'strict') + file.write(text) + file.write("\n") + + +def sh(cmd, nolog=False): + if not nolog: + safe_print("cmd: " + cmd) + p = subprocess.Popen(cmd, shell=True, env=os.environ, cwd=os.getcwd()) + p.communicate() + if p.returncode != 0: + sys.exit(p.returncode) def cmd(fun): @@ -74,7 +101,7 @@ def safe_remove(path): if err.errno != errno.ENOENT: raise else: - print("rm %s" % path) + safe_print("rm %s" % path) def safe_rmtree(path): def onerror(fun, path, excinfo): @@ -85,7 +112,7 @@ def onerror(fun, path, excinfo): existed = os.path.isdir(path) shutil.rmtree(path, onerror=onerror) if existed: - print("rmdir -f %s" % path) + safe_print("rmdir -f %s" % path) if "*" not in pattern: if directory: @@ -102,10 +129,10 @@ def onerror(fun, path, excinfo): for name in found: path = os.path.join(root, name) if directory: - print("rmdir -f %s" % path) + safe_print("rmdir -f %s" % path) safe_rmtree(path) else: - print("rm %s" % path) + safe_print("rm %s" % path) safe_remove(path) @@ -116,7 +143,7 @@ def safe_remove(path): if err.errno != errno.ENOENT: raise else: - print("rm %s" % path) + safe_print("rm %s" % path) def safe_rmtree(path): @@ -128,12 +155,12 @@ def onerror(fun, path, excinfo): existed = os.path.isdir(path) shutil.rmtree(path, onerror=onerror) if existed: - print("rmdir -f %s" % path) + safe_print("rmdir -f %s" % path) def recursive_rm(*patterns): """Recursively remove a file or matching a list of patterns.""" - for root, subdirs, subfiles in os.walk('.'): + for root, subdirs, subfiles in os.walk(u'.'): root = os.path.normpath(root) if root.startswith('.git/'): continue @@ -147,6 +174,12 @@ def recursive_rm(*patterns): safe_rmtree(os.path.join(root, dir)) +def test_setup(): + os.environ['PYTHONWARNINGS'] = 'all' + os.environ['PSUTIL_TESTING'] = '1' + os.environ['PSUTIL_DEBUG'] = '1' + + # =================================================================== # commands # =================================================================== @@ -155,19 +188,33 @@ def recursive_rm(*patterns): @cmd def help(): """Print this help""" - print('Run "make " where is one of:') + safe_print('Run "make [-p ] " where is one of:') for name in sorted(_cmds): - print(" %-20s %s" % (name.replace('_', '-'), _cmds[name] or '')) + safe_print( + " %-20s %s" % (name.replace('_', '-'), _cmds[name] or '')) + sys.exit(1) @cmd def build(): """Build / compile""" + # Make sure setuptools is installed (needed for 'develop' / + # edit mode). + sh('%s -c "import setuptools"' % PYTHON) sh("%s setup.py build" % PYTHON) - # copies compiled *.pyd files in ./psutil directory in order to + # Copies compiled *.pyd files in ./psutil directory in order to # allow "import psutil" when using the interactive interpreter # from within this directory. sh("%s setup.py build_ext -i" % PYTHON) + # Make sure it actually worked. + sh('%s -c "import psutil"' % PYTHON) + + +@cmd +def build_wheel(): + """Create wheel file.""" + build() + sh("%s setup.py bdist_wheel" % PYTHON) @cmd @@ -186,7 +233,7 @@ def install_pip(): else: ctx = None kw = dict(context=ctx) if ctx else {} - print("downloading %s" % GET_PIP_URL) + safe_print("downloading %s" % GET_PIP_URL) req = urlopen(GET_PIP_URL, **kw) data = req.read() @@ -211,18 +258,13 @@ def install(): @cmd def uninstall(): """Uninstall psutil""" + # Uninstalling psutil on Windows seems to be tricky. + # On "import psutil" tests may import a psutil version living in + # C:\PythonXY\Lib\site-packages which is not what we want, so + # we try both "pip uninstall psutil" and manually remove stuff + # from site-packages. clean() - try: - import psutil - except ImportError: - return install_pip() - sh("%s -m pip uninstall -y psutil" % PYTHON) - - # Uninstalling psutil on Windows seems to be tricky as we may have - # different versions os psutil installed. Also we don't want to be - # in the main psutil source dir as "import psutil" will always - # succeed so this really removes files from site-packages dir. here = os.getcwd() try: os.chdir('C:\\') @@ -230,11 +272,17 @@ def uninstall(): try: import psutil # NOQA except ImportError: - return - sh("%s -m pip uninstall -y psutil" % PYTHON) + break + else: + sh("%s -m pip uninstall -y psutil" % PYTHON) finally: os.chdir(here) + for dir in site.getsitepackages(): + for name in os.listdir(dir): + if name.startswith('psutil'): + rm(os.path.join(dir, name)) + @cmd def clean(): @@ -279,13 +327,14 @@ def flake8(): py_files = py_files.decode() py_files = [x for x in py_files.split() if x.endswith('.py')] py_files = ' '.join(py_files) - sh("%s -m flake8 %s" % (PYTHON, py_files)) + sh("%s -m flake8 %s" % (PYTHON, py_files), nolog=True) @cmd def test(): """Run tests""" install() + test_setup() sh("%s %s" % (PYTHON, TSCRIPT)) @@ -294,6 +343,7 @@ def coverage(): """Run coverage tests.""" # Note: coverage options are controlled by .coveragerc file install() + test_setup() sh("%s -m coverage run %s" % (PYTHON, TSCRIPT)) sh("%s -m coverage report" % PYTHON) sh("%s -m coverage html" % PYTHON) @@ -304,6 +354,7 @@ def coverage(): def test_process(): """Run process tests""" install() + test_setup() sh("%s -m unittest -v psutil.tests.test_process" % PYTHON) @@ -311,6 +362,7 @@ def test_process(): def test_system(): """Run system tests""" install() + test_setup() sh("%s -m unittest -v psutil.tests.test_system" % PYTHON) @@ -318,6 +370,7 @@ def test_system(): def test_platform(): """Run windows only tests""" install() + test_setup() sh("%s -m unittest -v psutil.tests.test_windows" % PYTHON) @@ -325,25 +378,65 @@ def test_platform(): def test_misc(): """Run misc tests""" install() + test_setup() sh("%s -m unittest -v psutil.tests.test_misc" % PYTHON) +@cmd +def test_unicode(): + """Run unicode tests""" + install() + test_setup() + sh("%s -m unittest -v psutil.tests.test_unicode" % PYTHON) + + +@cmd +def test_connections(): + """Run connections tests""" + install() + test_setup() + sh("%s -m unittest -v psutil.tests.test_connections" % PYTHON) + + +@cmd +def test_contracts(): + """Run contracts tests""" + install() + test_setup() + sh("%s -m unittest -v psutil.tests.test_contracts" % PYTHON) + + @cmd def test_by_name(): """Run test by name""" try: - print(sys.argv) + safe_print(sys.argv) name = sys.argv[2] except IndexError: sys.exit('second arg missing') install() + test_setup() sh("%s -m unittest -v %s" % (PYTHON, name)) +@cmd +def test_script(): + """Quick way to test a script""" + try: + safe_print(sys.argv) + name = sys.argv[2] + except IndexError: + sys.exit('second arg missing') + install() + test_setup() + sh("%s %s" % (PYTHON, name)) + + @cmd def test_memleaks(): """Run memory leaks tests""" install() + test_setup() sh("%s psutil\\tests\\test_memory_leaks.py" % PYTHON) @@ -356,21 +449,54 @@ def install_git_hooks(): @cmd def bench_oneshot(): install() - sh("%s scripts\\internal\\bench_oneshot.py" % PYTHON) + sh("%s -Wa scripts\\internal\\bench_oneshot.py" % PYTHON) @cmd def bench_oneshot_2(): install() - sh("%s scripts\\internal\\bench_oneshot_2.py" % PYTHON) + sh("%s -Wa scripts\\internal\\bench_oneshot_2.py" % PYTHON) + + +def set_python(s): + global PYTHON + if os.path.isabs(s): + PYTHON = s + else: + # try to look for a python installation + orig = s + s = s.replace('.', '') + vers = ('26', '27', '34', '35', '36', '37', + '26-64', '27-64', '34-64', '35-64', '36-64', '37-64') + for v in vers: + if s == v: + path = 'C:\\python%s\python.exe' % s + if os.path.isfile(path): + print(path) + PYTHON = path + os.putenv('PYTHON', path) + return + return sys.exit( + "can't find any python installation matching %r" % orig) + + +def parse_cmdline(): + if '-p' in sys.argv: + try: + pos = sys.argv.index('-p') + sys.argv.pop(pos) + py = sys.argv.pop(pos) + except IndexError: + return help() + set_python(py) def main(): + parse_cmdline() try: cmd = sys.argv[1].replace('-', '_') except IndexError: return help() - if cmd in _cmds: fun = getattr(sys.modules[__name__], cmd) fun() diff --git a/scripts/netstat.py b/scripts/netstat.py index 1426cd76d..490b429f2 100755 --- a/scripts/netstat.py +++ b/scripts/netstat.py @@ -41,11 +41,8 @@ def main(): "Proto", "Local address", "Remote address", "Status", "PID", "Program name")) proc_names = {} - for p in psutil.process_iter(): - try: - proc_names[p.pid] = p.name() - except psutil.Error: - pass + for p in psutil.process_iter(attrs=['pid', 'name']): + proc_names[p.info['pid']] = p.info['name'] for c in psutil.net_connections(kind='inet'): laddr = "%s:%s" % (c.laddr) raddr = "" diff --git a/scripts/nettop.py b/scripts/nettop.py index 97f80aadc..e13903c11 100755 --- a/scripts/nettop.py +++ b/scripts/nettop.py @@ -49,6 +49,7 @@ def tear_down(): curses.echo() curses.endwin() + win = curses.initscr() atexit.register(tear_down) curses.endwin() diff --git a/scripts/pidof.py b/scripts/pidof.py index 1c23900f4..bcb8a2e6d 100755 --- a/scripts/pidof.py +++ b/scripts/pidof.py @@ -18,26 +18,11 @@ def pidof(pgname): pids = [] - for proc in psutil.process_iter(): - with proc.oneshot(): - # search for matches in the process name and cmdline - try: - name = proc.name() - except psutil.Error: - pass - else: - if name == pgname: - pids.append(str(proc.pid)) - continue - - try: - cmdline = proc.cmdline() - except psutil.Error: - pass - else: - if cmdline and cmdline[0] == pgname: - pids.append(str(proc.pid)) - + for proc in psutil.process_iter(attrs=['name', 'cmdline']): + # search for matches in the process name and cmdline + if proc.info['name'] == pgname or \ + proc.info['cmdline'] and proc.info['cmdline'][0] == pgname: + pids.append(str(proc.pid)) return pids diff --git a/scripts/procinfo.py b/scripts/procinfo.py index 8dc34c459..54205de36 100755 --- a/scripts/procinfo.py +++ b/scripts/procinfo.py @@ -190,6 +190,8 @@ def run(pid, verbose=False): print_('cpu-times', str_ntuple(pinfo['cpu_times'])) if hasattr(proc, "cpu_affinity"): print_("cpu-affinity", pinfo["cpu_affinity"]) + if hasattr(proc, "cpu_num"): + print_("cpu-num", pinfo["cpu_num"]) print_('memory', str_ntuple(pinfo['memory_info'], bytes2human=True)) print_('memory %', round(pinfo['memory_percent'], 2)) @@ -223,7 +225,8 @@ def run(pid, verbose=False): if 'io_counters' in pinfo: print_('I/O', str_ntuple(pinfo['io_counters'], bytes2human=True)) - print_("ctx-switches", str_ntuple(pinfo['num_ctx_switches'])) + if 'num_ctx_switches' in pinfo: + print_("ctx-switches", str_ntuple(pinfo['num_ctx_switches'])) if pinfo['children']: template = "%-6s %s" print_("children", template % ("PID", "NAME")) diff --git a/scripts/sensors.py b/scripts/sensors.py new file mode 100755 index 000000000..bbf3ac908 --- /dev/null +++ b/scripts/sensors.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +A clone of 'sensors' utility on Linux printing hardware temperatures, +fans speed and battery info. + +$ python scripts/sensors.py +asus + Temperatures: + asus 57.0°C (high=None°C, critical=None°C) + Fans: + cpu_fan 3500 RPM +acpitz + Temperatures: + acpitz 57.0°C (high=108.0°C, critical=108.0°C) +coretemp + Temperatures: + Physical id 0 61.0°C (high=87.0°C, critical=105.0°C) + Core 0 61.0°C (high=87.0°C, critical=105.0°C) + Core 1 59.0°C (high=87.0°C, critical=105.0°C) +Battery: + charge: 84.95% + status: charging + plugged in: yes +""" + +from __future__ import print_function + +import psutil + + +def secs2hours(secs): + mm, ss = divmod(secs, 60) + hh, mm = divmod(mm, 60) + return "%d:%02d:%02d" % (hh, mm, ss) + + +def main(): + if hasattr(psutil, "sensors_temperatures"): + temps = psutil.sensors_temperatures() + else: + temps = {} + if hasattr(psutil, "sensors_fans"): + fans = psutil.sensors_fans() + else: + fans = {} + if hasattr(psutil, "sensors_battery"): + battery = psutil.sensors_battery() + else: + battery = None + + if not any((temps, fans, battery)): + print("can't read any temperature, fans or battery info") + return + + names = set(list(temps.keys()) + list(fans.keys())) + for name in names: + print(name) + # Temperatures. + if name in temps: + print(" Temperatures:") + for entry in temps[name]: + print(" %-20s %s°C (high=%s°C, critical=%s°C)" % ( + entry.label or name, entry.current, entry.high, + entry.critical)) + # Fans. + if name in fans: + print(" Fans:") + for entry in fans[name]: + print(" %-20s %s RPM" % ( + entry.label or name, entry.current)) + + # Battery. + if battery: + print("Battery:") + print(" charge: %s%%" % round(battery.percent, 2)) + if battery.power_plugged: + print(" status: %s" % ( + "charging" if battery.percent < 100 else "fully charged")) + print(" plugged in: yes") + else: + print(" left: %s" % secs2hours(battery.secsleft)) + print(" status: %s" % "discharging") + print(" plugged in: no") + + +if __name__ == '__main__': + main() diff --git a/scripts/temperatures.py b/scripts/temperatures.py new file mode 100755 index 000000000..15b9156b8 --- /dev/null +++ b/scripts/temperatures.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +A clone of 'sensors' utility on Linux printing hardware temperatures. + +$ python scripts/sensors.py +asus + asus 47.0 °C (high = None °C, critical = None °C) + +acpitz + acpitz 47.0 °C (high = 103.0 °C, critical = 103.0 °C) + +coretemp + Physical id 0 54.0 °C (high = 100.0 °C, critical = 100.0 °C) + Core 0 47.0 °C (high = 100.0 °C, critical = 100.0 °C) + Core 1 48.0 °C (high = 100.0 °C, critical = 100.0 °C) + Core 2 47.0 °C (high = 100.0 °C, critical = 100.0 °C) + Core 3 54.0 °C (high = 100.0 °C, critical = 100.0 °C) +""" + +from __future__ import print_function +import sys + +import psutil + + +def main(): + if not hasattr(psutil, "sensors_temperatures"): + sys.exit("platform not supported") + temps = psutil.sensors_temperatures() + if not temps: + sys.exit("can't read any temperature") + for name, entries in temps.items(): + print(name) + for entry in entries: + print(" %-20s %s °C (high = %s °C, critical = %s °C)" % ( + entry.label or name, entry.current, entry.high, + entry.critical)) + print() + + +if __name__ == '__main__': + main() diff --git a/scripts/who.py b/scripts/who.py index 046ec23f0..748d936c9 100755 --- a/scripts/who.py +++ b/scripts/who.py @@ -9,10 +9,8 @@ currently logged in. $ python scripts/who.py -giampaolo tty7 2014-02-23 17:25 (:0) -giampaolo pts/7 2014-02-24 18:25 (:192.168.1.56) -giampaolo pts/8 2014-02-24 18:25 (:0) -giampaolo pts/9 2014-02-27 01:32 (:0) +giampaolo console 2017-03-25 22:24 loginwindow +giampaolo ttys000 2017-03-25 23:28 (10.0.2.2) sshd """ from datetime import datetime @@ -23,11 +21,14 @@ def main(): users = psutil.users() for user in users: - print("%-15s %-15s %s (%s)" % ( + proc_name = psutil.Process(user.pid).name() if user.pid else "" + print("%-12s %-10s %-10s %-14s %s" % ( user.name, user.terminal or '-', datetime.fromtimestamp(user.started).strftime("%Y-%m-%d %H:%M"), - user.host)) + "(%s)" % user.host if user.host else "", + proc_name + )) if __name__ == '__main__': diff --git a/scripts/winservices.py b/scripts/winservices.py index fed6a734e..1a65adcef 100755 --- a/scripts/winservices.py +++ b/scripts/winservices.py @@ -45,7 +45,7 @@ def main(): for service in psutil.win_service_iter(): info = service.as_dict() - print("%s (%s)" % (info['name'], info['display_name'])) + print("%r (%r)" % (info['name'], info['display_name'])) print("status: %s, start: %s, username: %s, pid: %s" % ( info['status'], info['start_type'], info['username'], info['pid'])) print("binpath: %s" % info['binpath']) diff --git a/setup.py b/setup.py index 726d46199..cd7543b0a 100755 --- a/setup.py +++ b/setup.py @@ -4,26 +4,29 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -"""psutil is a cross-platform library for retrieving information on -running processes and system utilization (CPU, memory, disks, network) -in Python. -""" +"""Cross-platform lib for process and system monitoring in Python.""" -import atexit import contextlib import io import os import platform import sys import tempfile -import warnings -try: - from setuptools import setup, Extension -except ImportError: - from distutils.core import setup, Extension import glob +import warnings + +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + try: + import setuptools + from setuptools import setup, Extension + except ImportError: + setuptools = None + from distutils.core import setup, Extension HERE = os.path.abspath(os.path.dirname(__file__)) + +# ...so we can import _common.py sys.path.insert(0, os.path.join(HERE, "psutil")) from _common import BSD # NOQA @@ -35,6 +38,7 @@ from _common import POSIX # NOQA from _common import SUNOS # NOQA from _common import WINDOWS # NOQA +from _common import AIX # NOQA macros = [] @@ -45,6 +49,22 @@ if BSD: macros.append(("PSUTIL_BSD", 1)) +sources = ['psutil/_psutil_common.c'] +if POSIX: + sources.append('psutil/_psutil_posix.c') + +tests_require = [] +if sys.version_info[:2] <= (2, 6): + tests_require.append('unittest2') +if sys.version_info[:2] <= (2, 7): + tests_require.append('mock') +if sys.version_info[:2] <= (3, 2): + tests_require.append('ipaddress') + +extras_require = {} +if sys.version_info[:2] <= (3, 3): + extras_require.update(dict(enum='enum34')) + def get_version(): INIT = os.path.join(HERE, 'psutil/__init__.py') @@ -56,8 +76,11 @@ def get_version(): for num in ret.split('.'): assert num.isdigit(), ret return ret - else: - raise ValueError("couldn't find version string") + raise ValueError("couldn't find version string") + + +VERSION = get_version() +macros.append(('PSUTIL_VERSION', int(VERSION.replace('.', '')))) def get_description(): @@ -83,30 +106,14 @@ def write(self, s): setattr(sys, stream_name, orig) -VERSION = get_version() -macros.append(('PSUTIL_VERSION', int(VERSION.replace('.', '')))) - - -# POSIX -if POSIX: - posix_extension = Extension( - 'psutil._psutil_posix', - sources=['psutil/_psutil_posix.c']) - if SUNOS: - posix_extension.libraries.append('socket') - if platform.release() == '5.10': - posix_extension.sources.append('psutil/arch/solaris/v10/ifaddrs.c') - posix_extension.define_macros.append(('PSUTIL_SUNOS10', 1)) - -# Windows if WINDOWS: def get_winver(): maj, min = sys.getwindowsversion()[0:2] return '0x0%s' % ((maj * 100) + min) if sys.getwindowsversion()[0] < 6: - msg = "Windows versions < Vista are no longer supported or maintained;" - msg = " latest supported version is psutil 3.4.2; " + msg = "warning: Windows versions < Vista are no longer supported or " + msg = "maintained; latest official supported version is psutil 3.4.2; " msg += "psutil may still be installed from sources if you have " msg += "Visual Studio and may also (kind of) work though" warnings.warn(msg, UserWarning) @@ -123,9 +130,8 @@ def get_winver(): ext = Extension( 'psutil._psutil_windows', - sources=[ + sources=sources + [ 'psutil/_psutil_windows.c', - 'psutil/_psutil_common.c', 'psutil/arch/windows/process_info.c', 'psutil/arch/windows/process_handles.c', 'psutil/arch/windows/security.c', @@ -135,74 +141,61 @@ def get_winver(): define_macros=macros, libraries=[ "psapi", "kernel32", "advapi32", "shell32", "netapi32", - "iphlpapi", "wtsapi32", "ws2_32", + "iphlpapi", "wtsapi32", "ws2_32", "PowrProf", ], # extra_compile_args=["/Z7"], # extra_link_args=["/DEBUG"] ) - extensions = [ext] -# OS X elif OSX: macros.append(("PSUTIL_OSX", 1)) ext = Extension( 'psutil._psutil_osx', - sources=[ + sources=sources + [ 'psutil/_psutil_osx.c', - 'psutil/_psutil_common.c', 'psutil/arch/osx/process_info.c', ], define_macros=macros, extra_link_args=[ '-framework', 'CoreFoundation', '-framework', 'IOKit' ]) - extensions = [ext, posix_extension] -# FreeBSD elif FREEBSD: macros.append(("PSUTIL_FREEBSD", 1)) ext = Extension( 'psutil._psutil_bsd', - sources=[ + sources=sources + [ 'psutil/_psutil_bsd.c', - 'psutil/_psutil_common.c', - 'psutil/arch/bsd/freebsd.c', - 'psutil/arch/bsd/freebsd_socks.c', + 'psutil/arch/freebsd/specific.c', + 'psutil/arch/freebsd/sys_socks.c', + 'psutil/arch/freebsd/proc_socks.c', ], define_macros=macros, libraries=["devstat"]) - extensions = [ext, posix_extension] -# OpenBSD elif OPENBSD: macros.append(("PSUTIL_OPENBSD", 1)) ext = Extension( 'psutil._psutil_bsd', - sources=[ + sources=sources + [ 'psutil/_psutil_bsd.c', - 'psutil/_psutil_common.c', - 'psutil/arch/bsd/openbsd.c', + 'psutil/arch/openbsd/specific.c', ], define_macros=macros, libraries=["kvm"]) - extensions = [ext, posix_extension] -# NetBSD elif NETBSD: macros.append(("PSUTIL_NETBSD", 1)) ext = Extension( 'psutil._psutil_bsd', - sources=[ + sources=sources + [ 'psutil/_psutil_bsd.c', - 'psutil/_psutil_common.c', - 'psutil/arch/bsd/netbsd.c', - 'psutil/arch/bsd/netbsd_socks.c', + 'psutil/arch/netbsd/specific.c', + 'psutil/arch/netbsd/socks.c', ], define_macros=macros, libraries=["kvm"]) - extensions = [ext, posix_extension] -# Linux elif LINUX: def get_ethtool_macro(): # see: https://github.com/giampaolo/psutil/issues/659 @@ -213,15 +206,8 @@ def get_ethtool_macro(): suffix='.c', delete=False, mode="wt") as f: f.write("#include ") - @atexit.register - def on_exit(): - try: - os.remove(f.name) - except OSError: - pass - - compiler = UnixCCompiler() try: + compiler = UnixCCompiler() with silenced_output('stderr'): with silenced_output('stdout'): compiler.compile([f.name]) @@ -229,6 +215,11 @@ def on_exit(): return ("PSUTIL_ETHTOOL_MISSING_TYPES", 1) else: return None + finally: + try: + os.remove(f.name) + except OSError: + pass macros.append(("PSUTIL_LINUX", 1)) ETHTOOL_MACRO = get_ethtool_macro() @@ -236,29 +227,59 @@ def on_exit(): macros.append(ETHTOOL_MACRO) ext = Extension( 'psutil._psutil_linux', - sources=['psutil/_psutil_linux.c'], + sources=sources + ['psutil/_psutil_linux.c'], define_macros=macros) - extensions = [ext, posix_extension] -# Solaris elif SUNOS: macros.append(("PSUTIL_SUNOS", 1)) ext = Extension( 'psutil._psutil_sunos', - sources=['psutil/_psutil_sunos.c'], + sources=sources + [ + 'psutil/_psutil_sunos.c', + 'psutil/arch/solaris/v10/ifaddrs.c', + 'psutil/arch/solaris/environ.c' + ], define_macros=macros, libraries=['kstat', 'nsl', 'socket']) - extensions = [ext, posix_extension] - +# AIX +elif AIX: + macros.append(("PSUTIL_AIX", 1)) + ext = Extension( + 'psutil._psutil_aix', + sources=sources + [ + 'psutil/_psutil_aix.c', + 'psutil/arch/aix/net_connections.c', + 'psutil/arch/aix/common.c', + 'psutil/arch/aix/ifaddrs.c'], + libraries=['perfstat'], + define_macros=macros) else: sys.exit('platform %s is not supported' % sys.platform) +if POSIX: + posix_extension = Extension( + 'psutil._psutil_posix', + define_macros=macros, + sources=sources) + if SUNOS: + posix_extension.libraries.append('socket') + if platform.release() == '5.10': + posix_extension.sources.append('psutil/arch/solaris/v10/ifaddrs.c') + posix_extension.define_macros.append(('PSUTIL_SUNOS10', 1)) + elif AIX: + posix_extension.sources.append('psutil/arch/aix/ifaddrs.c') + + extensions = [ext, posix_extension] +else: + extensions = [ext] + + def main(): - setup_args = dict( + kwargs = dict( name='psutil', version=VERSION, - description=__doc__.replace('\n', '').strip(), + description=__doc__ .replace('\n', ' ').strip() if __doc__ else '', long_description=get_description(), keywords=[ 'ps', 'top', 'kill', 'free', 'lsof', 'netstat', 'nice', 'tty', @@ -272,6 +293,7 @@ def main(): platforms='Platform Independent', license='BSD', packages=['psutil', 'psutil.tests'], + ext_modules=extensions, # see: python setup.py register --list-classifiers scripts=glob.glob('scripts/*.py'), classifiers=[ @@ -298,12 +320,9 @@ def main(): 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.0', - 'Programming Language :: Python :: 3.1', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python', @@ -319,9 +338,15 @@ def main(): 'Topic :: Utilities', ], ) - if extensions is not None: - setup_args["ext_modules"] = extensions - setup(**setup_args) + if setuptools is not None: + kwargs.update( + python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + test_suite="psutil.tests.get_suite", + tests_require=tests_require, + extras_require=extras_require, + zip_safe=False, + ) + setup(**kwargs) if __name__ == '__main__': diff --git a/tox.ini b/tox.ini index be87cf082..3bee1d5a0 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ # directory. [tox] -envlist = py26, py27, py32, py33, py34 +envlist = py26, py27, py33, py34, py35, py36 [testenv] deps = @@ -24,7 +24,7 @@ setenv = TOX = 1 commands = - python psutil/tests/runner.py + python psutil/tests/__main__.py git ls-files | grep \\.py$ | xargs flake8 # suppress "WARNING: 'git' command found but not installed in testenv