diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2018-01-01 22:04:16 +0100 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2018-01-01 22:04:16 +0100 |
commit | 9806c4801cba4c239425226d9db8c5a7f8beb902 (patch) | |
tree | 3e7feba9e15c9f08ecfdf4d9714d8dd37311d576 | |
parent | c604a55694cc70d54f7abea628d41d99842944cc (diff) | |
parent | a86c6f65c123442802c44d27e45b5e014a62fe3b (diff) | |
download | psutil-1102-proc-bitness.tar.gz |
merge from master1102-proc-bitness
77 files changed, 4912 insertions, 1389 deletions
diff --git a/.ci/travis/run.sh b/.ci/travis/run.sh index b27e6695..1501387a 100755 --- a/.ci/travis/run.sh +++ b/.ci/travis/run.sh @@ -30,6 +30,6 @@ if [ "$PYVER" == "2.7" ] || [ "$PYVER" == "3.6" ]; then PSUTIL_TESTING=1 python -Wa psutil/tests/test_memory_leaks.py # run linter (on Linux only) if [[ "$(uname -s)" != 'Darwin' ]]; then - python -Wa -m flake8 + python -m flake8 fi fi diff --git a/.git-pre-commit b/.git-pre-commit index a2f2d18e..c3c605e0 100755 --- a/.git-pre-commit +++ b/.git-pre-commit @@ -75,7 +75,7 @@ def sh(cmd): def main(): out = sh("git diff --cached --name-only") - py_files = [x for x in out.split(b'\n') if x.endswith(b'.py') and + py_files = [x for x in out.split('\n') if x.endswith('.py') and os.path.exists(x)] lineno = 0 diff --git a/.travis.yml b/.travis.yml index 0f191938..9289eb6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,31 +3,19 @@ language: python cache: pip matrix: include: + # Linux - python: 2.6 - python: 2.7 - - python: 3.3 - python: 3.4 - python: 3.5 - python: 3.6 - - "pypy" - # XXX - commented because OSX builds are deadly slow - # - language: generic - # os: osx - # env: PYVER=py26 + # 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: @@ -43,6 +43,8 @@ Github usernames of people to CC on github when in need of help. - SunOS: - wiggin15, Arnon Yaari - alxchk, Oleksii Shevchuk +- AIX: + - wiggin15, Arnon Yaari (maintainer) Contributors ============ @@ -53,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 @@ -363,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 @@ -412,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 @@ -443,7 +445,7 @@ I: 919 N: Max BĂ©langer W: https://github.com/maxbelanger -I: 936 +I: 936, 1133 N: Pierre Fersing C: France @@ -485,3 +487,31 @@ 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 6a0f08fc..2d48ced2 100644 --- a/DEVGUIDE.rst +++ b/DEVGUIDE.rst @@ -4,17 +4,23 @@ 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 test deps and GIT hooks:: +- install test deps and GIT hooks: + +.. code-block:: bash + + make setup-dev-env - $ make setup-dev-env +- run tests: -- run tests:: +.. code-block:: bash - $ make test + make test - bear in mind that ``make`` (see `Makefile <https://github.com/giampaolo/psutil/blob/master/Makefile>`_) @@ -23,6 +29,7 @@ If you plan on hacking on psutil this is what you're supposed to do first: (see `make.bat <https://github.com/giampaolo/psutil/blob/master/make.bat>`_). - 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 @@ -37,34 +44,46 @@ 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 coverage # run test coverage - $ make flake8 # run PEP8 linter + 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:: +For instance, to run a specific Python version. On UNIX: + +.. code-block:: bash make test PYTHON=python3.5 -On Windows:: +On Windows: + +.. code-block:: bat set PYTHON=C:\python35\python.exe && make test - # ...or: +...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):: +(on UNIX): + +.. code-block:: bash make test TSCRIPT=foo.py -On Windows:: +On Windows: + +.. code-block:: bat set TSCRIPT=foo.py && make test @@ -74,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 @@ -142,7 +161,7 @@ Two icons in the home page (README) always show the build status: :target: https://ci.appveyor.com/project/giampaolo/psutil :alt: Windows tests (Appveyor) -OSX, BSD and Solaris are currently tested manually (sigh!). +OSX, BSD, AIX and Solaris are currently tested manually (sigh!). Test coverage ------------- @@ -164,7 +183,7 @@ Documentation - it uses `RsT syntax <http://docutils.sourceforge.net/docs/user/rst/quickref.html>`_ and it's built with `sphinx <http://sphinx-doc.org/>`_. - doc can be built with ``make setup-dev-env; cd docs; make html``. -- public doc is hosted on http://pythonhosted.org/psutil/ +- public doc is hosted on http://psutil.readthedocs.io/ ======================= Releasing a new version @@ -184,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 aa9628b2..dd813c5b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,10 +1,113 @@ *Bug tracker at https://github.com/giampaolo/psutil/issues* -*XXXX-XX-XX* +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 @@ -65,7 +168,7 @@ - 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 proces has terminated. +- 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. @@ -101,16 +204,16 @@ - 1040_: the following Windows APIs on Python 2 now return a string instead of unicode: - Process.memory_maps().path - - WindosService.bin_path() - - WindosService.description() - - WindosService.display_name() - - WindosService.username() - -*2017-04-10* + - WindowsService.bin_path() + - WindowsService.description() + - WindowsService.display_name() + - WindowsService.username() 5.2.2 ===== +*2017-04-10* + **Bug fixes** - 1000_: fixed some setup.py warnings. @@ -122,11 +225,11 @@ - 1009_: [Linux] sensors_temperatures() may raise OSError. - 1010_: [Linux] virtual_memory() may raise ValueError on Ubuntu 14.04. -*2017-03-24* - 5.2.1 ===== +*2017-03-24* + **Bug fixes** - 981_: [Linux] cpu_freq() may return an empty list. @@ -136,11 +239,11 @@ - 997_: [FreeBSD] virtual_memory() may fail due to missing sysctl parameter on FreeBSD 12. -*2017-03-05* - 5.2.0 ===== +*2017-03-05* + **Enhancements** - 971_: [Linux] Add psutil.sensors_fans() function. (patch by Nicolas Hennion) @@ -9,7 +9,6 @@ PLATFORMS ========= - #355: Android (with patch) -- #605: AIX (with branch) - #82: Cygwin (PR at #998) - #276: GNU/Hurd - #693: Windows Nano diff --git a/INSTALL.rst b/INSTALL.rst index 8737c94a..f2a80eed 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -4,16 +4,22 @@ Install pip pip is the easiest way to install psutil. 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:: +On Linux or via wget: + +.. code-block:: bash wget https://bootstrap.pypa.io/get-pip.py -O - | python -On OSX or via curl:: +On OSX or via curl: + +.. code-block:: bash python < <(curl -s https://bootstrap.pypa.io/get-pip.py) On Windows, `download pip <https://pip.pypa.io/en/latest/installing/>`__, open -cmd.exe and install it:: +cmd.exe and install it: + +.. code-block:: bat C:\Python27\python.exe get-pip.py @@ -25,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 `Xcode <https://developer.apple.com/downloads/?name=Xcode>`__ first, then: -:: +.. code-block:: bash pip install psutil @@ -77,7 +76,9 @@ Windows The easiest way to install psutil on Windows is to just use the pre-compiled exe/wheel installers hosted on -`PYPI <https://pypi.python.org/pypi/psutil/#downloads>`__ via pip:: +`PYPI <https://pypi.python.org/pypi/psutil/#downloads>`__ via pip: + +.. code-block:: bat C:\Python27\python.exe -m pip install psutil @@ -92,7 +93,9 @@ Compiling 64 bit versions of Python 2.6 and 2.7 with VS 2008 requires `Windows SDK and .NET Framework 3.5 SP1 <https://www.microsoft.com/en-us/download/details.aspx?id=3138>`__. Once installed run vcvars64.bat, then you can finally compile (see `here <http://stackoverflow.com/questions/11072521/>`__). -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 @@ -100,7 +103,7 @@ To compile / install psutil from sources on Windows run:: FreeBSD ======= -:: +.. code-block:: bash pkg install python gcc python -m pip install psutil @@ -108,7 +111,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 @@ -117,7 +120,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 @@ -129,13 +132,13 @@ 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 @@ -143,7 +146,7 @@ Install: Install from sources ==================== -:: +.. code-block:: bash git clone https://github.com/giampaolo/psutil.git cd psutil diff --git a/MANIFEST.in b/MANIFEST.in index 89c42216..7a92a4e5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -13,26 +13,25 @@ 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/_template/globaltoc.html -include docs/_template/indexcontent.html -include docs/_template/indexsidebar.html -include docs/_template/page.html -include docs/_themes/pydoctheme/static/pydoctheme.css -include docs/_themes/pydoctheme/theme.conf 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 @@ -43,6 +42,13 @@ 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 @@ -76,6 +82,7 @@ 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 @@ -5,7 +5,6 @@ PYTHON = python TSCRIPT = psutil/tests/__main__.py ARGS = - # List of nice-to-have dev libs. DEPS = \ argparse \ @@ -23,10 +22,11 @@ DEPS = \ sphinx \ twine \ unittest2 \ - requests + 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 @@ -34,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 \ @@ -60,9 +59,7 @@ clean: _: - -# Compile without installing. -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 @@ -73,21 +70,16 @@ build: _ 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: +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: - PYTHONWARNINGS=all $(PYTHON) -c \ +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; \ pyexc = 'from urllib.request import urlopen' if sys.version_info[0] == 3 else 'from urllib2 import urlopen'; \ @@ -105,8 +97,7 @@ install-pip: f.close(); \ sys.exit(code);" -# Install GIT hooks, pip, test deps (also upgrades them). -setup-dev-env: +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 @@ -116,68 +107,55 @@ setup-dev-env: # Tests # =================================================================== -# Run all tests. -test: +test: ## Run all tests. ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) $(TSCRIPT) + $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) -# Run process-related API tests. -test-process: +test-process: ## Run process-related API tests. ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) -m unittest -v psutil.tests.test_process + $(TEST_PREFIX) $(PYTHON) -m unittest -v psutil.tests.test_process -# Run system-related API tests. -test-system: +test-system: ## Run system-related API tests. ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) -m unittest -v psutil.tests.test_system + $(TEST_PREFIX) $(PYTHON) -m unittest -v psutil.tests.test_system -# Run miscellaneous tests. -test-misc: +test-misc: ## Run miscellaneous tests. ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) psutil/tests/test_misc.py + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_misc.py -# Test APIs dealing with strings. -test-unicode: +test-unicode: ## Test APIs dealing with strings. ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) psutil/tests/test_unicode.py + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_unicode.py -# APIs sanity tests. -test-contracts: +test-contracts: ## APIs sanity tests. ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) psutil/tests/test_contracts.py + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_contracts.py -# Test net_connections() and Process.connections(). -test-connections: +test-connections: ## Test net_connections() and Process.connections(). ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) psutil/tests/test_connections.py + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_connections.py -# POSIX specific tests. -test-posix: +test-posix: ## POSIX specific tests. ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) psutil/tests/test_posix.py + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_posix.py -# Run specific platform tests only. -test-platform: +test-platform: ## Run specific platform tests only. ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(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_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 -# Memory leak tests. -test-memleaks: +test-memleaks: ## Memory leak tests. ${MAKE} install - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) psutil/tests/test_memory_leaks.py + $(TEST_PREFIX) $(PYTHON) psutil/tests/test_memory_leaks.py -# Run a specific test by name, e.g. -# make test-by-name psutil.tests.test_system.TestSystemAPIs.test_cpu_times -test-by-name: +test-by-name: ## e.g. make test-by-name ARGS=psutil.tests.test_system.TestSystemAPIs ${MAKE} install - @PSUTIL_TESTING=1 PYTHONWARNINGS=all $(PYTHON) -m unittest -v $(ARGS) + @$(TEST_PREFIX) $(PYTHON) -m unittest -v $(ARGS) -# Run test coverage. -coverage: +test-coverage: ## Run test coverage. ${MAKE} install # Note: coverage options are controlled by .coveragerc file rm -rf .coverage htmlcov - PSUTIL_TESTING=1 PYTHONWARNINGS=all $(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 @@ -187,27 +165,25 @@ coverage: # 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 @@ -215,86 +191,79 @@ install-git-hooks: # Distribution # =================================================================== -# Generate tar.gz source distribution. -sdist: - ${MAKE} clean +# --- create + +sdist: ## Create tar.gz source distribution. ${MAKE} generate-manifest - PYTHONWARNINGS=all $(PYTHON) setup.py sdist + $(PYTHON) setup.py sdist -# Upload source tarball on https://pypi.python.org/pypi/psutil. -upload-src: - ${MAKE} sdist - PYTHONWARNINGS=all $(PYTHON) setup.py sdist upload +wheel: ## Generate wheel. + $(PYTHON) setup.py bdist_wheel -# Download exes/wheels hosted on appveyor. -win-download-exes: - PYTHONWARNINGS=all $(PYTHON) scripts/internal/download_exes.py --user giampaolo --project psutil +win-download-wheels: ## Download wheels hosted on appveyor. + $(TEST_PREFIX) $(PYTHON) scripts/internal/download_exes.py --user giampaolo --project psutil -# Upload exes/wheels in dist/* directory to PYPI. -win-upload-exes: - PYTHONWARNINGS=all $(PYTHON) -m twine upload dist/*.exe - PYTHONWARNINGS=all $(PYTHON) -m twine upload dist/*.whl +# --- upload -# All the necessary steps before making a release. -pre-release: - @PYTHONWARNINGS=all $(PYTHON) -c "import subprocess, sys; out = subprocess.check_output('git diff-index HEAD --', shell=True).strip(); sys.exit('there are uncommitted changes') if out else sys.exit(0);" +upload-src: ## Upload source tarball on https://pypi.python.org/pypi/psutil. ${MAKE} sdist + $(PYTHON) setup.py sdist upload + +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 - @PYTHONWARNINGS=all $(PYTHON) -c \ + ${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, 'XXXX in HISTORY.rst';" - ${MAKE} win-download-exes + $(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 - PYTHONWARNINGS=all $(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: - @PYTHONWARNINGS=all $(PYTHON) scripts/internal/print_announce.py +print-announce: ## Print announce of new release. + @$(TEST_PREFIX) $(PYTHON) scripts/internal/print_announce.py -# Print releases' timeline. -print-timeline: - @PYTHONWARNINGS=all $(PYTHON) scripts/internal/print_timeline.py +print-timeline: ## Print releases' timeline. + @$(TEST_PREFIX) $(PYTHON) scripts/internal/print_timeline.py -# Inspect MANIFEST.in file. -check-manifest: - PYTHONWARNINGS=all $(PYTHON) -m check_manifest -v $(ARGS) +check-manifest: ## Inspect MANIFEST.in file. + $(PYTHON) -m check_manifest -v $(ARGS) -# Generates MANIFEST.in file. -generate-manifest: - @PYTHONWARNINGS=all $(PYTHON) scripts/internal/generate_manifest.py > MANIFEST.in +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: +bench-oneshot: ## Benchmarks for oneshot() ctx manager (see #799). ${MAKE} install - PYTHONWARNINGS=all $(PYTHON) scripts/internal/bench_oneshot.py + $(TEST_PREFIX) $(PYTHON) scripts/internal/bench_oneshot.py -# same as above but using perf module (supposed to be more precise) -bench-oneshot-2: +bench-oneshot-2: ## Same as above but using perf module (supposed to be more precise) ${MAKE} install - PYTHONWARNINGS=all $(PYTHON) scripts/internal/bench_oneshot_2.py + $(TEST_PREFIX) $(PYTHON) scripts/internal/bench_oneshot_2.py -# generate a doc.zip file and manually upload it to PYPI. -doc: - cd docs && make html && cd _build/html/ && zip doc.zip -r . - mv docs/_build/html/doc.zip . - @echo "done; now manually upload doc.zip from here: https://pypi.python.org/pypi?:action=pkg_edit&name=psutil" - -# check whether the links mentioned in some files are valid. -check-broken-links: +check-broken-links: ## Look for broken links in source files. git ls-files | xargs $(PYTHON) -Wa scripts/internal/check_broken_links.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}' @@ -10,6 +10,10 @@ :target: https://coveralls.io/github/giampaolo/psutil?branch=master :alt: Test coverage (coverall.io) +.. 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 <https://github.com/giampaolo/psutil>`_ - `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_ -- `Documentation <http://pythonhosted.org/psutil/>`_ +- `Documentation <http://psutil.readthedocs.io>`_ - `Download <https://pypi.python.org/pypi?:action=display&name=psutil#downloads>`_ - `Forum <http://groups.google.com/group/psutil/topics>`_ - `Blog <http://grodola.blogspot.com/search/label/psutil>`_ @@ -44,14 +48,20 @@ retrieving information on **running processes** and **system utilization** (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 command line tools such as: +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.6** (users of Python 2.4 and 2.5 may use -`2.1.3 <https://pypi.python.org/pypi?name=psutil&version=2.1.3&:action=files>`__ version). +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 <http://pypy.org/>`__ is also known to work. ==================== @@ -67,14 +77,16 @@ Example applications +------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+ Also see `scripts directory <https://github.com/giampaolo/psutil/tree/master/scripts>`__ -and `doc recipes <https://pythonhosted.org/psutil/#recipes/>`__. +and `doc recipes <http://psutil.readthedocs.io/#recipes/>`__. ===================== Projects using psutil ===================== -At the time of writing there are over -`5400 open source projects <https://libraries.io/pypi/psutil/dependent_repositories?page=1>`__ +At the time of writing psutil has roughly +`2.9 milion downloads <https://github.com/giampaolo/psutil/issues/1053#issuecomment-340166262>`__ +per month and there are over +`6000 open source projects <https://libraries.io/pypi/psutil/dependent_repositories?page=1>`__ on github which depend from psutil. Here's some I find particularly interesting: @@ -83,7 +95,7 @@ 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 @@ -438,7 +450,7 @@ Windows services Other samples ============= -See `doc recipes <https://pythonhosted.org/psutil/#recipes/>`__. +See `doc recipes <http://psutil.readthedocs.io/#recipes>`__. ====== Author diff --git a/appveyor.yml b/appveyor.yml index d4671784..092dc23a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,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" @@ -42,10 +38,6 @@ environment: 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" @@ -91,11 +83,10 @@ build: off test_script: - "%WITH_COMPILER% %PYTHON%/python -V" - - "set PSUTIL_TESTING=1 && %WITH_COMPILER% %PYTHON%/python psutil/tests/__main__.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\* diff --git a/docs/Makefile b/docs/Makefile index a69fc329..0c4bdf48 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 <target>' where <target> 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 3aaea8a5..8ceb5f21 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 00000000..b76f442a --- /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 f5fbb406..00000000 --- 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. -#} -<h3>{{ _('Manual') }}</h3> -{{ toctree() }} -<a href="{{ pathto(master_doc) }}">Back to Welcome</a> diff --git a/docs/_template/indexcontent.html b/docs/_template/indexcontent.html deleted file mode 100644 index dd5e7249..00000000 --- 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 903675d1..00000000 --- a/docs/_template/indexsidebar.html +++ /dev/null @@ -1,8 +0,0 @@ -<h3>Useful links</h3> -<ul> - <li><a href="https://github.com/giampaolo/psutil">Github project</a></li> - <li><a href="http://grodola.blogspot.com/search/label/psutil">Blog</a></li> - <li><a href="https://pypi.python.org/pypi?:action=display&name=psutil#downloads">Download</a></li> - <li><a href="https://github.com/giampaolo/psutil/issues">Issues</a></li> - <li><a href="http://groups.google.com/group/psutil/topics">Forum</a></li> -</ul> diff --git a/docs/_template/page.html b/docs/_template/page.html deleted file mode 100644 index 04b47b41..00000000 --- a/docs/_template/page.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends "!page.html" %} -{% block extrahead %} -{{ super() }} -{% if not embedded %}<script type="text/javascript" src="{{ pathto('_static/copybutton.js', 1) }}"></script>{% endif %} -<script type="text/javascript"> - - // Store editor pop-up help state in localStorage - // so it does not re-pop-up itself between page loads. - // Do not even to pretend to support IE gracefully. - (function($) { - - $(document).ready(function() { - var box = $("#editor-trap"); - var klass = "toggled"; - var storageKey = "toggled"; - - function toggle() { - box.toggleClass(klass); - // Store the toggle status in local storage as "has value string" or null - window.localStorage.setItem(storageKey, box.hasClass(klass) ? "toggled" : "not-toggled"); - } - - box.click(toggle); - - // Check the persistent state of the editor pop-up - // Note that localStorage does not necessarily support boolean values (ugh!) - // http://stackoverflow.com/questions/3263161/cannot-set-boolean-values-in-localstorage - var v = window.localStorage.getItem(storageKey); - if(v == "toggled" || !v) { - box.addClass(klass); - } - - }); - - })(jQuery); -</script> -<script type="text/javascript"> - - var _gaq = _gaq || []; - _gaq.push(['_setAccount', 'UA-2097050-4']); - _gaq.push(['_trackPageview']); - - (function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })(); - -</script> -{% endblock %} - -{% block rootrellink %} - <li><a href="https://github.com/giampaolo/psutil/"><img src="{{ pathto('_static/logo.png', 1) }}" style="height: 30px; vertical-align: middle; padding-right: 1em;" /> Project Homepage</a>{{ reldelim1 }}</li> - <li><a href="{{ pathto('index') }}">{{ shorttitle }}</a>{{ reldelim1 }}</li> -{% endblock %} - - -{% block footer %} -<div class="footer"> - © Copyright {{ copyright|e }}. - <br /> - Last updated on {{ last_updated|e }}. - <br /> - Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> {{ sphinx_version|e }}. -</div> -{% 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 c6f19ab7..00000000 --- a/docs/_themes/pydoctheme/static/pydoctheme.css +++ /dev/null @@ -1,197 +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; -} - -.data { - margin-top: 4px !important; - margin-bottom: 4px !important; -} - -.data dd { - margin-top: 0px !important; - margin-bottom: 0px !important; -} diff --git a/docs/_themes/pydoctheme/theme.conf b/docs/_themes/pydoctheme/theme.conf deleted file mode 100644 index 95b97e53..00000000 --- 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 f0a206db..df825cbd 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__)) @@ -39,7 +49,8 @@ def get_version(): 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 @@ -51,12 +62,16 @@ extensions = ['sphinx.ext.autodoc', '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. @@ -65,6 +80,7 @@ master_doc = 'index' # 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 @@ -72,35 +88,47 @@ copyright = '2009-%s, %s' % (THIS_YEAR, AUTHOR) # # 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. @@ -109,141 +137,240 @@ pygments_style = 'sphinx' # 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 -# "<project> v<release> documentation". -html_title = "{project} {version} documentation".format(**locals()) +# The name for this set of Sphinx documents. +# "<project> v<release> 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 <link> 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 36123d96..b35c89bf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,13 +8,13 @@ psutil documentation Quick links ----------- -* `Home page <https://github.com/giampaolo/psutil>`__ -* `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_ -* `Blog <http://grodola.blogspot.com/search/label/psutil>`__ -* `Forum <http://groups.google.com/group/psutil/topics>`__ -* `Download <https://pypi.python.org/pypi?:action=display&name=psutil#downloads>`__ -* `Development guide <https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst>`_ -* `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`__ +- `Home page <https://github.com/giampaolo/psutil>`__ +- `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_ +- `Blog <http://grodola.blogspot.com/search/label/psutil>`__ +- `Forum <http://groups.google.com/group/psutil/topics>`__ +- `Download <https://pypi.python.org/pypi?:action=display&name=psutil#downloads>`__ +- `Development guide <https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst>`_ +- `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`__ About ----- @@ -25,11 +25,19 @@ retrieving information on running 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 command line tools +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 +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 <https://pypi.python.org/pypi?name=psutil&version=2.1.3&:action=files>`__ version). `PyPy <http://pypy.org/>`__ is also known to work. @@ -155,12 +163,14 @@ CPU Return the number of logical CPUs in the system (same as `os.cpu_count() <http://docs.python.org/3/library/os.html#os.cpu_count>`__ - in Python 3.4). - This number is not equivalent to the number of CPUs the current process can - use. The number of usable CPUs can be obtained with + 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: @@ -170,6 +180,11 @@ CPU >>> 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 named tuple: @@ -404,6 +419,8 @@ Disks 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() @@ -538,7 +555,7 @@ Network | ``"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 <https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py>`__. @@ -553,8 +570,8 @@ Network ...] .. note:: - (OSX) :class:`psutil.AccessDenied` is always raised unless running as root. - This is a limitation of the OS and ``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. @@ -687,7 +704,7 @@ Sensors .. warning:: - This API is experimental. Backward incompatible changes may occur if + this API is experimental. Backward incompatible changes may occur if deemed necessary. .. function:: sensors_fans() @@ -711,7 +728,7 @@ Sensors .. warning:: - This API is experimental. Backward incompatible changes may occur if + this API is experimental. Backward incompatible changes may occur if deemed necessary. .. function:: sensors_battery() @@ -751,9 +768,11 @@ Sensors .. versionadded:: 5.1.0 + .. versionchanged:: 5.4.2 added OSX support + .. warning:: - This API is experimental. Backward incompatible changes may occur if + this API is experimental. Backward incompatible changes may occur if deemed necessary. Other system info @@ -830,9 +849,9 @@ Functions Sorting order in which processes are returned is 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 and the resulting - dict is stored as a ``info`` attribute which is attached to the returned - :class:`Process` instance. + 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:: @@ -900,8 +919,8 @@ Functions ``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 tunction will return as soon as all processes terminate or when - *timeout* occurs, if specified. + 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: @@ -986,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), @@ -1003,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() @@ -1046,45 +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:`cpu_num` | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`cpu_num` | :meth:`name` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_percent` | :meth:`cmdline` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`~Process.cpu_times` | :meth:`io_counters()` | :meth:`memory_info` | :meth:`~Process.cpu_times` | :meth:`create_time` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`create_time` | :meth:`ionice` | :meth:`memory_percent` | :meth:`create_time` | | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`name` | :meth:`memory_info` | :meth:`num_ctx_switches` | :meth:`gids` | :meth:`memory_info` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`ppid` | :meth:`nice` | :meth:`num_threads` | :meth:`io_counters` | :meth:`memory_percent` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`status` | :meth:`memory_maps` | | :meth:`name` | :meth:`nice` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`terminal` | :meth:`num_ctx_switches` | :meth:`create_time` | :meth:`memory_info` | :meth:`num_threads` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | | :meth:`num_handles` | :meth:`gids` | :meth:`memory_percent` | :meth:`ppid` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`gids` | :meth:`num_threads` | :meth:`name` | :meth:`num_ctx_switches` | :meth:`status` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`num_ctx_switches` | :meth:`username` | :meth:`ppid` | :meth:`ppid` | :meth:`terminal` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`num_threads` | | :meth:`status` | :meth:`status` | | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`uids` | | :meth:`terminal` | :meth:`terminal` | :meth:`gids` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | :meth:`username` | | :meth:`uids` | :meth:`uids` | :meth:`uids` | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ - | | | :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* | - +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+ + +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+--------------------------+ + | 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 @@ -1138,7 +1157,7 @@ Process class Availability: Linux, OSX, Windows, SunOS .. versionadded:: 4.0.0 - .. versionchanged:: 5.3.0: added SunOS support + .. versionchanged:: 5.3.0 added SunOS support .. method:: create_time() @@ -1351,7 +1370,7 @@ Process class >>> p.io_counters() pio(read_count=454556, write_count=3456, read_bytes=110592, write_bytes=0, read_chars=769931, write_chars=203) - Availability: all platforms except OSX and Solaris + Availability: Linux, BSD, Windows, AIX .. versionchanged:: 5.2.0 added *read_chars* and *write_chars* on Linux; added *other_count* and *other_bytes* on Windows. @@ -1361,6 +1380,8 @@ Process class The number voluntary and involuntary context switches performed by this process (cumulative). + .. versionchanged:: 5.4.1 added AIX support + .. method:: num_fds() The number of file descriptors currently opened by this process @@ -1496,33 +1517,33 @@ Process class 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. @@ -1693,13 +1714,13 @@ Process class 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. + 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*: :: @@ -1719,7 +1740,7 @@ 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 <https://github.com/giampaolo/psutil/blob/fb9ae861cf3cf175c3da4a3cd4e558c6cbd6af91/psutil/tests/test_process.py#L1236-L1247>`__. + `unit test <https://github.com/giampaolo/psutil/blob/65a52341b55faaab41f68ebc4ed31f18f0929754/psutil/tests/test_process.py#L1064-L1075>`__. See also how to `kill a process tree <#kill-process-tree>`__ and `terminate my children <#terminate-my-children>`__. @@ -1855,6 +1876,10 @@ Process class (OpenBSD) "laddr" and "raddr" fields for UNIX sockets are always set to "". This is a limitation of the OS. + .. note:: + (AIX) :class:`psutil.AccessDenied` is always raised unless running + as root (lsof does the same). + .. versionchanged:: 5.3.0 : "laddr" and "raddr" are named tuples. .. method:: is64bit() @@ -1928,14 +1953,15 @@ Process class .. 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 @@ -2104,16 +2130,18 @@ Constants .. data:: OPENBSD .. data:: BSD .. data:: SUNOS +.. data:: AIX ``bool`` constants which define what platform you're on. E.g. if on Windows, :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 + 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 or if you want to retrieve @@ -2126,10 +2154,11 @@ Constants 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 + 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 @@ -2415,13 +2444,13 @@ resources. for p in procs: p.terminate() gone, alive = psutil.wait_procs(procs, timeout=timeout, callback=on_terminate) - if not alive: + 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 not alive: + if alive: # give up for p in alive: print("process {} survived SIGKILL; giving up" % p) @@ -2506,8 +2535,8 @@ Top 3 processes opening more file descriptors:: (2721, {'name': 'chrome', 'num_fds': 185}), (2650, {'name': 'chrome', 'num_fds': 354})] -Q&A -=== +FAQs +==== * Q: What Windows versions are supported? * A: From Windows **Vista** onwards, both 32 and 64 bit versions. @@ -2520,6 +2549,13 @@ Q&A ---- +* 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 <https://pypi.python.org/pypi?name=psutil&version=2.1.3&:action=files>`__. + PyPy is also known to work. + +---- + * Q: What SunOS versions are supported? * A: From Solaris 10 onwards. @@ -2542,7 +2578,7 @@ Q&A * Q: What about load average? * A: psutil does not expose any load average function as it's already available in python as - `os.getloadavg <https://docs.python.org/2/library/os.html#os.getloadavg>`__ + `os.getloadavg <https://docs.python.org/2/library/os.html#os.getloadavg>`__. Running tests ============= @@ -2566,6 +2602,30 @@ take a look at the Timeline ======== +- 2018-01-01: + `5.4.3 <https://pypi.python.org/pypi?name=psutil&version=5.4.3&:action=files>`__ - + `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#542>`__ - + `diff <https://github.com/giampaolo/psutil/compare/release-5.4.2...release-5.4.3#files_bucket>`__ +- 2017-12-07: + `5.4.2 <https://pypi.python.org/pypi?name=psutil&version=5.4.2&:action=files>`__ - + `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#542>`__ - + `diff <https://github.com/giampaolo/psutil/compare/release-5.4.1...release-5.4.2#files_bucket>`__ +- 2017-11-08: + `5.4.1 <https://pypi.python.org/pypi?name=psutil&version=5.4.1&:action=files>`__ - + `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#541>`__ - + `diff <https://github.com/giampaolo/psutil/compare/release-5.4.0...release-5.4.1#files_bucket>`__ +- 2017-10-12: + `5.4.0 <https://pypi.python.org/pypi?name=psutil&version=5.4.0&:action=files>`__ - + `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#540>`__ - + `diff <https://github.com/giampaolo/psutil/compare/release-5.3.1...release-5.4.0#files_bucket>`__ +- 2017-09-10: + `5.3.1 <https://pypi.python.org/pypi?name=psutil&version=5.3.1&:action=files>`__ - + `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#530>`__ - + `diff <https://github.com/giampaolo/psutil/compare/release-5.3.0...release-5.3.1#files_bucket>`__ +- 2017-09-01: + `5.3.0 <https://pypi.python.org/pypi?name=psutil&version=5.3.0&:action=files>`__ - + `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#530>`__ - + `diff <https://github.com/giampaolo/psutil/compare/release-5.2.2...release-5.3.0#files_bucket>`__ - 2017-04-10: `5.2.2 <https://pypi.python.org/pypi?name=psutil&version=5.2.2&:action=files>`__ - `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#522>`__ - diff --git a/psutil/DEVNOTES b/psutil/DEVNOTES new file mode 100644 index 00000000..4fd15ea3 --- /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 9c664a0c..8f929846 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -11,10 +11,11 @@ sensors) in Python. Supported platforms: - Linux - Windows - OSX - - Sun Solaris - FreeBSD - OpenBSD - NetBSD + - Sun Solaris + - AIX Works with Python versions from 2.6 to 3.X. """ @@ -23,6 +24,7 @@ from __future__ import division import collections import contextlib +import datetime import errno import functools import os @@ -73,6 +75,7 @@ from ._common import NIC_DUPLEX_FULL 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 @@ -83,6 +86,12 @@ from ._common import POSIX # NOQA 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. @@ -158,6 +167,13 @@ elif SUNOS: # _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" + else: # pragma: no cover raise NotImplementedError('platform %s is not supported' % sys.platform) @@ -185,7 +201,7 @@ __all__ = [ "POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED", "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "OSX", "POSIX", "SUNOS", - "WINDOWS", + "WINDOWS", "AIX", # classes "Process", "Popen", @@ -203,7 +219,7 @@ __all__ = [ ] __all__.extend(_psplatform.__extra__all__) __author__ = "Giampaolo Rodola'" -__version__ = "5.3.0" +__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 @@ -235,112 +251,29 @@ if (int(__version__.replace('.', '')) != # ===================================================================== -# --- exceptions +# --- Utils # ===================================================================== -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 = "%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): - 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 +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): @@ -355,6 +288,22 @@ def _assert_pid_not_reused(fun): 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. @@ -440,21 +389,26 @@ class Process(object): 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 @@ -785,7 +739,7 @@ class Process(object): """ 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): @@ -908,13 +862,15 @@ class Process(object): """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): @@ -943,73 +899,47 @@ class Process(object): 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): @@ -1157,13 +1087,11 @@ class Process(object): ('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) @@ -1179,7 +1107,6 @@ class Process(object): 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. @@ -2308,7 +2235,7 @@ if hasattr(_psplatform, "sensors_fans"): __all__.append("sensors_fans") -# Linux, Windows, FreeBSD +# Linux, Windows, FreeBSD, OSX if hasattr(_psplatform, "sensors_battery"): def sensors_battery(): @@ -2378,8 +2305,6 @@ 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 %7s %7s %-13s %5s %7s %s" attrs = ['pid', 'memory_percent', 'name', 'cpu_times', 'create_time', diff --git a/psutil/_common.py b/psutil/_common.py index 7c4af3d8..870971e4 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -81,6 +81,7 @@ OPENBSD = sys.platform.startswith("openbsd") 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") # =================================================================== @@ -460,14 +461,14 @@ 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 diff --git a/psutil/_exceptions.py b/psutil/_exceptions.py new file mode 100644 index 00000000..c08e6d83 --- /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 00000000..9abc8d17 --- /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 da264ef3..c2c926e2 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -27,6 +27,9 @@ from ._common import sockfam_to_enum 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__ = [] @@ -128,12 +131,6 @@ kinfo_proc_map = dict( name=24, ) -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -ZombieProcess = None -AccessDenied = None -TimeoutExpired = None - # ===================================================================== # --- named tuples @@ -394,9 +391,12 @@ 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) - laddr = _common.addr(*laddr) - raddr = _common.addr(*raddr) type = socktype_to_enum(type) nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid) ret.add(nt) @@ -719,8 +719,10 @@ class Process(object): except KeyError: status = TCP_STATUSES[cext.PSUTIL_CONN_NONE] if fam in (AF_INET, AF_INET6): - laddr = _common.addr(*laddr) - raddr = _common.addr(*raddr) + 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) @@ -737,8 +739,10 @@ class Process(object): for item in rawlist: fd, fam, type, laddr, raddr, status = item if fam in (AF_INET, AF_INET6): - laddr = _common.addr(*laddr) - raddr = _common.addr(*raddr) + 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] @@ -753,10 +757,7 @@ class Process(object): @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 b55f4197..06f1aa6b 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -41,6 +41,9 @@ from ._compat import b 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 @@ -137,12 +140,6 @@ TCP_STATUSES = { "0B": _common.CONN_CLOSING } -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -ZombieProcess = None -AccessDenied = None -TimeoutExpired = None - # ===================================================================== # --- named tuples @@ -1142,21 +1139,22 @@ def sensors_temperatures(): 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) - try: - current = float(cat(base + '_input')) / 1000.0 - except OSError as err: - # https://github.com/giampaolo/psutil/issues/1009 - # https://github.com/giampaolo/psutil/issues/1101 - if err.errno in (errno.EIO, errno.ENODEV): - warnings.warn("ignoring %r" % err, RuntimeWarning) - continue - else: - raise if high is not None: high = float(high) / 1000.0 @@ -1169,8 +1167,8 @@ def sensors_temperatures(): def sensors_fans(): - """Return hardware (CPU and others) fans as a dict - including hardware label, current speed. + """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 @@ -1187,11 +1185,14 @@ def sensors_fans(): 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) - current = int(cat(base + '_input')) - ret[unit_name].append(_common.sfan(label, current)) return dict(ret) @@ -1355,6 +1356,30 @@ def pid_exists(pid): 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): """Decorator which translates bare OSError and IOError exceptions into NoSuchProcess and AccessDenied. @@ -1470,9 +1495,17 @@ class Process(object): 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): @@ -1532,10 +1565,7 @@ class Process(object): @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): diff --git a/psutil/_psosx.py b/psutil/_psosx.py index a38efc5b..f149980f 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -23,6 +23,9 @@ from ._common import parse_environ_block 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__ = [] @@ -85,12 +88,6 @@ pidtaskinfo_map = dict( volctxsw=7, ) -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -ZombieProcess = None -AccessDenied = None -TimeoutExpired = None - # ===================================================================== # --- named tuples @@ -212,6 +209,29 @@ def disk_partitions(all=False): # ===================================================================== +# --- 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 # ===================================================================== @@ -282,7 +302,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 @@ -503,10 +538,7 @@ class Process(object): @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/_psposix.py b/psutil/_psposix.py index 66d81a3d..6bb8444d 100644 --- a/psutil/_psposix.py +++ b/psutil/_psposix.py @@ -15,14 +15,10 @@ from ._common import sdiskusage 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) diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py index 9931d885..5471d5aa 100644 --- a/psutil/_pssunos.py +++ b/psutil/_pssunos.py @@ -24,6 +24,9 @@ from ._common import socktype_to_enum 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"] @@ -78,12 +81,6 @@ proc_info_map = dict( status=6, ttynr=7) -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -ZombieProcess = None -AccessDenied = None -TimeoutExpired = None - # ===================================================================== # --- named tuples @@ -266,8 +263,10 @@ def net_connections(kind, _pid=-1): if type_ not in types: continue if fam in (AF_INET, AF_INET6): - laddr = _common.addr(*laddr) - raddr = _common.addr(*raddr) + 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_) @@ -723,7 +722,4 @@ class Process(object): @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 00000000..916254d5 --- /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 <Python.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/proc.h> +#include <sys/sysinfo.h> +#include <sys/procfs.h> +#include <sys/socket.h> +#include <sys/thread.h> +#include <fcntl.h> +#include <utmp.h> +#include <utmpx.h> +#include <mntent.h> +#include <sys/ioctl.h> +#include <sys/tihdr.h> +#include <stropts.h> +#include <netinet/tcp_fsm.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <libperfstat.h> + +#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/<pid>/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 64e1e7f7..09ae0c30 100644 --- a/psutil/_psutil_bsd.c +++ b/psutil/_psutil_bsd.c @@ -475,7 +475,7 @@ 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; } @@ -986,8 +986,8 @@ PsutilMethods[] = { #endif // --- others - {"py_psutil_testing", py_psutil_testing, METH_VARARGS, - "Return True if PSUTIL_TESTING env var is set"}, + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, {NULL, NULL, 0, NULL} }; @@ -1090,6 +1090,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 dace4724..908dbf14 100644 --- a/psutil/_psutil_common.c +++ b/psutil/_psutil_common.c @@ -9,14 +9,41 @@ #include <Python.h> #include <stdio.h> + +// 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; @@ -25,63 +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; } -static int _psutil_testing = -1; - - /* - * Return 1 if PSUTIL_TESTING env var is set else 0. + * 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_testing(void) { - if (_psutil_testing == -1) { - if (getenv("PSUTIL_TESTING") != NULL) - _psutil_testing = 1; - else - _psutil_testing = 0; - } - return _psutil_testing; +PyObject * +psutil_set_testing(PyObject *self, PyObject *args) { + PSUTIL_TESTING = 1; + Py_INCREF(Py_None); + return Py_None; } /* - * Return True if PSUTIL_TESTING env var is set else False. + * Print a debug message on stderr. No-op if PSUTIL_DEBUG env var is not set. */ -PyObject * -py_psutil_testing(PyObject *self, PyObject *args) { - PyObject *res; - res = psutil_testing() ? Py_True : Py_False; - Py_INCREF(res); - return res; +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); } /* - * 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 + * Called on module import on all platforms. */ -#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); +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 13404532..3db3f5ed 100644 --- a/psutil/_psutil_common.h +++ b/psutil/_psutil_common.h @@ -6,14 +6,20 @@ #include <Python.h> +extern int PSUTIL_TESTING; +extern int PSUTIL_DEBUG; + // a signaler for connections without an actual status static const int PSUTIL_CONN_NONE = 128; -PyObject* AccessDenied(void); -PyObject* NoSuchProcess(void); -int psutil_testing(void); -PyObject* py_psutil_testing(PyObject *self, PyObject *args); #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 a15ebe5c..d1f0d145 100644 --- a/psutil/_psutil_linux.c +++ b/psutil/_psutil_linux.c @@ -479,7 +479,7 @@ psutil_users(PyObject *self, PyObject *args) { "(OOOfOi)", py_username, // username py_tty, // tty - py_username, // hostname + py_hostname, // hostname (float)ut->ut_tv.tv_sec, // tstamp py_user_proc, // (bool) user process ut->ut_pid // process id @@ -607,8 +607,8 @@ PsutilMethods[] = { #endif // --- others - {"py_psutil_testing", py_psutil_testing, METH_VARARGS, - "Return True if PSUTIL_TESTING env var is set"}, + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, {NULL, NULL, 0, NULL} }; @@ -713,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 6e37bca5..f43bb010 100644 --- a/psutil/_psutil_osx.c +++ b/psutil/_psutil_osx.c @@ -38,6 +38,8 @@ #include <IOKit/storage/IOBlockStorageDriver.h> #include <IOKit/storage/IOMedia.h> #include <IOKit/IOBSD.h> +#include <IOKit/ps/IOPowerSources.h> +#include <IOKit/ps/IOPSKeys.h> #include "_psutil_common.h" #include "_psutil_posix.h" @@ -273,9 +275,9 @@ 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; } return PyUnicode_DecodeFSDefault(buf); @@ -345,7 +347,16 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { err = task_for_pid(mach_task_self(), (pid_t)pid, &task); if (err != KERN_SUCCESS) { - psutil_raise_for_pid(pid, "task_for_pid() failed"); + 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; } @@ -356,8 +367,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++; } @@ -385,8 +403,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; } @@ -569,9 +588,9 @@ 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; } @@ -1016,9 +1035,9 @@ psutil_proc_threads(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(""); goto error; } @@ -1028,7 +1047,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 @@ -1138,7 +1157,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) { @@ -1157,7 +1175,8 @@ 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; } } @@ -1176,7 +1195,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 } } @@ -1267,7 +1288,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; } } @@ -1785,6 +1807,92 @@ 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. @@ -1851,10 +1959,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 - {"py_psutil_testing", py_psutil_testing, METH_VARARGS, - "Return True if PSUTIL_TESTING env var is set"}, + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, {NULL, NULL, 0, NULL} }; @@ -1935,6 +2045,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 80c1b8cb..cc827273 100644 --- a/psutil/_psutil_posix.c +++ b/psutil/_psutil_posix.c @@ -18,12 +18,15 @@ #ifdef PSUTIL_SUNOS10 #include "arch/solaris/v10/ifaddrs.h" +#elif PSUTIL_AIX + #include "arch/aix/ifaddrs.h" #else #include <ifaddrs.h> #endif #if defined(PSUTIL_LINUX) #include <netdb.h> + #include <linux/types.h> #include <linux/if_packet.h> #elif defined(PSUTIL_BSD) || defined(PSUTIL_OSX) #include <netdb.h> @@ -35,6 +38,8 @@ #elif defined(PSUTIL_SUNOS) #include <netdb.h> #include <sys/sockio.h> +#elif defined(PSUTIL_AIX) + #include <netdb.h> #endif #include "_psutil_common.h" @@ -107,16 +112,21 @@ psutil_pid_exists(long pid) { * This will always set a Python exception and return NULL. */ int -psutil_raise_for_pid(long pid, char *msg) { +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; } - if (psutil_pid_exists(pid) == 0) - NoSuchProcess(); - else - PyErr_SetString(PyExc_RuntimeError, msg); + 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; } @@ -688,7 +698,7 @@ void init_psutil_posix(void) PyObject *module = Py_InitModule("_psutil_posix", PsutilMethods); #endif -#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) || defined(PSUTIL_SUNOS) +#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_sunos.c b/psutil/_psutil_sunos.c index 12caaec7..c6673642 100644 --- a/psutil/_psutil_sunos.c +++ b/psutil/_psutil_sunos.c @@ -9,13 +9,13 @@ * 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 =\ -// +/* 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 =\ +*/ #define NEW_MIB_COMPLIANT 1 #define _STRUCTURED_PROC 1 @@ -126,9 +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_retlist; + PyObject *py_name = NULL; + PyObject *py_args = NULL; + PyObject *py_retlist = NULL; if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) return NULL; @@ -185,7 +185,7 @@ psutil_proc_environ(PyObject *self, PyObject *args) { goto error; if (! info.pr_envp) { - AccessDenied(); + AccessDenied(""); goto error; } @@ -328,7 +328,7 @@ psutil_proc_cpu_num(PyObject *self, PyObject *args) { return Py_BuildValue("i", proc_num); error: - if (fd != NULL) + if (fd != -1) close(fd); if (ptr != NULL) free(ptr); @@ -360,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) { @@ -548,6 +548,7 @@ psutil_users(PyObject *self, PyObject *args) { if (py_retlist == NULL) return NULL; + setutxent(); while (NULL != (ut = getutxent())) { if (ut->ut_type == USER_PROCESS) py_user_proc = Py_True; @@ -580,7 +581,7 @@ psutil_users(PyObject *self, PyObject *args) { Py_DECREF(py_hostname); Py_DECREF(py_tuple); } - endutent(); + endutxent(); return py_retlist; @@ -590,8 +591,7 @@ error: Py_XDECREF(py_hostname); Py_XDECREF(py_tuple); Py_DECREF(py_retlist); - if (ut != NULL) - endutent(); + endutxent(); return NULL; } @@ -832,7 +832,7 @@ 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' : '-'); @@ -1332,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); } @@ -1592,8 +1592,8 @@ PsutilMethods[] = { "Return CPU statistics"}, // --- others - {"py_psutil_testing", py_psutil_testing, METH_VARARGS, - "Return True if PSUTIL_TESTING env var is set"}, + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, {NULL, NULL, 0, NULL} }; @@ -1679,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 0ed4f761..21738e8c 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -25,6 +25,7 @@ #include <wtsapi32.h> #include <Winsvc.h> #include <PowrProf.h> +#include <signal.h> // Link with Iphlpapi.lib #pragma comment(lib, "IPHLPAPI.lib") @@ -334,13 +335,15 @@ psutil_proc_kill(PyObject *self, PyObject *args) { 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); @@ -349,7 +352,7 @@ psutil_proc_kill(PyObject *self, PyObject *args) { } // kill the process - if (! TerminateProcess(hProcess, 0)) { + if (! TerminateProcess(hProcess, SIGTERM)) { err = GetLastError(); // See: https://github.com/giampaolo/psutil/issues/1099 if (err != ERROR_ACCESS_DENIED) { @@ -378,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); @@ -441,7 +444,7 @@ 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 { return PyErr_SetFromWindowsErr(0); @@ -495,7 +498,7 @@ 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 { return PyErr_SetFromWindowsErr(0); @@ -514,7 +517,7 @@ 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. @@ -628,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; @@ -651,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; @@ -716,7 +719,7 @@ psutil_proc_name(PyObject *self, PyObject *args) { } CloseHandle(hSnapShot); - NoSuchProcess(); + NoSuchProcess(""); return NULL; } @@ -1037,7 +1040,6 @@ error: /* * Return process current working directory as a Python string. */ - static PyObject * psutil_proc_cwd(PyObject *self, PyObject *args) { long pid; @@ -1048,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; @@ -1063,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; } @@ -1088,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); @@ -1109,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); @@ -1172,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) @@ -1569,7 +1568,7 @@ psutil_net_connections(PyObject *self, PyObject *args) { 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(); @@ -2303,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, @@ -2313,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, @@ -2365,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; @@ -2379,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); } @@ -2451,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, "") @@ -2460,11 +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; @@ -2535,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) @@ -3657,8 +3734,8 @@ PsutilMethods[] = { "QueryDosDevice binding"}, // --- others - {"py_psutil_testing", py_psutil_testing, METH_VARARGS, - "Return True if PSUTIL_TESTING env var is set"}, + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, {NULL, NULL, 0, NULL} }; @@ -3804,6 +3881,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 4584fd6e..505846e7 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -26,7 +26,8 @@ except ImportError as err: # 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 @@ -45,6 +46,9 @@ 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 @@ -139,11 +143,6 @@ pinfo_map = dict( mem_private=21, ) -# these get overwritten on "import psutil" from the __init__.py file -NoSuchProcess = None -AccessDenied = None -TimeoutExpired = None - # ===================================================================== # --- named tuples diff --git a/psutil/arch/aix/common.c b/psutil/arch/aix/common.c new file mode 100644 index 00000000..6115a15d --- /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 <Python.h> +#include <sys/core.h> +#include <stdlib.h> +#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 00000000..b677d8c2 --- /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 <sys/core.h> + +#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 00000000..1a819365 --- /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 <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <net/if.h> +#include <netinet/in.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> + +#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 00000000..3920c1cc --- /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 <sys/socket.h> +#include <net/if.h> + +#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 00000000..69b43892 --- /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 <Python.h> +#include <stdlib.h> +#include <fcntl.h> +#define _KERNEL +#include <sys/file.h> +#undef _KERNEL +#include <sys/types.h> +#include <sys/core.h> +#include <sys/domain.h> +#include <sys/un.h> +#include <netinet/in_pcb.h> +#include <arpa/inet.h> + +#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 00000000..222bcaf3 --- /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 <Python.h> + +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 00000000..09f320ff --- /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 <sys/socketvar.h> +#include <sys/protosw.h> +#include <sys/unpcb.h> +#include <sys/mbuf_base.h> +#include <netinet/ip_var.h> +#include <netinet/tcp.h> +#include <netinet/tcpip.h> +#include <netinet/tcp_timer.h> +#include <netinet/tcp_var.h> +#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/freebsd/proc_socks.c b/psutil/arch/freebsd/proc_socks.c index 9b03e059..a458a01e 100644 --- a/psutil/arch/freebsd/proc_socks.c +++ b/psutil/arch/freebsd/proc_socks.c @@ -136,20 +136,36 @@ psutil_search_tcplist(char *buf, struct kinfo_file *kif) { 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; } @@ -196,7 +212,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; } @@ -243,19 +259,35 @@ psutil_proc_connections(PyObject *self, PyObject *args) { 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); @@ -287,7 +319,11 @@ psutil_proc_connections(PyObject *self, PyObject *args) { 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))), diff --git a/psutil/arch/freebsd/specific.c b/psutil/arch/freebsd/specific.c index 006c813c..52e2ae4a 100644 --- a/psutil/arch/freebsd/specific.c +++ b/psutil/arch/freebsd/specific.c @@ -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; @@ -297,7 +297,7 @@ psutil_proc_exe(PyObject *self, PyObject *args) { if (ret == -1) return NULL; else if (ret == 0) - return NoSuchProcess(); + return NoSuchProcess(""); else strcpy(pathname, ""); } @@ -354,7 +354,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { goto error; } if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); goto error; } @@ -370,7 +370,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { goto error; } if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); goto error; } @@ -548,7 +548,7 @@ 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; } @@ -597,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); @@ -765,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++) { diff --git a/psutil/arch/netbsd/specific.c b/psutil/arch/netbsd/specific.c index 1dc2080e..cab60d60 100644 --- a/psutil/arch/netbsd/specific.c +++ b/psutil/arch/netbsd/specific.c @@ -72,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; @@ -157,7 +157,7 @@ psutil_proc_exe(PyObject *self, PyObject *args) { if (ret == -1) return NULL; else if (ret == 0) - return NoSuchProcess(); + return NoSuchProcess(""); else strcpy(pathname, ""); } @@ -209,7 +209,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { goto error; } if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); goto error; } @@ -226,7 +226,7 @@ psutil_proc_threads(PyObject *self, PyObject *args) { goto error; } if (size == 0) { - NoSuchProcess(); + NoSuchProcess(""); goto error; } @@ -502,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); diff --git a/psutil/arch/openbsd/specific.c b/psutil/arch/openbsd/specific.c index de30c4d7..33ebdeec 100644 --- a/psutil/arch/openbsd/specific.c +++ b/psutil/arch/openbsd/specific.c @@ -67,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; @@ -242,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; @@ -253,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; @@ -404,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); @@ -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; } diff --git a/psutil/arch/osx/process_info.c b/psutil/arch/osx/process_info.c index 7c715be8..40c79a2c 100644 --- a/psutil/arch/osx/process_info.c +++ b/psutil/arch/osx/process_info.c @@ -144,7 +144,7 @@ psutil_get_cmdline(long pid) { // 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(); + NoSuchProcess(""); else PyErr_SetFromErrno(PyExc_OSError); goto error; @@ -238,7 +238,7 @@ psutil_get_environ(long pid) { // 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(); + NoSuchProcess(""); else PyErr_SetFromErrno(PyExc_OSError); goto error; @@ -338,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; @@ -354,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/v10/ifaddrs.h b/psutil/arch/solaris/v10/ifaddrs.h index d2771193..0953a9b9 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 <sys/socket.h> #include <net/if.h> diff --git a/psutil/arch/windows/ntextapi.h b/psutil/arch/windows/ntextapi.h index 1bbbf2ac..ea23ddb7 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_info.c b/psutil/arch/windows/process_info.c index a9687f9c..ffd3c80e 100644 --- a/psutil/arch/windows/process_info.c +++ b/psutil/arch/windows/process_info.c @@ -256,7 +256,7 @@ psutil_check_phandle(HANDLE hProcess, DWORD pid) { if (ret == 1) return hProcess; else if (ret == 0) - return NoSuchProcess(); + return NoSuchProcess(""); else if (ret == -1) return PyErr_SetFromWindowsErr(0); else // -2 @@ -277,7 +277,7 @@ psutil_handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess) { if (pid == 0) { // otherwise we'd get NoSuchProcess - return AccessDenied(); + return AccessDenied(""); } hProcess = OpenProcess(dwDesiredAccess, FALSE, pid); @@ -337,7 +337,7 @@ psutil_get_pids(DWORD *numberOfReturnedPIDs) { int psutil_assert_pid_exists(DWORD pid, char *err) { - if (psutil_testing()) { + if (PSUTIL_TESTING) { if (psutil_pid_in_pids(pid) == 0) { PyErr_SetString(PyExc_AssertionError, err); return 0; @@ -349,7 +349,7 @@ psutil_assert_pid_exists(DWORD pid, char *err) { int psutil_assert_pid_not_exists(DWORD pid, char *err) { - if (psutil_testing()) { + if (PSUTIL_TESTING) { if (psutil_pid_in_pids(pid) == 1) { PyErr_SetString(PyExc_AssertionError, err); return 0; @@ -959,7 +959,7 @@ psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, } } while ( (process = PSUTIL_NEXT_PROCESS(process)) ); - NoSuchProcess(); + NoSuchProcess(""); goto error; error: diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 7d8e5def..0890b6f9 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -65,7 +65,7 @@ else: __all__ = [ # constants 'APPVEYOR', 'DEVNULL', 'GLOBAL_TIMEOUT', 'MEMORY_TOLERANCE', 'NO_RETRIES', - 'PYPY', 'PYTHON', 'ROOT_DIR', 'SCRIPTS_DIR', 'TESTFILE_PREFIX', + '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", @@ -159,6 +159,7 @@ 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") @@ -166,7 +167,33 @@ HAS_SENSORS_TEMPERATURES = hasattr(psutil, "sensors_temperatures") # --- misc -PYTHON = os.path.realpath(sys.executable) + +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 + + +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_')] @@ -182,7 +209,11 @@ _testfiles_created = set() def _cleanup_files(): DEVNULL.close() for name in os.listdir(u('.')): - if name.startswith(u(TESTFILE_PREFIX)): + if isinstance(name, unicode): + prefix = u(TESTFILE_PREFIX) + else: + prefix = TESTFILE_PREFIX + if name.startswith(prefix): try: safe_rmpath(name) except Exception: @@ -286,7 +317,7 @@ def get_test_subprocess(cmd=None, **kwds): pyline = "from time import sleep;" \ "open(r'%s', 'w').close();" \ "sleep(60);" % _TESTFN - cmd = [PYTHON, "-c", pyline] + cmd = [PYTHON_EXE, "-c", pyline] sproc = subprocess.Popen(cmd, **kwds) _subprocesses_started.add(sproc) wait_for_file(_TESTFN, delete=True, empty=True) @@ -308,15 +339,14 @@ def create_proc_children_pair(): _TESTFN2 = os.path.basename(_TESTFN) + '2' # need to be relative s = textwrap.dedent("""\ import subprocess, os, sys, time - PYTHON = os.path.realpath(sys.executable) s = "import os, time;" s += "f = open('%s', 'w');" s += "f.write(str(os.getpid()));" s += "f.close();" s += "time.sleep(60);" - subprocess.Popen([PYTHON, '-c', s]) + subprocess.Popen(['%s', '-c', s]) time.sleep(60) - """ % _TESTFN2) + """ % (_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. @@ -382,7 +412,7 @@ def pyrun(src, **kwds): _testfiles_created.add(f.name) f.write(src) f.flush() - subp = get_test_subprocess([PYTHON, f.name], **kwds) + subp = get_test_subprocess([PYTHON_EXE, f.name], **kwds) wait_for_pid(subp.pid) return subp @@ -689,7 +719,7 @@ def create_exe(outpath, c_code=None): if c_code: if not which("gcc"): raise ValueError("gcc is not installed") - if c_code is None: + if isinstance(c_code, bool): # c_code is True c_code = textwrap.dedent( """ #include <unistd.h> @@ -698,6 +728,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) @@ -707,7 +738,7 @@ def create_exe(outpath, c_code=None): safe_rmpath(f.name) else: # copy python executable - shutil.copyfile(sys.executable, outpath) + shutil.copyfile(PYTHON_EXE, outpath) if POSIX: st = os.stat(outpath) os.chmod(outpath, st.st_mode | stat.S_IEXEC) @@ -742,16 +773,21 @@ unittest.TestCase = TestCase def _setup_tests(): - assert 'PSUTIL_TESTING' in os.environ - assert psutil._psplatform.cext.py_psutil_testing() + 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(): - 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')] + 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 testmodules: + 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)) diff --git a/psutil/tests/__main__.py b/psutil/tests/__main__.py index 475e6b81..2cdf5c42 100755 --- a/psutil/tests/__main__.py +++ b/psutil/tests/__main__.py @@ -21,11 +21,11 @@ try: 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__)) -PYTHON = os.path.basename(sys.executable) GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" TEST_DEPS = [] if sys.version_info[:2] == (2, 6): @@ -54,7 +54,7 @@ def install_pip(): f.flush() print("installing pip") - code = os.system('%s %s --user' % (sys.executable, f.name)) + code = os.system('%s %s --user' % (PYTHON_EXE, f.name)) return code @@ -68,12 +68,12 @@ def install_test_deps(deps=None): opts = "--user" if not is_venv else "" install_pip() code = os.system('%s -m pip install %s --upgrade %s' % ( - sys.executable, opts, " ".join(deps))) + PYTHON_EXE, opts, " ".join(deps))) return code def main(): - usage = "%s -m psutil.tests [opts]" % PYTHON + 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, @@ -88,8 +88,8 @@ def main(): try: __import__(dep.split("==")[0]) except ImportError: - sys.exit("%r lib is not installed; run:\n" - "%s -m psutil.tests --install-deps" % (dep, PYTHON)) + sys.exit("%r lib is not installed; run %s -m psutil.tests " + "--install-deps" % (dep, PYTHON_EXE)) run_suite() diff --git a/psutil/tests/test_aix.py b/psutil/tests/test_aix.py new file mode 100755 index 00000000..7a8a4c33 --- /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<space>\S+)\s+" + "(?P<vol>\S+)\s+" + "(?P<vg>\S+)\s+" + "(?P<size>\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_connections.py b/psutil/tests/test_connections.py index 203ddebb..176e2664 100755 --- a/psutil/tests/test_connections.py +++ b/psutil/tests/test_connections.py @@ -152,6 +152,7 @@ class TestUnconnectedSockets(Base, unittest.TestCase): 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: @@ -166,6 +167,7 @@ class TestUnconnectedSockets(Base, unittest.TestCase): 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: @@ -418,7 +420,7 @@ class TestConnectedSocketPairs(Base, unittest.TestCase): # ===================================================================== -class TestSystemWideConnections(unittest.TestCase): +class TestSystemWideConnections(Base, unittest.TestCase): """Tests for net_connections().""" @skip_on_access_denied() diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index 95bf2146..f9543e57 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -14,8 +14,10 @@ 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 @@ -65,7 +67,8 @@ class TestAvailability(unittest.TestCase): self.assertEqual(hasattr(psutil, "win_service_get"), WINDOWS) def test_PROCFS_PATH(self): - self.assertEqual(hasattr(psutil, "PROCFS_PATH"), LINUX or SUNOS) + self.assertEqual(hasattr(psutil, "PROCFS_PATH"), + LINUX or SUNOS or AIX) def test_win_priority(self): ae = self.assertEqual @@ -108,7 +111,10 @@ class TestAvailability(unittest.TestCase): ae(hasattr(psutil, "RLIMIT_SIGPENDING"), hasit) def test_cpu_freq(self): - self.assertEqual(hasattr(psutil, "cpu_freq"), LINUX or OSX or WINDOWS) + 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) @@ -118,7 +124,7 @@ class TestAvailability(unittest.TestCase): def test_battery(self): self.assertEqual(hasattr(psutil, "sensors_battery"), - LINUX or WINDOWS or FREEBSD) + LINUX or WINDOWS or FREEBSD or OSX) def test_proc_environ(self): self.assertEqual(hasattr(psutil.Process, "environ"), @@ -159,7 +165,23 @@ class TestAvailability(unittest.TestCase): def test_proc_memory_maps(self): hasit = hasattr(psutil.Process, "memory_maps") - self.assertEqual(hasit, False if OPENBSD or NETBSD else True) + 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)) # =================================================================== @@ -372,12 +394,14 @@ class TestFetchAllProcesses(unittest.TestCase): self.assertGreaterEqual(ret, 0) def ppid(self, ret, proc): - self.assertIsInstance(ret, int) + self.assertIsInstance(ret, (int, long)) self.assertGreaterEqual(ret, 0) def name(self, ret, proc): self.assertIsInstance(ret, str) - assert ret + # on AIX, "<exiting>" processes don't have names + if not AIX: + assert ret def create_time(self, ret, proc): self.assertIsInstance(ret, float) @@ -482,7 +506,7 @@ class TestFetchAllProcesses(unittest.TestCase): for value in ret: self.assertIsInstance(value, (int, long)) self.assertGreaterEqual(value, 0) - if POSIX and ret.vms != 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': @@ -536,8 +560,8 @@ class TestFetchAllProcesses(unittest.TestCase): check_connection_ntuple(conn) def cwd(self, ret, proc): - self.assertIsInstance(ret, str) - if ret is not None: # BSD may return None + if ret: # 'ret' can be None or empty + self.assertIsInstance(ret, str) assert os.path.isabs(ret), ret try: st = os.stat(ret) @@ -607,7 +631,7 @@ class TestFetchAllProcesses(unittest.TestCase): def num_ctx_switches(self, ret, proc): assert is_namedtuple(ret) for value in ret: - self.assertIsInstance(value, int) + self.assertIsInstance(value, (int, long)) self.assertGreaterEqual(value, 0) def rlimit(self, ret, proc): diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index 468b3c66..6ba17b25 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -29,6 +29,7 @@ from psutil._compat import PY3 from psutil._compat import u from psutil.tests import call_until 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 @@ -607,11 +608,13 @@ class TestSystemCPU(unittest.TestCase): 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): @@ -629,6 +632,7 @@ class TestSystemCPU(unittest.TestCase): 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'): @@ -651,6 +655,7 @@ class TestSystemCPU(unittest.TestCase): 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'): @@ -675,6 +680,7 @@ class TestSystemCPU(unittest.TestCase): 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): @@ -762,21 +768,25 @@ class TestSystemNetwork(unittest.TestCase): # Not always reliable. # self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) self.assertEqual(stats.mtu, - int(re.findall(r'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(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 bytes:(\d+)', out)[0]) - ret['bytes_sent'] = int(re.findall(r'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 nio = psutil.net_io_counters(pernic=True, nowrap=False) @@ -1322,7 +1332,9 @@ class TestSensorsBattery(unittest.TestCase): # Emulate a case where energy_full file does not exist. # Expected fallback on /capacity. def open_mock(name, *args, **kwargs): - if name.startswith("/sys/class/power_supply/BAT0/energy_full"): + 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") @@ -1573,6 +1585,20 @@ class TestProcess(unittest.TestCase): self.assertEqual(p.cmdline(), ['foo', 'bar', '']) assert m.called + 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): with mock.patch('psutil._pslinux.os.readlink', return_value='/home/foo (deleted)'): diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index 85bab84c..f67c0e4c 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -18,7 +18,6 @@ import os import pickle import socket import stat -import sys from psutil import LINUX from psutil import POSIX @@ -49,6 +48,7 @@ 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 @@ -365,6 +365,8 @@ class TestMisc(unittest.TestCase): def test_setup_script(self): setup_py = os.path.join(ROOT_DIR, '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__) @@ -643,16 +645,20 @@ class TestWrapNumbers(unittest.TestCase): @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.""" @staticmethod - def assert_stdout(exe, args=None, **kwds): - exe = '"%s"' % os.path.join(SCRIPTS_DIR, exe) - if args: - exe = exe + ' ' + args + 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, **kwds).strip() + out = sh(cmd, **kwargs).strip() except RuntimeError as err: if 'AccessDenied' in str(err): return str(err) @@ -700,7 +706,7 @@ class TestScripts(unittest.TestCase): 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(), @@ -724,7 +730,7 @@ class TestScripts(unittest.TestCase): @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.skipIf(not HAS_MEMORY_FULL_INFO, "not supported") def test_procsmem(self): @@ -743,7 +749,7 @@ class TestScripts(unittest.TestCase): 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.skipIf(not WINDOWS, "WINDOWS only") @@ -1014,7 +1020,8 @@ class TestNetUtils(unittest.TestCase): # work around http://bugs.python.org/issue30204 types[s.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE)] += 1 self.assertGreaterEqual(fams[socket.AF_INET], 2) - self.assertGreaterEqual(fams[socket.AF_INET6], 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) diff --git a/psutil/tests/test_osx.py b/psutil/tests/test_osx.py index c8214f14..bcb2ba4e 100755 --- a/psutil/tests/test_osx.py +++ b/psutil/tests/test_osx.py @@ -14,6 +14,7 @@ import psutil from psutil import OSX 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 @@ -285,6 +286,18 @@ class TestSystemAPIs(unittest.TestCase): self.assertEqual(stats.mtu, 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__': run_test_module_by_name(__file__) diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py index 580abdfd..c59f9a1c 100755 --- a/psutil/tests/test_posix.py +++ b/psutil/tests/test_posix.py @@ -10,11 +10,13 @@ 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 @@ -27,7 +29,7 @@ from psutil.tests import APPVEYOR 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 @@ -46,8 +48,9 @@ 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") + if AIX: + cmd = cmd.replace("-o rss", "-o rssize") output = sh(cmd) if not LINUX: output = output.split('\n')[1].strip() @@ -56,6 +59,31 @@ 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. + + +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): @@ -63,7 +91,7 @@ class TestProcess(unittest.TestCase): @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) @@ -121,12 +149,14 @@ class TestProcess(unittest.TestCase): 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): @@ -179,8 +209,7 @@ class TestProcess(unittest.TestCase): 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) @@ -195,18 +224,17 @@ class TestProcess(unittest.TestCase): 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() @@ -262,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"] @@ -285,11 +313,7 @@ class TestSystemAPIs(unittest.TestCase): # 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 @@ -355,6 +379,8 @@ class TestSystemAPIs(unittest.TestCase): 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() diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index 8b88f766..3b60d38a 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -9,6 +9,7 @@ import collections import errno import getpass +import itertools import os import signal import socket @@ -22,6 +23,7 @@ import types import psutil +from psutil import AIX from psutil import BSD from psutil import LINUX from psutil import NETBSD @@ -49,9 +51,10 @@ 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 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 @@ -62,7 +65,6 @@ from psutil.tests import skip_on_not_implemented 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 wait_for_pid @@ -151,7 +153,7 @@ class TestProcess(unittest.TestCase): if POSIX: self.assertEqual(code, -signal.SIGKILL) else: - self.assertEqual(code, 0) + self.assertEqual(code, signal.SIGTERM) self.assertFalse(p.is_running()) sproc = get_test_subprocess() @@ -161,12 +163,12 @@ class TestProcess(unittest.TestCase): if POSIX: 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()) @@ -175,7 +177,7 @@ class TestProcess(unittest.TestCase): # 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)) @@ -207,8 +209,8 @@ class TestProcess(unittest.TestCase): # to get None. self.assertEqual(ret2, None) else: - self.assertEqual(ret1, 0) - self.assertEqual(ret1, 0) + self.assertEqual(ret1, signal.SIGTERM) + self.assertEqual(ret1, signal.SIGTERM) def test_wait_timeout_0(self): sproc = get_test_subprocess() @@ -227,7 +229,7 @@ class TestProcess(unittest.TestCase): if POSIX: 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): @@ -316,10 +318,10 @@ class TestProcess(unittest.TestCase): # 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: + if not BSD and not AIX: self.assertGreater(io2.read_count, io1.read_count) self.assertEqual(io2.write_count, io1.write_count) if LINUX: @@ -528,6 +530,7 @@ class TestProcess(unittest.TestCase): 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: @@ -552,6 +555,7 @@ class TestProcess(unittest.TestCase): @retry_before_failing() @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) @@ -693,12 +697,12 @@ class TestProcess(unittest.TestCase): 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" @@ -709,7 +713,7 @@ class TestProcess(unittest.TestCase): 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 @@ -718,7 +722,7 @@ class TestProcess(unittest.TestCase): 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()), @@ -728,27 +732,39 @@ class TestProcess(unittest.TestCase): # 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", ""] @@ -853,7 +869,8 @@ class TestProcess(unittest.TestCase): 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())") @@ -870,10 +887,9 @@ class TestProcess(unittest.TestCase): 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"): @@ -911,6 +927,24 @@ class TestProcess(unittest.TestCase): 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 @@ -937,7 +971,7 @@ class TestProcess(unittest.TestCase): # 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): @@ -1268,7 +1302,15 @@ class TestProcess(unittest.TestCase): # set methods succeed_or_zombie_p_exc(zproc.parent) if hasattr(zproc, 'cpu_affinity'): - succeed_or_zombie_p_exc(zproc.cpu_affinity, [0]) + try: + 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: @@ -1294,10 +1336,14 @@ class TestProcess(unittest.TestCase): # 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()]) + 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): @@ -1360,28 +1406,23 @@ class TestProcess(unittest.TestCase): @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 'PSUTIL_TESTING' in os.environ: - removes.append('PSUTIL_TESTING') - 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) + 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") @@ -1485,7 +1526,7 @@ class TestPopen(unittest.TestCase): # 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);"] + cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"] with psutil.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: proc.name() @@ -1496,7 +1537,7 @@ class TestPopen(unittest.TestCase): proc.terminate() def test_ctx_manager(self): - with psutil.Popen([PYTHON, "-V"], + with psutil.Popen([PYTHON_EXE, "-V"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) as proc: @@ -1510,7 +1551,7 @@ class TestPopen(unittest.TestCase): # subprocess.Popen()'s terminate(), kill() and send_signal() do # not raise exception after the process is gone. psutil.Popen # diverges from that. - cmd = [PYTHON, "-c", "import time; time.sleep(60);"] + cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"] with psutil.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: proc.terminate() diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index e93bb6b5..20b132a9 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -19,6 +19,7 @@ import tempfile import time import psutil +from psutil import AIX from psutil import BSD from psutil import FREEBSD from psutil import LINUX @@ -669,7 +670,8 @@ class TestSystemAPIs(unittest.TestCase): @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) @@ -689,6 +691,7 @@ class TestSystemAPIs(unittest.TestCase): 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 @@ -742,7 +745,8 @@ class TestSystemAPIs(unittest.TestCase): 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") diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py index 9b99fdf9..c2a2f847 100755 --- a/psutil/tests/test_unicode.py +++ b/psutil/tests/test_unicode.py @@ -49,7 +49,7 @@ etc.) and make sure that: For a detailed explanation of how psutil handles unicode see: - https://github.com/giampaolo/psutil/issues/1040 -- https://pythonhosted.org/psutil/#unicode +- http://psutil.readthedocs.io/#unicode """ import os @@ -91,8 +91,6 @@ import psutil.tests def safe_rmpath(path): - # XXX - return _safe_rmpath(path) if APPVEYOR: # TODO - this is quite random and I'm not sure why it happens, # nor I can reproduce it locally: @@ -125,8 +123,9 @@ def subprocess_supports_unicode(name): except UnicodeEncodeError: return False else: - reap_children() return True + finally: + reap_children() # An invalid unicode string. @@ -145,18 +144,23 @@ else: class _BaseFSAPIsTests(object): funky_name = None - def setUp(self): - safe_rmpath(self.funky_name) + @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() - safe_rmpath(self.funky_name) def expect_exact_path_match(self): raise NotImplementedError("must be implemented in subclass") def test_proc_exe(self): - create_exe(self.funky_name) subp = get_test_subprocess(cmd=[self.funky_name]) p = psutil.Process(subp.pid) exe = p.exe() @@ -165,7 +169,6 @@ class _BaseFSAPIsTests(object): self.assertEqual(exe, self.funky_name) def test_proc_name(self): - create_exe(self.funky_name) subp = get_test_subprocess(cmd=[self.funky_name]) if WINDOWS: # On Windows name() is determined from exe() first, because @@ -182,7 +185,6 @@ class _BaseFSAPIsTests(object): self.assertEqual(name, os.path.basename(self.funky_name)) def test_proc_cmdline(self): - create_exe(self.funky_name) subp = get_test_subprocess(cmd=[self.funky_name]) p = psutil.Process(subp.pid) cmdline = p.cmdline() @@ -192,18 +194,20 @@ class _BaseFSAPIsTests(object): self.assertEqual(cmdline, [self.funky_name]) def test_proc_cwd(self): - safe_mkdir(self.funky_name) - with chdir(self.funky_name): + 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, self.funky_name) + self.assertEqual(cwd, dname) def test_proc_open_files(self): p = psutil.Process() start = set(p.open_files()) - with open(self.funky_name, 'wb'): + with open(self.funky_name, 'rb'): new = set(p.open_files()) path = (new - start).pop().path self.assertIsInstance(path, str) @@ -260,8 +264,10 @@ class _BaseFSAPIsTests(object): self.assertEqual(conn.laddr, name) def test_disk_usage(self): - safe_mkdir(self.funky_name) - psutil.disk_usage(self.funky_name) + 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") @@ -335,6 +341,9 @@ class TestWinProcessName(unittest.TestCase): 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 diff --git a/scripts/ifconfig.py b/scripts/ifconfig.py index b823b374..e2a9ce53 100755 --- a/scripts/ifconfig.py +++ b/scripts/ifconfig.py @@ -10,34 +10,34 @@ A clone of 'ifconfig' on UNIX. $ 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 @@ duplex_map = { } +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/download_exes.py b/scripts/internal/download_exes.py index 9688919b..1b004428 100755 --- a/scripts/internal/download_exes.py +++ b/scripts/internal/download_exes.py @@ -25,7 +25,7 @@ from psutil import __version__ as PSUTIL_VERSION BASE_URL = 'https://ci.appveyor.com/api' -PY_VERSIONS = ['2.7', '3.3', '3.4', '3.5', '3.6'] +PY_VERSIONS = ['2.7', '3.4', '3.5', '3.6'] TIMEOUT = 30 COLORS = True @@ -170,8 +170,8 @@ def main(options): completed += 1 print("downloaded %-45s %s" % ( local_fname, bytes2human(os.path.getsize(local_fname)))) - # 2 exes (32 and 64 bit) and 2 wheels (32 and 64 bit) for each ver. - expected = len(PY_VERSIONS) * 4 + # 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: diff --git a/scripts/internal/generate_manifest.py b/scripts/internal/generate_manifest.py index 8b6b4f5f..3511b749 100755 --- a/scripts/internal/generate_manifest.py +++ b/scripts/internal/generate_manifest.py @@ -12,6 +12,10 @@ 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() @@ -21,8 +25,8 @@ def main(): files = sh("git ls-files").split('\n') for file in files: if file.startswith('.ci/') or \ - os.path.splitext(file)[1] in ('.png', '.jpg') or \ - file in ('.travis.yml', 'appveyor.yml'): + os.path.splitext(file)[1].lower() in IGNORED_EXTS or \ + file in IGNORED_FILES: continue print("include " + file) diff --git a/scripts/internal/print_announce.py b/scripts/internal/print_announce.py index e47911c2..1c2b9e11 100755 --- a/scripts/internal/print_announce.py +++ b/scripts/internal/print_announce.py @@ -19,7 +19,7 @@ HISTORY = os.path.abspath(os.path.join(HERE, '../../HISTORY.rst')) 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 @@ 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). 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/winmake.py b/scripts/internal/winmake.py index 138a0b0c..548f7a8e 100755 --- a/scripts/internal/winmake.py +++ b/scripts/internal/winmake.py @@ -174,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 # =================================================================== @@ -205,13 +211,6 @@ def build(): @cmd -def build_exe(): - """Create exe file.""" - build() - sh("%s setup.py bdist_wininst" % PYTHON) - - -@cmd def build_wheel(): """Create wheel file.""" build() @@ -328,15 +327,15 @@ 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 -Wa -m flake8 %s" % (PYTHON, py_files), nolog=True) + sh("%s -m flake8 %s" % (PYTHON, py_files), nolog=True) @cmd def test(): """Run tests""" install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa %s" % (PYTHON, TSCRIPT)) + test_setup() + sh("%s %s" % (PYTHON, TSCRIPT)) @cmd @@ -344,8 +343,8 @@ def coverage(): """Run coverage tests.""" # Note: coverage options are controlled by .coveragerc file install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa -m coverage run %s" % (PYTHON, TSCRIPT)) + test_setup() + sh("%s -m coverage run %s" % (PYTHON, TSCRIPT)) sh("%s -m coverage report" % PYTHON) sh("%s -m coverage html" % PYTHON) sh("%s -m webbrowser -t htmlcov/index.html" % PYTHON) @@ -355,56 +354,56 @@ def coverage(): def test_process(): """Run process tests""" install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa -m unittest -v psutil.tests.test_process" % PYTHON) + test_setup() + sh("%s -m unittest -v psutil.tests.test_process" % PYTHON) @cmd def test_system(): """Run system tests""" install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa -m unittest -v psutil.tests.test_system" % PYTHON) + test_setup() + sh("%s -m unittest -v psutil.tests.test_system" % PYTHON) @cmd def test_platform(): """Run windows only tests""" install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa -m unittest -v psutil.tests.test_windows" % PYTHON) + test_setup() + sh("%s -m unittest -v psutil.tests.test_windows" % PYTHON) @cmd def test_misc(): """Run misc tests""" install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa -m unittest -v psutil.tests.test_misc" % PYTHON) + test_setup() + sh("%s -m unittest -v psutil.tests.test_misc" % PYTHON) @cmd def test_unicode(): """Run unicode tests""" install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa -m unittest -v psutil.tests.test_unicode" % PYTHON) + test_setup() + sh("%s -m unittest -v psutil.tests.test_unicode" % PYTHON) @cmd def test_connections(): """Run connections tests""" install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa -m unittest -v psutil.tests.test_connections" % PYTHON) + test_setup() + sh("%s -m unittest -v psutil.tests.test_connections" % PYTHON) @cmd def test_contracts(): """Run contracts tests""" install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa -m unittest -v psutil.tests.test_contracts" % PYTHON) + test_setup() + sh("%s -m unittest -v psutil.tests.test_contracts" % PYTHON) @cmd @@ -416,8 +415,8 @@ def test_by_name(): except IndexError: sys.exit('second arg missing') install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa -m unittest -v %s" % (PYTHON, name)) + test_setup() + sh("%s -m unittest -v %s" % (PYTHON, name)) @cmd @@ -429,16 +428,16 @@ def test_script(): except IndexError: sys.exit('second arg missing') install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa %s" % (PYTHON, name)) + test_setup() + sh("%s %s" % (PYTHON, name)) @cmd def test_memleaks(): """Run memory leaks tests""" install() - os.environ['PSUTIL_TESTING'] = '1' - sh("%s -Wa psutil\\tests\\test_memory_leaks.py" % PYTHON) + test_setup() + sh("%s psutil\\tests\\test_memory_leaks.py" % PYTHON) @cmd @@ -467,8 +466,8 @@ def set_python(s): # try to look for a python installation orig = s s = s.replace('.', '') - vers = ('26', '27', '33', '34', '35', '36', '37', - '26-64', '27-64', '33-64', '34-64', '35-64', '36-64', '37-64') + 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 diff --git a/scripts/procinfo.py b/scripts/procinfo.py index d8625560..54205de3 100755 --- a/scripts/procinfo.py +++ b/scripts/procinfo.py @@ -225,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")) @@ -4,10 +4,7 @@ # 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 contextlib import io @@ -40,6 +37,7 @@ from _common import OSX # NOQA from _common import POSIX # NOQA from _common import SUNOS # NOQA from _common import WINDOWS # NOQA +from _common import AIX # NOQA macros = [] @@ -242,7 +240,18 @@ elif SUNOS: ], define_macros=macros, libraries=['kstat', 'nsl', 'socket']) - +# 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) @@ -257,6 +266,8 @@ if POSIX: 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: @@ -267,7 +278,7 @@ def main(): kwargs = dict( name='psutil', version=VERSION, - description=__doc__ .replace('\n', '').strip() if __doc__ else '', + description=__doc__ .replace('\n', ' ').strip() if __doc__ else '', long_description=get_description(), keywords=[ 'ps', 'top', 'kill', 'free', 'lsof', 'netstat', 'nice', 'tty', @@ -307,10 +318,6 @@ 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', |