diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2020-10-17 00:53:59 +0200 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2020-10-17 00:53:59 +0200 |
commit | a8cd5893a920adcd5b8922b80748461fbea8dc1c (patch) | |
tree | f74c5f7cfc8ae84319f201f21528c51c5ba553e7 | |
parent | bd4d2bf420e1dfa3298143daebd485b97335b256 (diff) | |
download | psutil-a8cd5893a920adcd5b8922b80748461fbea8dc1c.tar.gz |
pypi download stats script
58 files changed, 1187 insertions, 542 deletions
diff --git a/.ci/travis/install.sh b/.ci/travis/install.sh index f06e43d5..16bd5c0c 100755 --- a/.ci/travis/install.sh +++ b/.ci/travis/install.sh @@ -24,13 +24,21 @@ if [[ "$(uname -s)" == 'Darwin' ]]; then pyenv install 3.6.6 pyenv virtualenv 3.6.6 psutil ;; + py37) + pyenv install 3.7.6 + pyenv virtualenv 3.7.6 psutil + ;; + py38) + pyenv install 3.8.2 + pyenv virtualenv 3.8.2 psutil + ;; esac pyenv rehash pyenv activate psutil fi if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]] || [[ $PYVER == 'py27' ]]; then - pip install -U ipaddress mock + pip install -U ipaddress mock unittest2 fi -pip install -U coverage coveralls flake8 setuptools concurrencytest +pip install -U coverage coveralls flake8 setuptools diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index dd130351..03c7c77c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -3,7 +3,7 @@ tidelift: "pypi/psutil" github: giampaolo patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username +open_collective: psutil ko_fi: # Replace with a single Ko-fi username community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -custom: # Replace with a single custom sponsorship URL +custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8 diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 00000000..7099c810 --- /dev/null +++ b/.github/lock.yml @@ -0,0 +1,35 @@ +# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app + +# Number of days of inactivity before a closed issue or pull request is locked +daysUntilLock: 3 + +# Skip issues and pull requests created before a given timestamp. Timestamp must +# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable +skipCreatedBefore: false + +# Issues and pull requests with these labels will be ignored. Set to `[]` to disable +exemptLabels: [] + +# Label to add before locking, such as `outdated`. Set to `false` to disable +lockLabel: false + +# Comment to post before locking. Set to `false` to disable +lockComment: false + +# Assign `resolved` as the reason for locking. Set to `false` to disable +setLockReason: false + +# Limit to only `issues` or `pulls` +# only: issues + +# Optionally, specify configuration settings just for `issues` or `pulls` +# issues: +# exemptLabels: +# - help-wanted +# lockLabel: outdated + +# pulls: +# daysUntilLock: 30 + +# Repository to extend settings from +# _extends: repo diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 7d230b90..ec784ed5 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -1,57 +1,19 @@ -name: Build wheel +name: Build wheels on: [push, pull_request] jobs: - wheel_without_test: - name: build wheel for ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [windows-latest, macos-latest, ubuntu-latest] - env: - CIBW_SKIP: "pp27-*win* cp27-*manylinux* pp-*manylinux*" - CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 - CIBW_MANYLINUX_I686_IMAGE: manylinux2014 - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-python@v1 - name: Install Python 3.7 - with: - python-version: '3.7' - - - name: Install Visual C++ for Python 2.7 - if: startsWith(matrix.os, 'windows') - run: | - choco install vcpython27 -f -y - - - name: "install cibuildwheel" - run: pip install cibuildwheel==1.4.1 - - - name: build wheel - run: cibuildwheel . - - - name: Upload wheels - uses: actions/upload-artifact@v1 - with: - name: wheels2 - path: wheelhouse - wheel: - name: build wheel for ${{ matrix.os }} + name: ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [windows-latest, macos-latest, ubuntu-latest] + os: [macos-latest, ubuntu-latest] env: - CIBW_SKIP: "pp27-*win* *27* cp27-*manylinux* pp-*manylinux*" - CIBW_TEST_COMMAND: python -Wa {project}/psutil/tests/runner.py + CIBW_TEST_COMMAND: python -u -Wa {project}/psutil/tests/runner.py CIBW_TEST_COMMAND_MACOS: LC_ALL='en_US.utf8' python -Wa {project}/psutil/tests/runner.py CIBW_TEST_EXTRAS: test - CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 - CIBW_MANYLINUX_I686_IMAGE: manylinux2014 steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1 @@ -64,10 +26,10 @@ jobs: run: | choco install vcpython27 -f -y - - name: "install cibuildwheel" + - name: Install cibuildwheel run: pip install cibuildwheel==1.4.1 - - name: build wheel + - name: Build wheels run: cibuildwheel . - name: Upload wheels @@ -1,25 +1,26 @@ Intro -===== +------------------------------------------------------------------------------- I would like to recognize some of the people who have been instrumental in the development of psutil. I'm sure I'm forgetting somebody (feel free to email me) but here is a short list. It's modeled after the Linux CREDITS file where the fields are: name (N), e-mail (E), website (W), country (C), description (D), -(I) issues. Issue tracker is at https://github.com/giampaolo/psutil/issues). +(I) issues. Issue tracker is at: +https://github.com/giampaolo/psutil/issues. A big thanks to all of you. - Giampaolo Author -====== +------------------------------------------------------------------------------- N: Giampaolo Rodola C: Italy E: g.rodola@gmail.com -W: http://grodola.blogspot.com/ +W: https://gmpy.dev Experts -======= +------------------------------------------------------------------------------- Github usernames of people to CC on github when in need of help. @@ -45,7 +46,7 @@ Github usernames of people to CC on github when in need of help. - wiggin15, Arnon Yaari (maintainer) Top contributors -================ +------------------------------------------------------------------------------- N: Jay Loden C: NJ, USA @@ -69,7 +70,6 @@ W: https://github.com/mrjefftang I: 340, 529, 616, 653, 654, 648, 641 N: Jeremy Whitlock -E: jcscoobyrs@gmail.com D: great help with macOS C development. I: 125, 150, 174, 206 @@ -79,7 +79,6 @@ D: OpenBSD implementation. I: 615 N: Justin Venus -E: justin.venus@gmail.com D: Solaris support I: 18 @@ -93,28 +92,115 @@ W: https://github.com/ryoon D: NetBSD implementation (co-author). I: 557 +Donations +------------------------------------------------------------------------------- + +N: Rodion Stratov +C: Canada + +N: Remi Chateauneu +C: London, UK + +N: Olivier Grisel +C: Paris, France + +N: Praveen Bhamidipati +C: Bellevue, USA + +N: Willem de Groot +C: Netherlands + +N: Sigmund Vik + +N: Kahntent +C: NYC, USA + +N: Gyula Áfra +C: Budapest, Hungary + +N: Mahmut Dumlupinar + +N: Thomas Guettler +C: Germany + +N: Karthik Kumar +C: India + +N: Oche Ejembi +C: UK + +N: Russell Robinson +C: New Zealand + +N: Wompasoft +C: Texas, USA + +N: Amit Kulkarni +C: Santa Clara, USA + +N: Alexander Kaftan +C: Augsburg Germany + +N: Andrew Bays +C: Maynard, USA + +N: Carver Koella +C: Pittsburgh, USA + +N: Kristjan Võrk +C: Tallin, Estonia + +N: HTB Industries +C: Willow Springs, USA + +N: Brett Harris +C: Melbourne, Australia + +N: Peter Friedland +C: CT, USA + +N: Matthew Callow +C: Australia + +N: Marco Schrank +C: Germany + +N: Mindview LLC +C: USA + +N: Григорьев Андрей +C: Russia + +N: Heijdemann Morgan +C: Singapore + +N: Florian Bruhin +C: Winterthur, Switzerland + +N: Heijdemann Morgan +C: Singapore + +N: Morgan Heijdemann +C: Singapore + Contributors -============ +------------------------------------------------------------------------------- N: wj32 -E: wj32.64@gmail.com D: process username() and get_connections() on Windows I: 114, 115 N: Yan Raber C: Bologna, Italy -E: yanraber@gmail.com D: help on Windows development (initial version of Process.username()) N: Dave Daeschler C: USA -E: david.daeschler@gmail.com W: http://daviddaeschler.com D: some contributions to initial design/bootstrap plus occasional bug fixing I: 522, 536 N: cjgohlke -E: cjgohlke@gmail.com D: Windows 64 bit support I: 107 @@ -133,64 +219,49 @@ I: 1368, 1348 ---- N: Jeffery Kline -E: jeffery.kline@gmail.com I: 130 N: Grabriel Monnerat -E: gabrielmonnerat@gmail.com I: 146 N: Philip Roberts -E: philip.roberts@gmail.com I: 168 N: jcscoobyrs -E: jcscoobyrs@gmail.com I: 125 N: Sandro Tosi -E: sandro.tosi@gmail.com I: 200, 201 N: Andrew Colin -E: andrew.colin@gmail.com I: 248 N: Amoser -E: amoser@google.com I: 266, 267, 340 N: Matthew Grant -E: matthewgrant5@gmail.com I: 271 N: oweidner -E: oweidner@cct.lsu.edu I: 275 N: Tarek Ziade -E: ziade.tarek I: 281 N: Luca Cipriani C: Turin, Italy -E: luca.opensource@gmail.com I: 278 N: Maciej Lach, -E: maciej.lach@gmail.com I: 294 N: James Pye -E: james.pye@gmail.com I: 305, 306 N: Stanchev Emil -E: stanchev.emil I: 314 N: Kim Gräsman -E: kim.grasman@gmail.com D: ...also kindly donated some money. I: 316 @@ -199,15 +270,12 @@ C: Italy I: 318 N: Florent Xicluna -E: florent.xicluna@gmail.com I: 319 N: Michal Spondr -E: michal.spondr I: 313 N: Jean Sebastien -E: dumbboules@gmail.com I: 344 N: Rob Smith @@ -223,50 +291,39 @@ W: https://plus.google.com/116873264322260110710/posts I: 323 N: André Oriani -E: aoriani@gmail.com I: 361 N: clackwell -E: clackwell@gmail.com I: 356 N: m.malycha -E: m.malycha@gmail.com I: 351 N: John Baldwin -E: jhb@FreeBSD.org I: 370 N: Jan Beich -E: jbeich@tormail.org I: 325 N: floppymaster -E: floppymaster@gmail.com I: 380 N: Arfrever.FTA -E: Arfrever.FTA@gmail.com I: 369, 404 N: danudey -E: danudey@gmail.com I: 386 N: Adrien Fallou I: 224 N: Gisle Vanem -E: gisle.vanem@gmail.com I: 411 N: thepyr0 -E: thepyr0@gmail.com I: 414 N: John Pankov -E: john.pankov@gmail.com I: 435 N: Matt Good @@ -274,11 +331,9 @@ W: http://matt-good.net/ I: 438 N: Ulrich Klank -E: ulrich.klank@scitics.de I: 448 N: Josiah Carlson -E: josiah.carlson@gmail.com I: 451, 452 N: Raymond Hettinger @@ -291,41 +346,31 @@ M: Ken Seeho D: @cached_property decorator N: crusaderky -E: crusaderky@gmail.com I: 470, 477 -E: alex@mroja.net I: 471 N: Gautam Singh -E: gautam.singh@gmail.com I: 466 -E: lhn@hupfeldtit.dk I: 476, 479 N: Francois Charron -E: francois.charron.1@gmail.com I: 474 N: Naveed Roudsari -E: naveed.roudsari@gmail.com I: 421 N: Alexander Grothe -E: Alexander.Grothe@gmail.com I: 497 N: Szigeti Gabor Niif -E: szigeti.gabor.niif@gmail.com I: 446 N: msabramo -E: msabramo@gmail.com I: 492 N: Yaolong Huang -E: airekans@gmail.com W: http://airekans.github.io/ I: 530 @@ -335,18 +380,15 @@ I: 496 N: spacewander W: https://github.com/spacewander -E: spacewanderlzx@gmail.com I: 561, 603 N: Sylvain Mouquet -E: sylvain.mouquet@gmail.com I: 565 N: karthikrev I: 568 N: Bruno Binet -E: bruno.binet@gmail.com I: 572 N: Gabi Davar @@ -356,7 +398,6 @@ I: 578, 581, 587 N: spacewanderlzx C: Guangzhou,China -E: spacewanderlzx@gmail.com I: 555 N: Fabian Groffen @@ -373,8 +414,6 @@ C: Irvine, CA, US I: 614 N: Árni Már Jónsson -E: Reykjavik, Iceland -E: https://github.com/arnimarj I: 634 N: Bart van Kleef @@ -406,12 +445,10 @@ W: https://github.com/syohex I: 730 N: Visa Hankala -E: visa@openbsd.org I: 741 N: Sebastian-Gabriel Brestin C: Romania -E: sebastianbrestin@gmail.com I: 704 N: Timmy Konick @@ -427,11 +464,9 @@ W: https://github.com/wxwright I: 776 N: Farhan Khan -E: khanzf@gmail.com I: 823 N: Jake Omann -E: https://github.com/jomann09 I: 816, 775 N: Jeremy Humble @@ -448,7 +483,6 @@ I: 798 N: Andre Caron C: Montreal, QC, Canada -E: andre.l.caron@gmail.com W: https://github.com/AndreLouisCaron I: 880 @@ -466,7 +500,6 @@ I: 936, 1133 N: Pierre Fersing C: France -E: pierre.fersing@bleemeo.com I: 950 N: Thiago Borges Abdnur @@ -609,9 +642,8 @@ W: https://github.com/samertm I: 1480 N: Ammar Askar -E: ammar@ammaraskar.com W: http://ammaraskar.com/ -I: 604, 1484 +I: 604, 1484, 1781 N: agnewee W: https://github.com/Agnewee @@ -668,3 +700,19 @@ I: 1695 N: Michał Górny W: https://github.com/mgorny I: 1726 + +N: Julien Lebot +W: https://github.com/julien-lebot +I: 1768 + +N: Armin Gruner +W: https://github.com/ArminGruner +I: 1800 + +N: Chris Burger +W: https://github.com/phobozad +I: 1830 + +N: aristocratos +W: https://github.com/aristocratos +I: 1837, 1838 diff --git a/HISTORY.rst b/HISTORY.rst index 3bae76b4..1d760dbc 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,12 +1,40 @@ *Bug tracker at https://github.com/giampaolo/psutil/issues* -5.7.1 (unreleased) -================== +5.7.3 +===== XXXX-XX-XX **Enhancements** +- 893_: implement `Process.environ()` on BSD family. (patch by Armin Gruner) +- 1830_: [UNIX] `net_if_stats()`'s `isup` also checks whether the NIC is + running (meaning Wi-Fi or ethernet cable is connected). (patch by Chris Burger) +- 1837_: [Linux] improved battery detection and charge "secsleft" calculation + (patch by aristocratos) + +**Bug fixes** + +- 1838_: [Linux] sensors_battery(): if `percent` can be determined but not + the remaining values, still return a result instead of None. + (patch by aristocratos) + +5.7.2 +===== + +2020-07-15 + +**Bug fixes** + +- wheels for 2.7 were inadvertently deleted. + +5.7.1 +===== + +2020-07-15 + +**Enhancements** + - 1729_: parallel tests on UNIX (make test-parallel). They're twice as fast! - 1741_: "make build/install" is now run in parallel and it's about 15% faster on UNIX. @@ -25,12 +53,17 @@ XXXX-XX-XX psutil.Process(pid=12739, name='python3', status='terminated', exitcode=<Negsigs.SIGTERM: -15>, started='15:08:20') - 1757_: memory leak tests are now stable. +- 1768_: [Windows] added support for Windows Nano Server. (contributed by + Julien Lebot) **Bug fixes** - 1726_: [Linux] cpu_freq() parsing should use spaces instead of tabs on ia64. (patch by Michał Górny) - 1760_: [Linux] Process.rlimit() does not handle long long type properly. +- 1766_: [macOS] NoSuchProcess may be raised instead of ZombieProcess. +- 1781_: fix signature of callback function for getloadavg(). (patch by + Ammar Askar) 5.7.0 ===== diff --git a/MANIFEST.in b/MANIFEST.in index 93c49180..b8c2064e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -117,17 +117,19 @@ include scripts/internal/bench_oneshot.py include scripts/internal/bench_oneshot_2.py include scripts/internal/check_broken_links.py include scripts/internal/clinter.py -include scripts/internal/download_wheels.py +include scripts/internal/download_wheels_appveyor.py +include scripts/internal/download_wheels_github.py include scripts/internal/fix_flake8.py include scripts/internal/generate_manifest.py include scripts/internal/git_pre_commit.py include scripts/internal/print_access_denied.py include scripts/internal/print_announce.py include scripts/internal/print_api_speed.py +include scripts/internal/print_downloads.py include scripts/internal/print_timeline.py +include scripts/internal/print_wheels.py include scripts/internal/purge_installation.py include scripts/internal/tidelift.py -include scripts/internal/win_download_wheels.py include scripts/internal/winmake.py include scripts/iotop.py include scripts/killall.py @@ -16,6 +16,7 @@ DEPS = \ flake8 \ flake8-print \ pyperf \ + pypinfo \ requests \ setuptools \ twine \ @@ -37,7 +38,7 @@ BUILD_OPTS = `$(PYTHON) -c \ # 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 +TEST_PREFIX = PYTHONWARNINGS=always PSUTIL_TESTING=1 PSUTIL_DEBUG=1 all: test @@ -208,6 +209,25 @@ install-git-hooks: ## Install GIT pre-commit hook. chmod +x .git/hooks/pre-commit # =================================================================== +# Wheels +# =================================================================== + +download-wheels-appveyor: ## Download latest wheels hosted on appveyor. + $(PYTHON) scripts/internal/download_wheels_appveyor.py --user giampaolo --project psutil + +download-wheels-github: ## Download latest wheels hosted on github. + $(PYTHON) scripts/internal/download_wheels_github.py --user=giampaolo --project=psutil --tokenfile=~/.github.token + +download-wheels: ## Download wheels from github and appveyor + rm -rf dist + ${MAKE} download-wheels-appveyor + # ${MAKE} download-wheels-github + ${MAKE} print-wheels + +print-wheels: ## Print downloaded wheels + $(PYTHON) scripts/internal/print_wheels.py + +# =================================================================== # Distribution # =================================================================== @@ -219,20 +239,11 @@ sdist: ## Create tar.gz source distribution. ${MAKE} generate-manifest $(PYTHON) setup.py sdist -wheel: ## Generate wheel. - $(PYTHON) setup.py bdist_wheel - -win-download-wheels: ## Download latest wheels hosted on appveyor. - $(PYTHON) scripts/internal/win_download_wheels.py --user giampaolo --project psutil - -download-wheels: ## Download latest wheels hosted on github. - $(PYTHON) scripts/internal/download_wheels.py --user=giampaolo --project=psutil --tokenfile=~/.github.token - upload-src: ## Upload source tarball on https://pypi.org/project/psutil/ ${MAKE} sdist - $(PYTHON) setup.py sdist upload + $(PYTHON) -m twine upload dist/*.tar.gz -upload-win-wheels: ## Upload wheels in dist/* directory on PyPI. +upload-wheels: ## Upload wheels in dist/* directory on PyPI. $(PYTHON) -m twine upload dist/*.whl # --- others @@ -253,7 +264,7 @@ pre-release: ## Check if we're ready to produce a new release. ${MAKE} install ${MAKE} generate-manifest git diff MANIFEST.in > /dev/null # ...otherwise 'git diff-index HEAD' will complain - ${MAKE} win-download-wheels + ${MAKE} download-wheels ${MAKE} sdist $(PYTHON) -c \ "from psutil import __version__ as ver; \ @@ -294,6 +305,9 @@ print-api-speed: ## Benchmark all API calls ${MAKE} build @$(TEST_PREFIX) $(PYTHON) scripts/internal/print_api_speed.py $(ARGS) +print-downloads: ## Print PYPI download statistics + $(PYTHON) scripts/internal/print_downloads.py + # =================================================================== # Misc # =================================================================== @@ -77,7 +77,7 @@ Quick links - `Download <https://pypi.org/project/psutil/#files>`_ - `Forum <http://groups.google.com/group/psutil/topics>`_ - `StackOverflow <https://stackoverflow.com/questions/tagged/psutil>`_ -- `Blog <http://grodola.blogspot.com/search/label/psutil>`_ +- `Blog <https://gmpy.dev/tags/psutil>`_ - `Development guide <https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst>`_ - `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`_ @@ -100,7 +100,7 @@ psutil currently supports the following platforms: - **Sun Solaris** - **AIX** -...both **32-bit** and **64-bit** architectures. Supported Python versions are **2.6**, **2.7** and **3.4+**. `PyPy3 <http://pypy.org/>`__ is also known to work. +...both **32-bit** and **64-bit** architectures. Supported Python versions are **2.6**, **2.7** and **3.4+**, `PyPy <http://pypy.org/>`__ 2.7 and 3.X. psutil for enterprise ===================== @@ -131,19 +131,12 @@ Security To report a security vulnerability, please use the `Tidelift security contact`_. Tidelift will coordinate the fix and disclosure. -Example applications -==================== - -+------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+ -| .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/procinfo-small.png | .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/top-small.png | -| :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/procinfo.png | :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/top.png | -+------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+ -| .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/procsmem-small.png | .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/pmap-small.png | -| :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/procsmem.png | :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/pmap.png | -+------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+ +Sponsorship +=========== -Also see `scripts directory <https://github.com/giampaolo/psutil/tree/master/scripts>`__ -and `doc recipes <http://psutil.readthedocs.io/#recipes/>`__. +A lot of time and effort went into making psutil as it is today. If you whish +to help its future development consider +`sponsoring <https://github.com/sponsors/giampaolo>`__ it. Projects using psutil ===================== @@ -154,9 +147,7 @@ psutil has roughly the following monthly downloads: :target: https://pepy.tech/project/psutil :alt: Downloads -There are over -`10.000 open source projects <https://libraries.io/pypi/psutil/dependent_repositories?page=1>`__ -on github which depend from psutil. +...and has over 55,000 projects on GitHub depending from it. Here's some I find particularly interesting: - https://github.com/google/grr @@ -166,16 +157,14 @@ Here's some I find particularly interesting: - https://github.com/ajenti/ajenti - https://github.com/home-assistant/home-assistant/ - Portings ======== - Go: https://github.com/shirou/gopsutil - C: https://github.com/hamon-in/cpslib -- Rust: https://github.com/borntyping/rust-psutil +- Rust: https://github.com/rust-psutil/rust-psutil - Nim: https://github.com/johnscillieri/psutil-nim - Example usages ============== @@ -514,7 +503,7 @@ Windows services 'username': 'NT AUTHORITY\\LocalService'} -.. _`Giampaolo Rodola`: http://grodola.blogspot.com/p/about.html +.. _`Giampaolo Rodola`: https://gmpy.dev/about .. _`donation`: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8 .. _Tidelift security contact: https://tidelift.com/security .. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme diff --git a/docs/index.rst b/docs/index.rst index 699ea1f1..97d6b420 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ 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>`__ +- `Blog <https://gmpy.dev/tags/psutil>`__ - `Forum <http://groups.google.com/group/psutil/topics>`__ - `Download <https://pypi.org/project/psutil/#files>`__ - `Development guide <https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst>`_ @@ -268,16 +268,17 @@ CPU .. function:: getloadavg() Return the average system load over the last 1, 5 and 15 minutes as a tuple. - The load represents the processes which are in a runnable state, either + The "load" represents the processes which are in a runnable state, either using the CPU or waiting to use the CPU (e.g. waiting for disk I/O). On UNIX systems this relies on `os.getloadavg`_. On Windows this is emulated by using a Windows API that spawns a thread which keeps running in - background and updates the load average every 5 seconds, mimicking the UNIX - behavior. Thus, the first time this is called and for the next 5 seconds + background and updates results every 5 seconds, mimicking the UNIX behavior. + Thus, on Windows, the first time this is called and for the next 5 seconds it will return a meaningless ``(0.0, 0.0, 0.0)`` tuple. The numbers returned only make sense if related to the number of CPU cores - installed on the system. So, for instance, `3.14` on a system with 10 CPU - cores means that the system load was 31.4% percent over the last N minutes. + installed on the system. So, for instance, a value of `3.14` on a system + with 10 logical CPUs means that the system load was 31.4% percent over the + last N minutes. .. code-block:: python @@ -686,7 +687,8 @@ Network system as a dictionary whose keys are the NIC names and value is a named tuple with the following fields: - - **isup**: a bool indicating whether the NIC is up and running. + - **isup**: a bool indicating whether the NIC is up and running (meaning + ethernet cable or Wi-Fi is connected). - **duplex**: the duplex communication type; it can be either :const:`NIC_DUPLEX_FULL`, :const:`NIC_DUPLEX_HALF` or :const:`NIC_DUPLEX_UNKNOWN`. @@ -705,6 +707,7 @@ Network .. versionadded:: 3.0.0 + .. versionchanged:: 5.7.3 `isup` on UNIX also checks whether the NIC is running. Sensors ------- @@ -819,7 +822,7 @@ Other system info Return users currently connected on the system as a list of named tuples including the following fields: - - **user**: the name of the user. + - **name**: the name of the user. - **terminal**: the tty or pseudo-tty associated with the user, if any, else ``None``. - **host**: the host name associated with the entry, if any. @@ -1143,11 +1146,10 @@ Process class >>> psutil.Process().environ() {'LC_NUMERIC': 'it_IT.UTF-8', 'QT_QPA_PLATFORMTHEME': 'appmenu-qt5', 'IM_CONFIG_PHASE': '1', 'XDG_GREETER_DATA_DIR': '/var/lib/lightdm-data/giampaolo', 'GNOME_DESKTOP_SESSION_ID': 'this-is-deprecated', 'XDG_CURRENT_DESKTOP': 'Unity', 'UPSTART_EVENTS': 'started starting', 'GNOME_KEYRING_PID': '', 'XDG_VTNR': '7', 'QT_IM_MODULE': 'ibus', 'LOGNAME': 'giampaolo', 'USER': 'giampaolo', 'PATH': '/home/giampaolo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/giampaolo/svn/sysconf/bin', 'LC_PAPER': 'it_IT.UTF-8', 'GNOME_KEYRING_CONTROL': '', 'GTK_IM_MODULE': 'ibus', 'DISPLAY': ':0', 'LANG': 'en_US.UTF-8', 'LESS_TERMCAP_se': '\x1b[0m', 'TERM': 'xterm-256color', 'SHELL': '/bin/bash', 'XDG_SESSION_PATH': '/org/freedesktop/DisplayManager/Session0', 'XAUTHORITY': '/home/giampaolo/.Xauthority', 'LANGUAGE': 'en_US', 'COMPIZ_CONFIG_PROFILE': 'ubuntu', 'LC_MONETARY': 'it_IT.UTF-8', 'QT_LINUX_ACCESSIBILITY_ALWAYS_ON': '1', 'LESS_TERMCAP_me': '\x1b[0m', 'LESS_TERMCAP_md': '\x1b[01;38;5;74m', 'LESS_TERMCAP_mb': '\x1b[01;31m', 'HISTSIZE': '100000', 'UPSTART_INSTANCE': '', 'CLUTTER_IM_MODULE': 'xim', 'WINDOWID': '58786407', 'EDITOR': 'vim', 'SESSIONTYPE': 'gnome-session', 'XMODIFIERS': '@im=ibus', 'GPG_AGENT_INFO': '/home/giampaolo/.gnupg/S.gpg-agent:0:1', 'HOME': '/home/giampaolo', 'HISTFILESIZE': '100000', 'QT4_IM_MODULE': 'xim', 'GTK2_MODULES': 'overlay-scrollbar', 'XDG_SESSION_DESKTOP': 'ubuntu', 'SHLVL': '1', 'XDG_RUNTIME_DIR': '/run/user/1000', 'INSTANCE': 'Unity', 'LC_ADDRESS': 'it_IT.UTF-8', 'SSH_AUTH_SOCK': '/run/user/1000/keyring/ssh', 'VTE_VERSION': '4205', 'GDMSESSION': 'ubuntu', 'MANDATORY_PATH': '/usr/share/gconf/ubuntu.mandatory.path', 'VISUAL': 'vim', 'DESKTOP_SESSION': 'ubuntu', 'QT_ACCESSIBILITY': '1', 'XDG_SEAT_PATH': '/org/freedesktop/DisplayManager/Seat0', 'LESSCLOSE': '/usr/bin/lesspipe %s %s', 'LESSOPEN': '| /usr/bin/lesspipe %s', 'XDG_SESSION_ID': 'c2', 'DBUS_SESSION_BUS_ADDRESS': 'unix:abstract=/tmp/dbus-9GAJpvnt8r', '_': '/usr/bin/python', 'DEFAULTS_PATH': '/usr/share/gconf/ubuntu.default.path', 'LC_IDENTIFICATION': 'it_IT.UTF-8', 'LESS_TERMCAP_ue': '\x1b[0m', 'UPSTART_SESSION': 'unix:abstract=/com/ubuntu/upstart-session/1000/1294', 'XDG_CONFIG_DIRS': '/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg', 'GTK_MODULES': 'gail:atk-bridge:unity-gtk-module', 'XDG_SESSION_TYPE': 'x11', 'PYTHONSTARTUP': '/home/giampaolo/.pythonstart', 'LC_NAME': 'it_IT.UTF-8', 'OLDPWD': '/home/giampaolo/svn/curio_giampaolo/tests', 'GDM_LANG': 'en_US', 'LC_TELEPHONE': 'it_IT.UTF-8', 'HISTCONTROL': 'ignoredups:erasedups', 'LC_MEASUREMENT': 'it_IT.UTF-8', 'PWD': '/home/giampaolo/svn/curio_giampaolo', 'JOB': 'gnome-session', 'LESS_TERMCAP_us': '\x1b[04;38;5;146m', 'UPSTART_JOB': 'unity-settings-daemon', 'LC_TIME': 'it_IT.UTF-8', 'LESS_TERMCAP_so': '\x1b[38;5;246m', 'PAGER': 'less', 'XDG_DATA_DIRS': '/usr/share/ubuntu:/usr/share/gnome:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop', 'XDG_SEAT': 'seat0'} - Availability: Linux, macOS, Windows, SunOS - .. versionadded:: 4.0.0 .. versionchanged:: 5.3.0 added SunOS support .. versionchanged:: 5.6.3 added AIX suport + .. versionchanged:: 5.7.3 added BSD suport .. method:: create_time() @@ -1626,7 +1628,7 @@ Process class (USS, PSS and swap). The additional metrics provide a better representation of "effective" process memory consumption (in case of USS) as explained in detail in this - `blog post <http://grodola.blogspot.com/2016/02/psutil-4-real-process-memory-and-environ.html>`__. + `blog post <https://gmpy.dev/blog/2016/real-process-memory-and-environ-in-python>`__. It does so by passing through the whole process address. As such it usually requires higher user privileges than :meth:`memory_info` and is considerably slower. @@ -2492,6 +2494,7 @@ If you want to develop psutil take a look at the `development guide`_. Platforms support history ========================= +* psutil 5.7.1 (2020-07): **Windows Nano** * psutil 5.7.0 (2020-02): drop Windows XP & Server 2003 support * psutil 5.7.0 (2020-02): **PyPy** on Windows * psutil 5.4.0 (2017-11): **AIX** @@ -2506,6 +2509,14 @@ Supported Python versions are 2.6, 2.7, 3.4+ and PyPy3. Timeline ======== +- 2020-07-15: + `5.7.2 <https://pypi.org/project/psutil/5.7.2/#files>`__ - + `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#571>`__ - + `diff <https://github.com/giampaolo/psutil/compare/release-5.7.1...release-5.7.2#files_bucket>`__ +- 2020-07-15: + `5.7.1 <https://pypi.org/project/psutil/5.7.1/#files>`__ - + `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#571>`__ - + `diff <https://github.com/giampaolo/psutil/compare/release-5.7.0...release-5.7.1#files_bucket>`__ - 2020-02-18: `5.7.0 <https://pypi.org/project/psutil/5.7.0/#files>`__ - `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#570>`__ - @@ -2838,7 +2849,7 @@ Timeline .. _`getfsstat`: http://www.manpagez.com/man/2/getfsstat/ .. _`GetPriorityClass`: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getpriorityclass .. _`GetExitCodeProcess`: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess -.. _`Giampaolo Rodola`: http://grodola.blogspot.com/p/about.html +.. _`Giampaolo Rodola`: https://gmpy.dev/about .. _`hash`: https://docs.python.org/3/library/functions.html#hash .. _`ifconfig.py`: https://github.com/giampaolo/psutil/blob/master/scripts/ifconfig.py .. _`ioprio_get`: https://linux.die.net/man/2/ioprio_get diff --git a/psutil/__init__.py b/psutil/__init__.py index 7fdbc0fc..9d43f991 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -226,7 +226,7 @@ AF_LINK = _psplatform.AF_LINK __all__.extend(_psplatform.__extra__all__) __author__ = "Giampaolo Rodola'" -__version__ = "5.7.1" +__version__ = "5.7.3" version_info = tuple([int(num) for num in __version__.split('.')]) _timer = getattr(time, 'monotonic', time.time) @@ -853,7 +853,7 @@ class Process(object): """ return self._proc.cpu_num() - # Linux, macOS, Windows, Solaris, AIX + # All platforms has it, but maybe not in the future. if hasattr(_psplatform.Process, "environ"): def environ(self): diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index 49ad1e99..9565406b 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -51,7 +51,7 @@ if FREEBSD: cext.SWAIT: _common.STATUS_WAITING, cext.SLOCK: _common.STATUS_LOCKED, } -elif OPENBSD or NETBSD: +elif OPENBSD: PROC_STATUSES = { cext.SIDL: _common.STATUS_IDLE, cext.SSLEEP: _common.STATUS_SLEEPING, @@ -76,12 +76,11 @@ elif OPENBSD or NETBSD: elif NETBSD: PROC_STATUSES = { cext.SIDL: _common.STATUS_IDLE, - cext.SACTIVE: _common.STATUS_RUNNING, - cext.SDYING: _common.STATUS_ZOMBIE, + cext.SSLEEP: _common.STATUS_SLEEPING, cext.SSTOP: _common.STATUS_STOPPED, cext.SZOMB: _common.STATUS_ZOMBIE, - cext.SDEAD: _common.STATUS_DEAD, - cext.SSUSPENDED: _common.STATUS_SUSPENDED, # unique to NetBSD + cext.SRUN: _common.STATUS_WAKING, + cext.SONPROC: _common.STATUS_RUNNING, } TCP_STATUSES = { @@ -354,7 +353,7 @@ def net_if_stats(): for name in names: try: mtu = cext_posix.net_if_mtu(name) - isup = cext_posix.net_if_flags(name) + isup = cext_posix.net_if_is_running(name) duplex, speed = cext_posix.net_if_duplex_speed(name) except OSError as err: # https://github.com/giampaolo/psutil/issues/1279 @@ -551,10 +550,10 @@ def wrap_exceptions(fun): try: return fun(self, *args, **kwargs) except ProcessLookupError: - if not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name) - else: + if is_zombie(self.pid): raise ZombieProcess(self.pid, self._name, self._ppid) + else: + raise NoSuchProcess(self.pid, self._name) except PermissionError: raise AccessDenied(self.pid, self._name) except OSError: @@ -576,10 +575,10 @@ def wrap_exceptions_procfs(inst): # 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 not pid_exists(inst.pid): - raise NoSuchProcess(inst.pid, inst._name) - else: + if is_zombie(inst.pid): raise ZombieProcess(inst.pid, inst._name, inst._ppid) + else: + raise NoSuchProcess(inst.pid, inst._name) except PermissionError: raise AccessDenied(inst.pid, inst._name) @@ -670,6 +669,10 @@ class Process(object): return cext.proc_cmdline(self.pid) @wrap_exceptions + def environ(self): + return cext.proc_environ(self.pid) + + @wrap_exceptions def terminal(self): tty_nr = self.oneshot()[kinfo_proc_map['ttynr']] tmap = _psposix.get_terminal_map() diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index 3e3caace..3c9ff281 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -843,10 +843,6 @@ class Connections: else: ip = socket.inet_ntop(family, base64.b16decode(ip)) else: # IPv6 - # old version - let's keep it, just in case... - # ip = ip.decode('hex') - # return socket.inet_ntop(socket.AF_INET6, - # ''.join(ip[i:i+4][::-1] for i in range(0, 16, 4))) ip = base64.b16decode(ip) try: # see: https://github.com/giampaolo/psutil/issues/201 @@ -1031,7 +1027,7 @@ def net_if_stats(): for name in names: try: mtu = cext_posix.net_if_mtu(name) - isup = cext_posix.net_if_flags(name) + isup = cext_posix.net_if_is_running(name) duplex, speed = cext.net_if_duplex_speed(name) except OSError as err: # https://github.com/giampaolo/psutil/issues/1279 @@ -1340,7 +1336,8 @@ def sensors_battery(): return int(ret) if ret.isdigit() else ret return None - bats = [x for x in os.listdir(POWER_SUPPLY_PATH) if x.startswith('BAT')] + bats = [x for x in os.listdir(POWER_SUPPLY_PATH) if x.startswith('BAT') or + 'battery' in x.lower()] if not bats: return None # Get the first available battery. Usually this is "BAT0", except @@ -1358,12 +1355,11 @@ def sensors_battery(): energy_full = multi_cat( root + "/energy_full", root + "/charge_full") - if energy_now is None or power_now is None: - return None + time_to_empty = multi_cat(root + "/time_to_empty_now") # Percent. If we have energy_full the percentage will be more # accurate compared to reading /capacity file (float vs. int). - if energy_full is not None: + if energy_full is not None and energy_now is not None: try: percent = 100.0 * energy_now / energy_full except ZeroDivisionError: @@ -1395,11 +1391,17 @@ def sensors_battery(): # 013937745fd9050c30146290e8f963d65c0179e6/bin/battery.py#L55 if power_plugged: secsleft = _common.POWER_TIME_UNLIMITED - else: + elif energy_now is not None and power_now is not None: try: secsleft = int(energy_now / power_now * 3600) except ZeroDivisionError: secsleft = _common.POWER_TIME_UNKNOWN + elif time_to_empty is not None: + secsleft = int(time_to_empty * 60) + if secsleft < 0: + secsleft = _common.POWER_TIME_UNKNOWN + else: + secsleft = _common.POWER_TIME_UNKNOWN return _common.sbattery(percent, secsleft, power_plugged) diff --git a/psutil/_psosx.py b/psutil/_psosx.py index e4296495..6a189931 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -262,7 +262,7 @@ def net_if_stats(): for name in names: try: mtu = cext_posix.net_if_mtu(name) - isup = cext_posix.net_if_flags(name) + isup = cext_posix.net_if_is_running(name) duplex, speed = cext_posix.net_if_duplex_speed(name) except OSError as err: # https://github.com/giampaolo/psutil/issues/1279 @@ -324,6 +324,14 @@ def pids(): pid_exists = _psposix.pid_exists +def is_zombie(pid): + try: + st = cext.proc_kinfo_oneshot(pid)[kinfo_proc_map['status']] + return st == cext.SZOMB + except Exception: + return False + + def wrap_exceptions(fun): """Decorator which translates bare OSError exceptions into NoSuchProcess and AccessDenied. @@ -333,7 +341,10 @@ def wrap_exceptions(fun): try: return fun(self, *args, **kwargs) except ProcessLookupError: - raise NoSuchProcess(self.pid, self._name) + if is_zombie(self.pid): + raise ZombieProcess(self.pid, self._name, self._ppid) + else: + raise NoSuchProcess(self.pid, self._name) except PermissionError: raise AccessDenied(self.pid, self._name) except cext.ZombieProcessError: diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c index 953fcd08..c4450d7d 100644 --- a/psutil/_psutil_bsd.c +++ b/psutil/_psutil_bsd.c @@ -57,6 +57,7 @@ #include <net/route.h> #include <netinet/in.h> // process open files/connections #include <sys/un.h> +#include <kvm.h> #include "_psutil_common.h" #include "_psutil_posix.h" @@ -392,6 +393,145 @@ psutil_proc_cmdline(PyObject *self, PyObject *args) { /* + * Return process environment as a Python dictionary + */ +PyObject * +psutil_proc_environ(PyObject *self, PyObject *args) { + int i, cnt = -1; + long pid; + char *s, **envs, errbuf[_POSIX2_LINE_MAX]; + PyObject *py_value=NULL, *py_retdict=NULL; + kvm_t *kd; +#ifdef PSUTIL_NETBSD + struct kinfo_proc2 *p; +#else + struct kinfo_proc *p; +#endif + + if (!PyArg_ParseTuple(args, "l", &pid)) + return NULL; + +#if defined(PSUTIL_FREEBSD) + kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf); +#else + kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); +#endif + if (!kd) { + convert_kvm_err("kvm_openfiles", errbuf); + return NULL; + } + + py_retdict = PyDict_New(); + if (!py_retdict) + goto error; + +#if defined(PSUTIL_FREEBSD) + p = kvm_getprocs(kd, KERN_PROC_PID, pid, &cnt); +#elif defined(PSUTIL_OPENBSD) + p = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(*p), &cnt); +#elif defined(PSUTIL_NETBSD) + p = kvm_getproc2(kd, KERN_PROC_PID, pid, sizeof(*p), &cnt); +#endif + if (!p) { + NoSuchProcess("kvm_getprocs"); + goto error; + } + if (cnt <= 0) { + NoSuchProcess(cnt < 0 ? kvm_geterr(kd) : "kvm_getprocs: cnt==0"); + goto error; + } + + // On *BSD kernels there are a few kernel-only system processes without an + // environment (See e.g. "procstat -e 0 | 1 | 2 ..." on FreeBSD.) + // + // Some system process have no stats attached at all + // (they are marked with P_SYSTEM.) + // + // On FreeBSD, it's possible that the process is swapped or paged out, + // then there no access to the environ stored in the process' user area. + // + // On NetBSD, we cannot call kvm_getenvv2() for a zombie process. + // + // To make unittest suite happy, return an empty environment. + // +#if defined(PSUTIL_FREEBSD) +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 700000) + if (!((p)->ki_flag & P_INMEM) || ((p)->ki_flag & P_SYSTEM)) { +#else + if ((p)->ki_flag & P_SYSTEM) { +#endif +#elif defined(PSUTIL_NETBSD) + if ((p)->p_stat == SZOMB) { +#elif defined(PSUTIL_OPENBSD) + if ((p)->p_flag & P_SYSTEM) { +#endif + kvm_close(kd); + return py_retdict; + } + +#if defined(PSUTIL_NETBSD) + envs = kvm_getenvv2(kd, p, 0); +#else + envs = kvm_getenvv(kd, p, 0); +#endif + if (!envs) { + // Map to "psutil" general high-level exceptions + switch (errno) { + case 0: + // Process has cleared it's environment, return empty one + kvm_close(kd); + return py_retdict; + case EPERM: + AccessDenied("kvm_getenvv"); + break; + case ESRCH: + NoSuchProcess("kvm_getenvv"); + break; +#if defined(PSUTIL_FREEBSD) + case ENOMEM: + // Unfortunately, under FreeBSD kvm_getenvv() returns + // failure for certain processes ( e.g. try + // "sudo procstat -e <pid of your XOrg server>".) + // Map the error condition to 'AccessDenied'. + sprintf(errbuf, + "kvm_getenvv(pid=%ld, ki_uid=%d): errno=ENOMEM", + pid, p->ki_uid); + AccessDenied(errbuf); + break; +#endif + default: + sprintf(errbuf, "kvm_getenvv(pid=%ld)", pid); + PyErr_SetFromOSErrnoWithSyscall(errbuf); + break; + } + goto error; + } + + for (i = 0; envs[i] != NULL; i++) { + s = strchr(envs[i], '='); + if (!s) + continue; + *s++ = 0; + py_value = PyUnicode_DecodeFSDefault(s); + if (!py_value) + goto error; + if (PyDict_SetItemString(py_retdict, envs[i], py_value)) { + goto error; + } + Py_DECREF(py_value); + } + + kvm_close(kd); + return py_retdict; + +error: + Py_XDECREF(py_value); + Py_XDECREF(py_retdict); + kvm_close(kd); + return NULL; +} + +/* * Return the number of logical CPUs in the system. * XXX this could be shared with macOS */ @@ -617,8 +757,10 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { strlcat(opts, ",softdep", sizeof(opts)); if (flags & MNT_NOSYMFOLLOW) strlcat(opts, ",nosymfollow", sizeof(opts)); +#ifdef MNT_GJOURNAL if (flags & MNT_GJOURNAL) strlcat(opts, ",gjournal", sizeof(opts)); +#endif if (flags & MNT_MULTILABEL) strlcat(opts, ",multilabel", sizeof(opts)); if (flags & MNT_ACLS) @@ -627,8 +769,10 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { strlcat(opts, ",noclusterr", sizeof(opts)); if (flags & MNT_NOCLUSTERW) strlcat(opts, ",noclusterw", sizeof(opts)); +#ifdef MNT_NFS4ACLS if (flags & MNT_NFS4ACLS) strlcat(opts, ",nfs4acls", sizeof(opts)); +#endif #elif PSUTIL_NETBSD if (flags & MNT_NODEV) strlcat(opts, ",nodev", sizeof(opts)); @@ -831,7 +975,7 @@ psutil_users(PyObject *self, PyObject *args) { py_tty, // tty py_hostname, // hostname (float)ut.ut_time, // start time -#ifdef PSUTIL_OPENBSD +#if defined(PSUTIL_OPENBSD) || (defined(__FreeBSD_version) && __FreeBSD_version < 900000) -1 // process id (set to None later) #else ut.ut_pid // TODO: use PyLong_FromPid @@ -956,6 +1100,8 @@ static PyMethodDef mod_methods[] = { {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, "Return an XML string to determine the number physical CPUs."}, #endif + {"proc_environ", psutil_proc_environ, METH_VARARGS, + "Return process environment"}, // --- system-related functions @@ -1060,7 +1206,9 @@ static PyMethodDef mod_methods[] = { if (PyModule_AddIntConstant(mod, "SSLEEP", LSSLEEP)) INITERR; if (PyModule_AddIntConstant(mod, "SSTOP", LSSTOP)) INITERR; if (PyModule_AddIntConstant(mod, "SZOMB", LSZOMB)) INITERR; +#if __NetBSD_Version__ < 500000000 if (PyModule_AddIntConstant(mod, "SDEAD", LSDEAD)) INITERR; +#endif if (PyModule_AddIntConstant(mod, "SONPROC", LSONPROC)) INITERR; // unique to NetBSD if (PyModule_AddIntConstant(mod, "SSUSPENDED", LSSUSPENDED)) INITERR; diff --git a/psutil/_psutil_common.c b/psutil/_psutil_common.c index d63b4d9c..f821aba3 100644 --- a/psutil/_psutil_common.c +++ b/psutil/_psutil_common.c @@ -167,6 +167,26 @@ psutil_setup(void) { } +// ============================================================================ +// Utility functions (BSD) +// ============================================================================ + +#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD) +void +convert_kvm_err(const char *syscall, char *errbuf) { + char fullmsg[8192]; + + sprintf(fullmsg, "(originated from %s: %s)", syscall, errbuf); + if (strstr(errbuf, "Permission denied") != NULL) + AccessDenied(fullmsg); + else if (strstr(errbuf, "Operation not permitted") != NULL) + AccessDenied(fullmsg); + else + PyErr_Format(PyExc_RuntimeError, fullmsg); +} +#endif + + // ==================================================================== // --- Windows // ==================================================================== @@ -264,10 +284,6 @@ psutil_loadlibs() { "ntdll.dll", "NtSetInformationProcess"); if (! NtSetInformationProcess) return 1; - WinStationQueryInformationW = psutil_GetProcAddressFromLib( - "winsta.dll", "WinStationQueryInformationW"); - if (! WinStationQueryInformationW) - return 1; NtQueryObject = psutil_GetProcAddressFromLib( "ntdll.dll", "NtQueryObject"); if (! NtQueryObject) @@ -320,6 +336,13 @@ psutil_loadlibs() { // minumum requirement: Win 7 GetLogicalProcessorInformationEx = psutil_GetProcAddressFromLib( "kernel32", "GetLogicalProcessorInformationEx"); + // minimum requirements: Windows Server Core + WTSEnumerateSessionsW = psutil_GetProcAddressFromLib( + "wtsapi32.dll", "WTSEnumerateSessionsW"); + WTSQuerySessionInformationW = psutil_GetProcAddressFromLib( + "wtsapi32.dll", "WTSQuerySessionInformationW"); + WTSFreeMemory = psutil_GetProcAddressFromLib( + "wtsapi32.dll", "WTSFreeMemory"); PyErr_Clear(); return 0; diff --git a/psutil/_psutil_common.h b/psutil/_psutil_common.h index 2408c9f6..3162772e 100644 --- a/psutil/_psutil_common.h +++ b/psutil/_psutil_common.h @@ -105,6 +105,12 @@ void psutil_debug(const char* format, ...); int psutil_setup(void); // ==================================================================== +// --- BSD +// ==================================================================== + +void convert_kvm_err(const char *syscall, char *errbuf); + +// ==================================================================== // --- Windows // ==================================================================== diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c index c94ec035..41547438 100644 --- a/psutil/_psutil_linux.c +++ b/psutil/_psutil_linux.c @@ -662,6 +662,8 @@ static PyMethodDef mod_methods[] = { if (PyModule_AddIntConstant(mod, "DUPLEX_FULL", DUPLEX_FULL)) INITERR; if (PyModule_AddIntConstant(mod, "DUPLEX_UNKNOWN", DUPLEX_UNKNOWN)) INITERR; + psutil_setup(); + if (mod == NULL) INITERR; #if PY_MAJOR_VERSION >= 3 diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c index f7f8b92d..1182765d 100644 --- a/psutil/_psutil_posix.c +++ b/psutil/_psutil_posix.c @@ -385,7 +385,7 @@ error: * http://www.i-scream.org/libstatgrab/ */ static PyObject * -psutil_net_if_flags(PyObject *self, PyObject *args) { +psutil_net_if_is_running(PyObject *self, PyObject *args) { char *nic_name; int sock = -1; int ret; @@ -404,7 +404,7 @@ psutil_net_if_flags(PyObject *self, PyObject *args) { goto error; close(sock); - if ((ifr.ifr_flags & IFF_UP) != 0) + if ((ifr.ifr_flags & IFF_RUNNING) != 0) return Py_BuildValue("O", Py_True); else return Py_BuildValue("O", Py_False); @@ -621,8 +621,8 @@ static PyMethodDef mod_methods[] = { "Retrieve NICs information"}, {"net_if_mtu", psutil_net_if_mtu, METH_VARARGS, "Retrieve NIC MTU"}, - {"net_if_flags", psutil_net_if_flags, METH_VARARGS, - "Retrieve NIC flags"}, + {"net_if_is_running", psutil_net_if_is_running, METH_VARARGS, + "Return True if the NIC is running."}, #if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS, "Return NIC stats."}, diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index be4759ac..c8b2f383 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -22,7 +22,6 @@ #include <Psapi.h> // memory_info(), memory_maps() #include <signal.h> #include <tlhelp32.h> // threads(), PROCESSENTRY32 -#include <wtsapi32.h> // users() // Link with Iphlpapi.lib #pragma comment(lib, "IPHLPAPI.lib") @@ -1191,17 +1190,17 @@ psutil_proc_is_suspended(PyObject *self, PyObject *args) { static PyObject * psutil_users(PyObject *self, PyObject *args) { HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; - WCHAR *buffer_user = NULL; - LPTSTR buffer_addr = NULL; - PWTS_SESSION_INFO sessions = NULL; + LPWSTR buffer_user = NULL; + LPWSTR buffer_addr = NULL; + LPWSTR buffer_info = NULL; + PWTS_SESSION_INFOW sessions = NULL; DWORD count; DWORD i; DWORD sessionId; DWORD bytes; PWTS_CLIENT_ADDRESS address; char address_str[50]; - WINSTATION_INFO station_info; - ULONG returnLen; + PWTSINFOW wts_info; PyObject *py_tuple = NULL; PyObject *py_address = NULL; PyObject *py_username = NULL; @@ -1210,8 +1209,21 @@ psutil_users(PyObject *self, PyObject *args) { if (py_retlist == NULL) return NULL; - if (WTSEnumerateSessions(hServer, 0, 1, &sessions, &count) == 0) { - PyErr_SetFromOSErrnoWithSyscall("WTSEnumerateSessions"); + if (WTSEnumerateSessionsW == NULL || + WTSQuerySessionInformationW == NULL || + WTSFreeMemory == NULL) { + // If we don't run in an environment that is a Remote Desktop Services environment + // the Wtsapi32 proc might not be present. + // https://docs.microsoft.com/en-us/windows/win32/termserv/run-time-linking-to-wtsapi32-dll + return py_retlist; + } + + if (WTSEnumerateSessionsW(hServer, 0, 1, &sessions, &count) == 0) { + if (ERROR_CALL_NOT_IMPLEMENTED == GetLastError()) { + // On Windows Nano server, the Wtsapi32 API can be present, but return WinError 120. + return py_retlist; + } + PyErr_SetFromOSErrnoWithSyscall("WTSEnumerateSessionsW"); goto error; } @@ -1223,9 +1235,12 @@ psutil_users(PyObject *self, PyObject *args) { WTSFreeMemory(buffer_user); if (buffer_addr != NULL) WTSFreeMemory(buffer_addr); + if (buffer_info != NULL) + WTSFreeMemory(buffer_info); buffer_user = NULL; buffer_addr = NULL; + buffer_info = NULL; // username bytes = 0; @@ -1239,21 +1254,22 @@ psutil_users(PyObject *self, PyObject *args) { // address bytes = 0; - if (WTSQuerySessionInformation(hServer, sessionId, WTSClientAddress, - &buffer_addr, &bytes) == 0) { - PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformation"); + if (WTSQuerySessionInformationW(hServer, sessionId, WTSClientAddress, + &buffer_addr, &bytes) == 0) { + PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); goto error; } address = (PWTS_CLIENT_ADDRESS)buffer_addr; - if (address->AddressFamily == 0) { // AF_INET + if (address->AddressFamily == 2) { // AF_INET == 2 sprintf_s(address_str, _countof(address_str), "%u.%u.%u.%u", - address->Address[0], - address->Address[1], + // The IP address is offset by two bytes from the start of the Address member of the WTS_CLIENT_ADDRESS structure. address->Address[2], - address->Address[3]); + address->Address[3], + address->Address[4], + address->Address[5]); py_address = Py_BuildValue("s", address_str); if (!py_address) goto error; @@ -1263,26 +1279,23 @@ psutil_users(PyObject *self, PyObject *args) { } // login time - if (! WinStationQueryInformationW( - hServer, - sessionId, - WinStationInformation, - &station_info, - sizeof(station_info), - &returnLen)) - { - PyErr_SetFromOSErrnoWithSyscall("WinStationQueryInformationW"); + bytes = 0; + if (WTSQuerySessionInformationW(hServer, sessionId, WTSSessionInfo, + &buffer_info, &bytes) == 0) { + PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); goto error; } + wts_info = (PWTSINFOW)buffer_info; py_username = PyUnicode_FromWideChar(buffer_user, wcslen(buffer_user)); if (py_username == NULL) goto error; + py_tuple = Py_BuildValue( "OOd", py_username, py_address, - psutil_FiletimeToUnixTime(station_info.ConnectTime) + psutil_LargeIntegerToUnixTime(wts_info->ConnectTime) ); if (!py_tuple) goto error; @@ -1296,6 +1309,7 @@ psutil_users(PyObject *self, PyObject *args) { WTSFreeMemory(sessions); WTSFreeMemory(buffer_user); WTSFreeMemory(buffer_addr); + WTSFreeMemory(buffer_info); return py_retlist; error: @@ -1310,6 +1324,8 @@ error: WTSFreeMemory(buffer_user); if (buffer_addr != NULL) WTSFreeMemory(buffer_addr); + if (buffer_info != NULL) + WTSFreeMemory(buffer_info); return NULL; } diff --git a/psutil/arch/freebsd/specific.c b/psutil/arch/freebsd/specific.c index 3f37a08e..c7832647 100644 --- a/psutil/arch/freebsd/specific.c +++ b/psutil/arch/freebsd/specific.c @@ -517,7 +517,7 @@ psutil_swap_mem(PyObject *self, PyObject *args) { } -#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 +#if defined(__FreeBSD_version) && __FreeBSD_version >= 701000 PyObject * psutil_proc_cwd(PyObject *self, PyObject *args) { pid_t pid; @@ -795,9 +795,11 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) { case KVME_TYPE_DEAD: path = "[dead]"; break; +#ifdef KVME_TYPE_SG case KVME_TYPE_SG: path = "[sg]"; break; +#endif case KVME_TYPE_UNKNOWN: path = "[unknown]"; break; diff --git a/psutil/arch/freebsd/sys_socks.c b/psutil/arch/freebsd/sys_socks.c index ab61f393..9f7cf8d5 100644 --- a/psutil/arch/freebsd/sys_socks.c +++ b/psutil/arch/freebsd/sys_socks.c @@ -16,6 +16,9 @@ #include <sys/un.h> #include <sys/unpcb.h> #include <sys/sysctl.h> +#if defined(__FreeBSD_version) && __FreeBSD_version < 800000 +#include <netinet/in_systm.h> +#endif #include <netinet/in.h> // for xinpcb struct #include <netinet/ip.h> #include <netinet/in_pcb.h> @@ -30,7 +33,7 @@ static int psutil_nxfiles; int -psutil_populate_xfiles() { +psutil_populate_xfiles(void) { size_t len; if ((psutil_xfiles = malloc(len = sizeof *psutil_xfiles)) == NULL) { diff --git a/psutil/arch/openbsd/specific.c b/psutil/arch/openbsd/specific.c index d97a8f9b..aa4568f5 100644 --- a/psutil/arch/openbsd/specific.c +++ b/psutil/arch/openbsd/specific.c @@ -47,20 +47,6 @@ // ============================================================================ -static void -convert_kvm_err(const char *syscall, char *errbuf) { - char fullmsg[8192]; - - sprintf(fullmsg, "(originated from %s: %s)", syscall, errbuf); - if (strstr(errbuf, "Permission denied") != NULL) - AccessDenied(fullmsg); - else if (strstr(errbuf, "Operation not permitted") != NULL) - AccessDenied(fullmsg); - else - PyErr_Format(PyExc_RuntimeError, fullmsg); -} - - int psutil_kinfo_proc(pid_t pid, struct kinfo_proc *proc) { // Fills a kinfo_proc struct based on process pid. diff --git a/psutil/arch/windows/ntextapi.h b/psutil/arch/windows/ntextapi.h index 8cb00430..ea1f4281 100644 --- a/psutil/arch/windows/ntextapi.h +++ b/psutil/arch/windows/ntextapi.h @@ -19,6 +19,12 @@ typedef LONG NTSTATUS; #define STATUS_NOT_FOUND ((NTSTATUS)0xC0000225L) #define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) +// WtsApi32.h +#define WTS_CURRENT_SERVER_HANDLE ((HANDLE)NULL) +#define WINSTATIONNAME_LENGTH 32 +#define DOMAIN_LENGTH 17 +#define USERNAME_LENGTH 20 + // ================================================================ // Enums // ================================================================ @@ -93,6 +99,53 @@ typedef enum _KWAIT_REASON { MaximumWaitReason } KWAIT_REASON, *PKWAIT_REASON; +// users() +typedef enum _WTS_INFO_CLASS { + WTSInitialProgram, + WTSApplicationName, + WTSWorkingDirectory, + WTSOEMId, + WTSSessionId, + WTSUserName, + WTSWinStationName, + WTSDomainName, + WTSConnectState, + WTSClientBuildNumber, + WTSClientName, + WTSClientDirectory, + WTSClientProductId, + WTSClientHardwareId, + WTSClientAddress, + WTSClientDisplay, + WTSClientProtocolType, + WTSIdleTime, + WTSLogonTime, + WTSIncomingBytes, + WTSOutgoingBytes, + WTSIncomingFrames, + WTSOutgoingFrames, + WTSClientInfo, + WTSSessionInfo, + WTSSessionInfoEx, + WTSConfigInfo, + WTSValidationInfo, // Info Class value used to fetch Validation Information through the WTSQuerySessionInformation + WTSSessionAddressV4, + WTSIsRemoteSession +} WTS_INFO_CLASS; + +typedef enum _WTS_CONNECTSTATE_CLASS { + WTSActive, // User logged on to WinStation + WTSConnected, // WinStation connected to client + WTSConnectQuery, // In the process of connecting to client + WTSShadow, // Shadowing another WinStation + WTSDisconnected, // WinStation logged on without client + WTSIdle, // Waiting for client to connect + WTSListen, // WinStation is listening for connection + WTSReset, // WinStation is being reset + WTSDown, // WinStation is down due to error + WTSInit, // WinStation in initialization +} WTS_CONNECTSTATE_CLASS; + // ================================================================ // Structs. // ================================================================ @@ -309,17 +362,42 @@ typedef struct { } RTL_USER_PROCESS_PARAMETERS_, *PRTL_USER_PROCESS_PARAMETERS_; // users() -typedef struct _WINSTATION_INFO { - BYTE Reserved1[72]; - ULONG SessionId; - BYTE Reserved2[4]; - FILETIME ConnectTime; - FILETIME DisconnectTime; - FILETIME LastInputTime; - FILETIME LoginTime; - BYTE Reserved3[1096]; - FILETIME CurrentTime; -} WINSTATION_INFO, *PWINSTATION_INFO; +typedef struct _WTS_SESSION_INFOW { + DWORD SessionId; // session id + LPWSTR pWinStationName; // name of WinStation this session is + // connected to + WTS_CONNECTSTATE_CLASS State; // connection state (see enum) +} WTS_SESSION_INFOW, * PWTS_SESSION_INFOW; + +#define PWTS_SESSION_INFO PWTS_SESSION_INFOW + +typedef struct _WTS_CLIENT_ADDRESS { + DWORD AddressFamily; // AF_INET, AF_INET6, AF_IPX, AF_NETBIOS, AF_UNSPEC + BYTE Address[20]; // client network address +} WTS_CLIENT_ADDRESS, * PWTS_CLIENT_ADDRESS; + +typedef struct _WTSINFOW { + WTS_CONNECTSTATE_CLASS State; // connection state (see enum) + DWORD SessionId; // session id + DWORD IncomingBytes; + DWORD OutgoingBytes; + DWORD IncomingFrames; + DWORD OutgoingFrames; + DWORD IncomingCompressedBytes; + DWORD OutgoingCompressedBytes; + WCHAR WinStationName[WINSTATIONNAME_LENGTH]; + WCHAR Domain[DOMAIN_LENGTH]; + WCHAR UserName[USERNAME_LENGTH + 1];// name of WinStation this session is + // connected to + LARGE_INTEGER ConnectTime; + LARGE_INTEGER DisconnectTime; + LARGE_INTEGER LastInputTime; + LARGE_INTEGER LogonTime; + LARGE_INTEGER CurrentTime; + +} WTSINFOW, * PWTSINFOW; + +#define PWTSINFO PWTSINFOW // cpu_count_phys() #if (_WIN32_WINNT < 0x0601) // Windows < 7 (Vista and XP) @@ -551,6 +629,32 @@ DWORD (CALLBACK *_GetActiveProcessorCount) ( #define GetActiveProcessorCount _GetActiveProcessorCount +BOOL(CALLBACK *_WTSQuerySessionInformationW) ( + HANDLE hServer, + DWORD SessionId, + WTS_INFO_CLASS WTSInfoClass, + LPWSTR* ppBuffer, + DWORD* pBytesReturned + ); + +#define WTSQuerySessionInformationW _WTSQuerySessionInformationW + +BOOL(CALLBACK *_WTSEnumerateSessionsW)( + HANDLE hServer, + DWORD Reserved, + DWORD Version, + PWTS_SESSION_INFO* ppSessionInfo, + DWORD* pCount + ); + +#define WTSEnumerateSessionsW _WTSEnumerateSessionsW + +VOID(CALLBACK *_WTSFreeMemory)( + IN PVOID pMemory + ); + +#define WTSFreeMemory _WTSFreeMemory + ULONGLONG (CALLBACK *_GetTickCount64) ( void); diff --git a/psutil/arch/windows/wmi.c b/psutil/arch/windows/wmi.c index 42a70df7..0a1fb891 100644 --- a/psutil/arch/windows/wmi.c +++ b/psutil/arch/windows/wmi.c @@ -30,7 +30,7 @@ double load_avg_5m = 0; double load_avg_15m = 0; -VOID CALLBACK LoadAvgCallback(PVOID hCounter) { +VOID CALLBACK LoadAvgCallback(PVOID hCounter, BOOLEAN timedOut) { PDH_FMT_COUNTERVALUE displayValue; double currentLoad; PDH_STATUS err; diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 6a119bf5..b508a629 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -76,7 +76,7 @@ __all__ = [ # constants 'APPVEYOR', 'DEVNULL', 'GLOBAL_TIMEOUT', 'TOLERANCE_SYS_MEM', 'NO_RETRIES', 'PYPY', 'PYTHON_EXE', 'ROOT_DIR', 'SCRIPTS_DIR', 'TESTFN_PREFIX', - 'UNICODE_SUFFIX', 'INVALID_UNICODE_SUFFIX', 'TOX', 'TRAVIS', 'CIRRUS', + 'UNICODE_SUFFIX', 'INVALID_UNICODE_SUFFIX', 'TRAVIS', 'CIRRUS', 'CI_TESTING', 'VALID_PROC_STATUSES', 'TOLERANCE_DISK_USAGE', 'IS_64BIT', "HAS_CPU_AFFINITY", "HAS_CPU_FREQ", "HAS_ENVIRON", "HAS_PROC_IO_COUNTERS", "HAS_IONICE", "HAS_MEMORY_MAPS", "HAS_PROC_CPU_NUM", "HAS_RLIMIT", @@ -117,7 +117,6 @@ __all__ = [ # --- platforms -TOX = os.getenv('TOX') or '' in ('1', 'true') PYPY = '__pypy__' in sys.builtin_module_names # whether we're running this test suite on a Continuous Integration service TRAVIS = 'TRAVIS' in os.environ @@ -186,7 +185,7 @@ HAS_SENSORS_BATTERY = hasattr(psutil, "sensors_battery") try: HAS_BATTERY = HAS_SENSORS_BATTERY and bool(psutil.sensors_battery()) except Exception: - HAS_BATTERY = True + HAS_BATTERY = False HAS_SENSORS_FANS = hasattr(psutil, "sensors_fans") HAS_SENSORS_TEMPERATURES = hasattr(psutil, "sensors_temperatures") HAS_THREADS = hasattr(psutil.Process, "threads") @@ -499,6 +498,9 @@ def terminate(proc_or_pid, sig=signal.SIGTERM, wait_timeout=GLOBAL_TIMEOUT): pass def sendsig(proc, sig): + # XXX: otherwise the build hangs for some reason. + if MACOS and GITHUB_WHEELS: + sig = signal.SIGKILL # If the process received SIGSTOP, SIGCONT is necessary first, # otherwise SIGTERM won't work. if POSIX and sig != signal.SIGKILL: @@ -922,7 +924,7 @@ class TestMemoryLeak(PsutilTestCase): If available (Linux, OSX, Windows), USS memory is used for comparison, since it's supposed to be more precise, see: - http://grodola.blogspot.com/2016/02/psutil-4-real-process-memory-and-environ.html + https://gmpy.dev/blog/2016/real-process-memory-and-environ-in-python If not, RSS memory is used. mallinfo() on Linux and _heapwalk() on Windows may give even more precision, but at the moment are not implemented. @@ -1632,7 +1634,6 @@ def cleanup_test_procs(): # atexit module does not execute exit functions in case of SIGTERM, which # gets sent to test subprocesses, which is a problem if they import this # module. With this it will. See: -# http://grodola.blogspot.com/ -# 2016/02/how-to-always-execute-exit-functions-in-py.html +# https://gmpy.dev/blog/2016/how-to-always-execute-exit-functions-in-python if POSIX: signal.signal(signal.SIGTERM, lambda sig, frame: sys.exit(sig)) diff --git a/psutil/tests/runner.py b/psutil/tests/runner.py index 8b86a3e9..0777f5e7 100755 --- a/psutil/tests/runner.py +++ b/psutil/tests/runner.py @@ -48,10 +48,9 @@ from psutil.tests import import_module_by_path from psutil.tests import print_sysinfo from psutil.tests import reap_children from psutil.tests import safe_rmpath -from psutil.tests import TOX -VERBOSITY = 1 if TOX else 2 +VERBOSITY = 2 FAILED_TESTS_FNAME = '.failed-tests.txt' NWORKERS = psutil.cpu_count() or 1 USE_COLORS = not CI_TESTING and term_supports_colors() diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py index 8a9a6eb4..d1425bcb 100755 --- a/psutil/tests/test_connections.py +++ b/psutil/tests/test_connections.py @@ -38,6 +38,8 @@ from psutil.tests import enum from psutil.tests import get_free_port from psutil.tests import HAS_CONNECTIONS_UNIX from psutil.tests import PsutilTestCase +from psutil.tests import reap_children +from psutil.tests import retry_on_failure from psutil.tests import serialrun from psutil.tests import skip_on_access_denied from psutil.tests import SKIP_SYSCONS @@ -392,6 +394,8 @@ class TestFilters(_ConnTestCase): @skip_on_access_denied(only_if=MACOS) def test_combos(self): + reap_children() + def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): all_kinds = ("all", "inet", "inet4", "inet6", "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6") @@ -569,6 +573,7 @@ class TestSystemWideConnections(_ConnTestCase): # See: https://travis-ci.org/giampaolo/psutil/jobs/237566297 @unittest.skipIf(MACOS and TRAVIS, "unreliable on MACOS + TRAVIS") + @retry_on_failure() def test_multi_sockets_procs(self): # Creates multiple sub processes, each creating different # sockets. For each process check that proc.connections() diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index 35ab61e0..2d9e5917 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -34,6 +34,7 @@ from psutil._compat import long from psutil._compat import range from psutil.tests import create_sockets from psutil.tests import enum +from psutil.tests import GITHUB_WHEELS from psutil.tests import HAS_CPU_FREQ from psutil.tests import HAS_NET_IO_COUNTERS from psutil.tests import HAS_SENSORS_FANS @@ -85,6 +86,7 @@ class TestAvailConstantsAPIs(PsutilTestCase): ae(hasattr(psutil, "IOPRIO_LOW"), WINDOWS) ae(hasattr(psutil, "IOPRIO_VERYLOW"), WINDOWS) + @unittest.skipIf(GITHUB_WHEELS, "not exposed via GITHUB_WHEELS") def test_linux_rlimit(self): ae = self.assertEqual ae(hasattr(psutil, "RLIM_INFINITY"), LINUX) @@ -135,7 +137,8 @@ class TestAvailProcessAPIs(PsutilTestCase): def test_environ(self): self.assertEqual(hasattr(psutil.Process, "environ"), - LINUX or MACOS or WINDOWS or AIX or SUNOS) + LINUX or MACOS or WINDOWS or AIX or SUNOS or + FREEBSD or OPENBSD or NETBSD) def test_uids(self): self.assertEqual(hasattr(psutil.Process, "uids"), POSIX) @@ -149,6 +152,7 @@ class TestAvailProcessAPIs(PsutilTestCase): def test_ionice(self): self.assertEqual(hasattr(psutil.Process, "ionice"), LINUX or WINDOWS) + @unittest.skipIf(GITHUB_WHEELS, "not exposed via GITHUB_WHEELS") def test_rlimit(self): # requires Linux 2.6.36 self.assertEqual(hasattr(psutil.Process, "rlimit"), LINUX) diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index 9dd12890..163be0f9 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -53,6 +53,8 @@ HERE = os.path.abspath(os.path.dirname(__file__)) SIOCGIFADDR = 0x8915 SIOCGIFCONF = 0x8912 SIOCGIFHWADDR = 0x8927 +SIOCGIFNETMASK = 0x891b +SIOCGIFBRDADDR = 0x8919 if LINUX: SECTOR_SIZE = 512 EMPTY_TEMPERATURES = not glob.glob('/sys/class/hwmon/hwmon*') @@ -75,6 +77,49 @@ def get_ipv4_address(ifname): struct.pack('256s', ifname))[20:24]) +def get_ipv4_netmask(ifname): + import fcntl + ifname = ifname[:15] + if PY3: + ifname = bytes(ifname, 'ascii') + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + with contextlib.closing(s): + return socket.inet_ntoa( + fcntl.ioctl(s.fileno(), + SIOCGIFNETMASK, + struct.pack('256s', ifname))[20:24]) + + +def get_ipv4_broadcast(ifname): + import fcntl + ifname = ifname[:15] + if PY3: + ifname = bytes(ifname, 'ascii') + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + with contextlib.closing(s): + return socket.inet_ntoa( + fcntl.ioctl(s.fileno(), + SIOCGIFBRDADDR, + struct.pack('256s', ifname))[20:24]) + + +def get_ipv6_address(ifname): + with open("/proc/net/if_inet6", 'rt') as f: + for line in f.readlines(): + fields = line.split() + if fields[-1] == ifname: + break + else: + raise ValueError("could not find interface %r" % ifname) + unformatted = fields[0] + groups = [] + for i in range(0, len(unformatted), 4): + groups.append(unformatted[i:i + 4]) + formatted = ":".join(groups) + packed = socket.inet_pton(socket.AF_INET6, formatted) + return socket.inet_ntop(socket.AF_INET6, packed) + + def get_mac_address(ifname): import fcntl ifname = ifname[:15] @@ -890,7 +935,21 @@ class TestSystemNetIfAddrs(PsutilTestCase): self.assertEqual(addr.address, get_mac_address(name)) elif addr.family == socket.AF_INET: self.assertEqual(addr.address, get_ipv4_address(name)) - # TODO: test for AF_INET6 family + self.assertEqual(addr.netmask, get_ipv4_netmask(name)) + if addr.broadcast is not None: + self.assertEqual(addr.broadcast, + get_ipv4_broadcast(name)) + else: + self.assertEqual(get_ipv4_broadcast(name), '0.0.0.0') + elif addr.family == socket.AF_INET6: + # IPv6 addresses can have a percent symbol at the end. + # E.g. these 2 are equivalent: + # "fe80::1ff:fe23:4567:890a" + # "fe80::1ff:fe23:4567:890a%eth0" + # That is the "zone id" portion, which usually is the name + # of the network interface. + address = addr.address.split('%')[0] + self.assertEqual(address, get_ipv6_address(name)) # XXX - not reliable when having virtual NICs installed by Docker. # @unittest.skipIf(not which('ip'), "'ip' utility not available") @@ -919,11 +978,15 @@ class TestSystemNetIfStats(PsutilTestCase): except RuntimeError: pass else: - # Not always reliable. - # self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) + self.assertEqual(stats.isup, 'RUNNING' in out, msg=out) self.assertEqual(stats.mtu, int(re.findall(r'(?i)MTU[: ](\d+)', out)[0])) + def test_mtu(self): + for name, stats in psutil.net_if_stats().items(): + with open("/sys/class/net/%s/mtu" % name, "rt") as f: + self.assertEqual(stats.mtu, int(f.read().strip())) + @unittest.skipIf(not LINUX, "LINUX only") class TestSystemNetIOCounters(PsutilTestCase): @@ -1411,17 +1474,6 @@ class TestSensorsBattery(PsutilTestCase): psutil_value = psutil.sensors_battery().percent self.assertAlmostEqual(acpi_value, psutil_value, delta=1) - @unittest.skipIf(not which("acpi"), "acpi utility not available") - def test_power_plugged(self): - out = sh("acpi -b") - if 'unknown' in out.lower(): - return unittest.skip("acpi output not reliable") - if 'discharging at zero rate' in out: - plugged = True - else: - plugged = "Charging" in out.split('\n')[0] - self.assertEqual(psutil.sensors_battery().power_plugged, plugged) - def test_emulate_power_plugged(self): # Pretend the AC power cable is connected. def open_mock(name, *args, **kwargs): diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index 10e45d23..8fcee12a 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -763,6 +763,8 @@ class TestScripts(PsutilTestCase): def test_battery(self): self.assert_stdout('battery.py') + @unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported") + @unittest.skipIf(not HAS_BATTERY, "no battery") def test_sensors(self): self.assert_stdout('sensors.py') diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index a0b21c6e..b2328ba2 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -298,11 +298,9 @@ class TestProcess(PsutilTestCase): @unittest.skipIf(TRAVIS or CIRRUS, 'not reliable on TRAVIS/CIRRUS') def test_terminal(self): terminal = psutil.Process().terminal() - if sys.stdout.isatty(): + if terminal is not None: tty = os.path.realpath(sh('tty')) self.assertEqual(terminal, tty) - else: - self.assertIsNone(terminal) @unittest.skipIf(not HAS_PROC_IO_COUNTERS, 'not supported') @skip_on_not_implemented(only_if=LINUX) @@ -937,7 +935,7 @@ class TestProcess(PsutilTestCase): for combo in combos: p.cpu_affinity(combo) - self.assertEqual(p.cpu_affinity(), combo) + self.assertEqual(sorted(p.cpu_affinity()), sorted(combo)) # TODO: #595 @unittest.skipIf(BSD, "broken on BSD") diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py index 945bb2ed..580e1e5e 100755 --- a/psutil/tests/test_windows.py +++ b/psutil/tests/test_windows.py @@ -511,6 +511,7 @@ class TestProcessWMI(WindowsTestCase): p = psutil.Process(self.pid) self.assertEqual(p.name(), w.Caption) + # This fail on github because using virtualenv for test environment @unittest.skipIf(GITHUB_WHEELS, "unreliable path on GITHUB_WHEELS") def test_exe(self): w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] diff --git a/scripts/battery.py b/scripts/battery.py index 0da2b958..edf4ce8c 100755 --- a/scripts/battery.py +++ b/scripts/battery.py @@ -7,7 +7,7 @@ """ Show battery information. -$ python scripts/battery.py +$ python3 scripts/battery.py charge: 74% left: 2:11:31 status: discharging diff --git a/scripts/cpu_distribution.py b/scripts/cpu_distribution.py index 08997797..fb39d888 100755 --- a/scripts/cpu_distribution.py +++ b/scripts/cpu_distribution.py @@ -7,7 +7,7 @@ """ Shows CPU workload split across different CPUs. -$ python scripts/cpu_workload.py +$ python3 scripts/cpu_workload.py CPU 0 CPU 1 CPU 2 CPU 3 CPU 4 CPU 5 CPU 6 CPU 7 19.8 20.6 18.2 15.8 6.9 17.3 5.0 20.4 gvfsd pytho kwork chrom unity kwork kwork kwork diff --git a/scripts/disk_usage.py b/scripts/disk_usage.py index 901dbf8c..851ae9b1 100755 --- a/scripts/disk_usage.py +++ b/scripts/disk_usage.py @@ -7,7 +7,7 @@ """ List all mounted disk partitions a-la "df -h" command. -$ python scripts/disk_usage.py +$ python3 scripts/disk_usage.py Device Total Used Free Use % Type Mount /dev/sdb3 18.9G 14.7G 3.3G 77% ext4 / /dev/sda6 345.9G 83.8G 244.5G 24% ext4 /home diff --git a/scripts/free.py b/scripts/free.py index 000323c5..8c3359d8 100755 --- a/scripts/free.py +++ b/scripts/free.py @@ -7,7 +7,7 @@ """ A clone of 'free' cmdline utility. -$ python scripts/free.py +$ python3 scripts/free.py total used free shared buffers cache Mem: 10125520 8625996 1499524 0 349500 3307836 Swap: 0 0 0 diff --git a/scripts/ifconfig.py b/scripts/ifconfig.py index cfd02f0d..ae137fb4 100755 --- a/scripts/ifconfig.py +++ b/scripts/ifconfig.py @@ -7,7 +7,7 @@ """ A clone of 'ifconfig' on UNIX. -$ python scripts/ifconfig.py +$ python3 scripts/ifconfig.py lo: stats : speed=0MB, duplex=?, mtu=65536, up=yes incoming : bytes=1.95M, pkts=22158, errs=0, drops=0 diff --git a/scripts/internal/download_wheels.py b/scripts/internal/download_wheels.py deleted file mode 100644 index 1834f1b3..00000000 --- a/scripts/internal/download_wheels.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env python3 - -# 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. - -""" -Script which downloads wheel files hosted on GitHub: -https://github.com/giampaolo/psutil/actions -It needs an access token string generated from personal GitHub profile: -https://github.com/settings/tokens -The token must be created with at least "public_repo" scope/rights. -If you lose it, just generate a new token. -REST API doc: -https://developer.github.com/v3/actions/artifacts/ -""" - -import argparse -import collections -import json -import os -import requests -import shutil -import zipfile - -from psutil import __version__ as PSUTIL_VERSION -from psutil._common import bytes2human -from psutil._common import print_color - - -USER = "" -PROJECT = "" -TOKEN = "" -OUTFILE = "wheels.zip" - - -# --- GitHub API - - -def get_artifacts(): - base_url = "https://api.github.com/repos/%s/%s" % (USER, PROJECT) - url = base_url + "/actions/artifacts" - res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) - res.raise_for_status() - data = json.loads(res.content) - return data - - -def download_zip(url): - print("downloading: " + url) - res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) - res.raise_for_status() - totbytes = 0 - with open(OUTFILE, 'wb') as f: - for chunk in res.iter_content(chunk_size=16384): - f.write(chunk) - totbytes += len(chunk) - print("got %s, size %s)" % (OUTFILE, bytes2human(totbytes))) - - -# --- extract - - -def rename_27_wheels(): - # See: https://github.com/giampaolo/psutil/issues/810 - src = 'dist/psutil-%s-cp27-cp27m-win32.whl' % PSUTIL_VERSION - dst = 'dist/psutil-%s-cp27-none-win32.whl' % PSUTIL_VERSION - print("rename: %s\n %s" % (src, dst)) - os.rename(src, dst) - src = 'dist/psutil-%s-cp27-cp27m-win_amd64.whl' % PSUTIL_VERSION - dst = 'dist/psutil-%s-cp27-none-win_amd64.whl' % PSUTIL_VERSION - print("rename: %s\n %s" % (src, dst)) - os.rename(src, dst) - - -def extract(): - with zipfile.ZipFile(OUTFILE, 'r') as zf: - zf.extractall('dist') - - -def print_wheels(): - def is64bit(name): - return name.endswith(('x86_64.whl', 'amd64.whl')) - - groups = collections.defaultdict(list) - for name in os.listdir('dist'): - plat = name.split('-')[-1] - pyimpl = name.split('-')[3] - ispypy = 'pypy' in pyimpl - if 'linux' in plat: - if ispypy: - groups['pypy_on_linux'].append(name) - else: - groups['linux'].append(name) - elif 'win' in plat: - if ispypy: - groups['pypy_on_windows'].append(name) - else: - groups['windows'].append(name) - elif 'macosx' in plat: - if ispypy: - groups['pypy_on_macos'].append(name) - else: - groups['macos'].append(name) - else: - assert 0, name - - totsize = 0 - templ = "%-54s %7s %7s %7s" - for platf, names in groups.items(): - ppn = "%s (total = %s)" % (platf.replace('_', ' '), len(names)) - s = templ % (ppn, "size", "arch", "pyver") - print_color('\n' + s, color=None, bold=True) - for name in sorted(names): - path = os.path.join('dist', name) - size = os.path.getsize(path) - totsize += size - arch = '64' if is64bit(name) else '32' - pyver = 'pypy' if name.split('-')[3].startswith('pypy') else 'py' - pyver += name.split('-')[2][2:] - s = templ % (name, bytes2human(size), arch, pyver) - if 'pypy' in pyver: - print_color(s, color='violet') - else: - print_color(s, color='brown') - - -def run(): - if os.path.isdir('dist'): - shutil.rmtree('dist') - data = get_artifacts() - download_zip(data['artifacts'][0]['archive_download_url']) - os.mkdir('dist') - extract() - # rename_27_wheels() - print_wheels() - - -def main(): - global USER, PROJECT, TOKEN - parser = argparse.ArgumentParser(description='GitHub wheels downloader') - parser.add_argument('--user', required=True) - parser.add_argument('--project', required=True) - parser.add_argument('--tokenfile', required=True) - args = parser.parse_args() - USER = args.user - PROJECT = args.project - with open(os.path.expanduser(args.tokenfile)) as f: - TOKEN = f.read().strip() - run() - - -if __name__ == '__main__': - main() diff --git a/scripts/internal/win_download_wheels.py b/scripts/internal/download_wheels_appveyor.py index 8dae0573..b7c0aeae 100755 --- a/scripts/internal/win_download_wheels.py +++ b/scripts/internal/download_wheels_appveyor.py @@ -15,10 +15,8 @@ http://code.saghul.net/index.php/2015/09/09/ from __future__ import print_function import argparse import concurrent.futures -import errno import os import requests -import shutil import sys from psutil import __version__ as PSUTIL_VERSION @@ -29,33 +27,12 @@ from psutil._common import print_color BASE_URL = 'https://ci.appveyor.com/api' PY_VERSIONS = ['2.7', '3.5', '3.6', '3.7', '3.8'] TIMEOUT = 30 -COLORS = True - - -def safe_makedirs(path): - try: - os.makedirs(path) - except OSError as err: - if err.errno == errno.EEXIST: - if not os.path.isdir(path): - raise - else: - raise - - -def safe_rmtree(path): - def onerror(fun, path, excinfo): - exc = excinfo[1] - if exc.errno != errno.ENOENT: - raise - - shutil.rmtree(path, onerror=onerror) def download_file(url): local_fname = url.split('/')[-1] local_fname = os.path.join('dist', local_fname) - safe_makedirs('dist') + os.makedirs('dist', exist_ok=True) r = requests.get(url, stream=True, timeout=TIMEOUT) tot_bytes = 0 with open(local_fname, 'wb') as f: @@ -102,7 +79,6 @@ def rename_27_wheels(): def run(options): - safe_rmtree('dist') urls = get_file_urls(options) completed = 0 exc = None diff --git a/scripts/internal/download_wheels_github.py b/scripts/internal/download_wheels_github.py new file mode 100755 index 00000000..4aa50d5d --- /dev/null +++ b/scripts/internal/download_wheels_github.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +# 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. + +""" +Script which downloads wheel files hosted on GitHub: +https://github.com/giampaolo/psutil/actions +It needs an access token string generated from personal GitHub profile: +https://github.com/settings/tokens +The token must be created with at least "public_repo" scope/rights. +If you lose it, just generate a new token. +REST API doc: +https://developer.github.com/v3/actions/artifacts/ +""" + +import argparse +import json +import os +import requests +import zipfile + +from psutil._common import bytes2human +from psutil.tests import safe_rmpath + + +USER = "" +PROJECT = "" +TOKEN = "" +OUTFILE = "wheels-github.zip" + + +def get_artifacts(): + base_url = "https://api.github.com/repos/%s/%s" % (USER, PROJECT) + url = base_url + "/actions/artifacts" + res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) + res.raise_for_status() + data = json.loads(res.content) + return data + + +def download_zip(url): + print("downloading: " + url) + res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) + res.raise_for_status() + totbytes = 0 + with open(OUTFILE, 'wb') as f: + for chunk in res.iter_content(chunk_size=16384): + f.write(chunk) + totbytes += len(chunk) + print("got %s, size %s)" % (OUTFILE, bytes2human(totbytes))) + + +def run(): + data = get_artifacts() + download_zip(data['artifacts'][0]['archive_download_url']) + os.makedirs('dist', exist_ok=True) + with zipfile.ZipFile(OUTFILE, 'r') as zf: + zf.extractall('dist') + + +def main(): + global USER, PROJECT, TOKEN + parser = argparse.ArgumentParser(description='GitHub wheels downloader') + parser.add_argument('--user', required=True) + parser.add_argument('--project', required=True) + parser.add_argument('--tokenfile', required=True) + args = parser.parse_args() + USER = args.user + PROJECT = args.project + with open(os.path.expanduser(args.tokenfile)) as f: + TOKEN = f.read().strip() + try: + run() + finally: + safe_rmpath(OUTFILE) + + +if __name__ == '__main__': + main() diff --git a/scripts/internal/print_announce.py b/scripts/internal/print_announce.py index 9569c367..180bf377 100755 --- a/scripts/internal/print_announce.py +++ b/scripts/internal/print_announce.py @@ -58,7 +58,7 @@ Links -- -Giampaolo - http://grodola.blogspot.com +Giampaolo - https://gmpy.dev/about """ diff --git a/scripts/internal/print_downloads.py b/scripts/internal/print_downloads.py new file mode 100755 index 00000000..81132db9 --- /dev/null +++ b/scripts/internal/print_downloads.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 + +# 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. + +""" +Print PYPI statistics in MarkDown format. +Useful sites: +* https://pepy.tech/project/psutil +* https://pypistats.org/packages/psutil +* https://hugovk.github.io/top-pypi-packages/ +""" + +from __future__ import print_function +import json +import os +import subprocess +import sys + +import pypinfo # NOQA + +from psutil._common import memoize + + +AUTH_FILE = os.path.expanduser("~/.pypinfo.json") +PKGNAME = 'psutil' +DAYS = 30 +LIMIT = 100 +GITHUB_SCRIPT_URL = "https://github.com/giampaolo/psutil/blob/master/" \ + "scripts/internal/pypistats.py" +bytes_billed = 0 + + +# --- get + +@memoize +def sh(cmd): + assert os.path.exists(AUTH_FILE) + env = os.environ.copy() + env['GOOGLE_APPLICATION_CREDENTIALS'] = AUTH_FILE + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, universal_newlines=True) + stdout, stderr = p.communicate() + if p.returncode != 0: + raise RuntimeError(stderr) + assert not stderr, stderr + return stdout.strip() + + +@memoize +def query(cmd): + global bytes_billed + ret = json.loads(sh(cmd)) + bytes_billed += ret['query']['bytes_billed'] + return ret + + +def top_packages(): + return query( + f"pypinfo --all --json --days {DAYS} --limit {LIMIT} '' project") + + +def ranking(): + data = top_packages() + for i, line in enumerate(data['rows'], 1): + if line['project'] == PKGNAME: + return i + raise ValueError(f"can't find {PKGNAME}") + + +def downloads(): + data = top_packages() + for line in data['rows']: + if line['project'] == PKGNAME: + return line['download_count'] + raise ValueError(f"can't find {PKGNAME}") + + +def downloads_pyver(): + return query(f"pypinfo --json --days {DAYS} {PKGNAME} pyversion") + + +def downloads_by_country(): + return query(f"pypinfo --json --days {DAYS} {PKGNAME} country") + + +def downloads_by_system(): + return query(f"pypinfo --json --days {DAYS} {PKGNAME} system") + + +def downloads_by_distro(): + return query(f"pypinfo --json --days {DAYS} {PKGNAME} distro") + + +# --- print + + +templ = "| %-30s | %15s |" + + +def print_row(left, right): + if isinstance(right, int): + right = '{0:,}'.format(right) + print(templ % (left, right)) + + +def print_header(left, right="Downloads"): + print_row(left, right) + s = templ % ("-" * 30, "-" * 15) + print("|:" + s[2:-2] + ":|") + + +def print_markdown_table(title, left, rows): + pleft = left.replace('_', ' ').capitalize() + print("### " + title) + print() + print_header(pleft) + for row in rows: + lval = row[left] + print_row(lval, row['download_count']) + print() + + +def main(): + last_update = top_packages()['last_update'] + print("# Download stats") + print("") + s = f"psutil download statistics of the last {DAYS} days (last update " + s += f"*{last_update}*).\n" + s += f"Generated via [pypistats.py]({GITHUB_SCRIPT_URL}) script.\n" + print(s) + + data = [ + {'what': 'Per month', 'download_count': downloads()}, + {'what': 'Per day', 'download_count': int(downloads() / 30)}, + {'what': 'PYPI ranking', 'download_count': ranking()} + ] + print_markdown_table('Overview', 'what', data) + print_markdown_table('Operating systems', 'system_name', + downloads_by_system()['rows']) + print_markdown_table('Distros', 'distro_name', + downloads_by_distro()['rows']) + print_markdown_table('Python versions', 'python_version', + downloads_pyver()['rows']) + print_markdown_table('Countries', 'country', + downloads_by_country()['rows']) + + +if __name__ == '__main__': + try: + main() + finally: + print("bytes billed: %s" % bytes_billed, file=sys.stderr) diff --git a/scripts/internal/print_wheels.py b/scripts/internal/print_wheels.py new file mode 100755 index 00000000..be8290e0 --- /dev/null +++ b/scripts/internal/print_wheels.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +# 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. + +"""Nicely print wheels print in dist/ directory.""" + +import collections +import glob +import os + +from psutil._common import print_color +from psutil._common import bytes2human + + +def main(): + def is64bit(name): + return name.endswith(('x86_64.whl', 'amd64.whl')) + + groups = collections.defaultdict(list) + for path in glob.glob('dist/*.whl'): + name = os.path.basename(path) + plat = name.split('-')[-1] + pyimpl = name.split('-')[3] + ispypy = 'pypy' in pyimpl + if 'linux' in plat: + if ispypy: + groups['pypy_on_linux'].append(name) + else: + groups['linux'].append(name) + elif 'win' in plat: + if ispypy: + groups['pypy_on_windows'].append(name) + else: + groups['windows'].append(name) + elif 'macosx' in plat: + if ispypy: + groups['pypy_on_macos'].append(name) + else: + groups['macos'].append(name) + else: + assert 0, name + + totsize = 0 + templ = "%-54s %7s %7s %7s" + for platf, names in groups.items(): + ppn = "%s (total = %s)" % (platf.replace('_', ' '), len(names)) + s = templ % (ppn, "size", "arch", "pyver") + print_color('\n' + s, color=None, bold=True) + for name in sorted(names): + path = os.path.join('dist', name) + size = os.path.getsize(path) + totsize += size + arch = '64' if is64bit(name) else '32' + pyver = 'pypy' if name.split('-')[3].startswith('pypy') else 'py' + pyver += name.split('-')[2][2:] + s = templ % (name, bytes2human(size), arch, pyver) + if 'pypy' in pyver: + print_color(s, color='violet') + else: + print_color(s, color='brown') + + +if __name__ == '__main__': + main() diff --git a/scripts/iotop.py b/scripts/iotop.py index c3afd071..04683673 100755 --- a/scripts/iotop.py +++ b/scripts/iotop.py @@ -14,7 +14,7 @@ It doesn't work on Windows as curses module is required. Example output: -$ python scripts/iotop.py +$ python3 scripts/iotop.py Total DISK READ: 0.00 B/s | Total DISK WRITE: 472.00 K/s PID USER DISK READ DISK WRITE COMMAND 13155 giampao 0.00 B/s 428.00 K/s /usr/bin/google-chrome-beta @@ -30,7 +30,6 @@ PID USER DISK READ DISK WRITE COMMAND Author: Giampaolo Rodola' <g.rodola@gmail.com> """ -import atexit import time import sys try: @@ -42,20 +41,11 @@ import psutil from psutil._common import bytes2human -def tear_down(): - win.keypad(0) - curses.nocbreak() - curses.echo() - curses.endwin() - - win = curses.initscr() -atexit.register(tear_down) -curses.endwin() lineno = 0 -def print_line(line, highlight=False): +def printl(line, highlight=False): """A thin wrapper around curses's addstr().""" global lineno try: @@ -129,10 +119,10 @@ def refresh_window(procs, disks_read, disks_write): disks_tot = "Total DISK READ: %s | Total DISK WRITE: %s" \ % (bytes2human(disks_read), bytes2human(disks_write)) - print_line(disks_tot) + printl(disks_tot) header = templ % ("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND") - print_line(header, highlight=True) + printl(header, highlight=True) for p in procs: line = templ % ( @@ -142,21 +132,45 @@ def refresh_window(procs, disks_read, disks_write): bytes2human(p._write_per_sec), p._cmdline) try: - print_line(line) + printl(line) except curses.error: break win.refresh() +def setup(): + curses.start_color() + curses.use_default_colors() + for i in range(0, curses.COLORS): + curses.init_pair(i + 1, i, -1) + curses.endwin() + win.nodelay(1) + + +def tear_down(): + win.keypad(0) + curses.nocbreak() + curses.echo() + curses.endwin() + + def main(): + global lineno + setup() try: interval = 0 while True: + if win.getch() == ord('q'): + break args = poll(interval) refresh_window(*args) - interval = 1 + lineno = 0 + interval = 0.5 + time.sleep(interval) except (KeyboardInterrupt, SystemExit): pass + finally: + tear_down() if __name__ == '__main__': diff --git a/scripts/meminfo.py b/scripts/meminfo.py index 550fcd01..b98aa60b 100755 --- a/scripts/meminfo.py +++ b/scripts/meminfo.py @@ -7,7 +7,7 @@ """ Print system memory information. -$ python scripts/meminfo.py +$ python3 scripts/meminfo.py MEMORY ------ Total : 9.7G diff --git a/scripts/netstat.py b/scripts/netstat.py index fe3bfe40..5a21358e 100755 --- a/scripts/netstat.py +++ b/scripts/netstat.py @@ -7,7 +7,7 @@ """ A clone of 'netstat -antp' on Linux. -$ python scripts/netstat.py +$ python3 scripts/netstat.py Proto Local address Remote address Status PID Program name tcp 127.0.0.1:48256 127.0.0.1:45884 ESTABLISHED 13646 chrome tcp 127.0.0.1:47073 127.0.0.1:45884 ESTABLISHED 13646 chrome diff --git a/scripts/nettop.py b/scripts/nettop.py index ce647c9d..8cc19fda 100755 --- a/scripts/nettop.py +++ b/scripts/nettop.py @@ -11,7 +11,7 @@ Shows real-time network statistics. Author: Giampaolo Rodola' <g.rodola@gmail.com> -$ python scripts/nettop.py +$ python3 scripts/nettop.py ----------------------------------------------------------- total bytes: sent: 1.49 G received: 4.82 G total packets: sent: 7338724 received: 8082712 @@ -31,7 +31,6 @@ pkts-sent 0 0 pkts-recv 1214470 0 """ -import atexit import time import sys try: @@ -43,20 +42,11 @@ import psutil from psutil._common import bytes2human -def tear_down(): - win.keypad(0) - curses.nocbreak() - curses.echo() - curses.endwin() - - -win = curses.initscr() -atexit.register(tear_down) -curses.endwin() lineno = 0 +win = curses.initscr() -def print_line(line, highlight=False): +def printl(line, highlight=False): """A thin wrapper around curses's addstr().""" global lineno try: @@ -89,59 +79,80 @@ def refresh_window(tot_before, tot_after, pnic_before, pnic_after): global lineno # totals - print_line("total bytes: sent: %-10s received: %s" % ( + printl("total bytes: sent: %-10s received: %s" % ( bytes2human(tot_after.bytes_sent), bytes2human(tot_after.bytes_recv)) ) - print_line("total packets: sent: %-10s received: %s" % ( + printl("total packets: sent: %-10s received: %s" % ( tot_after.packets_sent, tot_after.packets_recv)) # per-network interface details: let's sort network interfaces so # that the ones which generated more traffic are shown first - print_line("") + printl("") nic_names = list(pnic_after.keys()) nic_names.sort(key=lambda x: sum(pnic_after[x]), reverse=True) for name in nic_names: stats_before = pnic_before[name] stats_after = pnic_after[name] templ = "%-15s %15s %15s" - print_line(templ % (name, "TOTAL", "PER-SEC"), highlight=True) - print_line(templ % ( + printl(templ % (name, "TOTAL", "PER-SEC"), highlight=True) + printl(templ % ( "bytes-sent", bytes2human(stats_after.bytes_sent), bytes2human( stats_after.bytes_sent - stats_before.bytes_sent) + '/s', )) - print_line(templ % ( + printl(templ % ( "bytes-recv", bytes2human(stats_after.bytes_recv), bytes2human( stats_after.bytes_recv - stats_before.bytes_recv) + '/s', )) - print_line(templ % ( + printl(templ % ( "pkts-sent", stats_after.packets_sent, stats_after.packets_sent - stats_before.packets_sent, )) - print_line(templ % ( + printl(templ % ( "pkts-recv", stats_after.packets_recv, stats_after.packets_recv - stats_before.packets_recv, )) - print_line("") + printl("") win.refresh() lineno = 0 +def setup(): + curses.start_color() + curses.use_default_colors() + for i in range(0, curses.COLORS): + curses.init_pair(i + 1, i, -1) + curses.endwin() + win.nodelay(1) + + +def tear_down(): + win.keypad(0) + curses.nocbreak() + curses.echo() + curses.endwin() + + def main(): + setup() try: interval = 0 while True: + if win.getch() == ord('q'): + break args = poll(interval) refresh_window(*args) - interval = 1 + interval = 0.5 except (KeyboardInterrupt, SystemExit): pass + finally: + tear_down() if __name__ == '__main__': diff --git a/scripts/pmap.py b/scripts/pmap.py index 5f7246a1..459927bf 100755 --- a/scripts/pmap.py +++ b/scripts/pmap.py @@ -8,7 +8,7 @@ A clone of 'pmap' utility on Linux, 'vmmap' on macOS and 'procstat -v' on BSD. Report memory map of a process. -$ python scripts/pmap.py 32402 +$ python3 scripts/pmap.py 32402 Address RSS Mode Mapping 0000000000400000 1200K r-xp /usr/bin/python2.7 0000000000838000 4K r--p /usr/bin/python2.7 diff --git a/scripts/procinfo.py b/scripts/procinfo.py index 01974513..f0605386 100755 --- a/scripts/procinfo.py +++ b/scripts/procinfo.py @@ -8,7 +8,7 @@ Print detailed information about a process. Author: Giampaolo Rodola' <g.rodola@gmail.com> -$ python scripts/procinfo.py +$ python3 scripts/procinfo.py pid 4600 name chrome parent 4554 (bash) diff --git a/scripts/ps.py b/scripts/ps.py index 540c032a..a234209f 100755 --- a/scripts/ps.py +++ b/scripts/ps.py @@ -7,7 +7,7 @@ """ A clone of 'ps aux'. -$ python scripts/ps.py +$ python3 scripts/ps.py USER PID %MEM VSZ RSS NICE STATUS START TIME CMDLINE root 1 0.0 220.9M 6.5M sleep Mar27 09:10 /lib/systemd root 2 0.0 0.0B 0.0B sleep Mar27 00:00 kthreadd diff --git a/scripts/pstree.py b/scripts/pstree.py index 0005e4a1..dba9f1bd 100755 --- a/scripts/pstree.py +++ b/scripts/pstree.py @@ -8,7 +8,7 @@ Similar to 'ps aux --forest' on Linux, prints the process list as a tree structure. -$ python scripts/pstree.py +$ python3 scripts/pstree.py 0 ? |- 1 init | |- 289 cgmanager diff --git a/scripts/sensors.py b/scripts/sensors.py index e532beba..911d7c9b 100755 --- a/scripts/sensors.py +++ b/scripts/sensors.py @@ -9,7 +9,7 @@ A clone of 'sensors' utility on Linux printing hardware temperatures, fans speed and battery info. -$ python scripts/sensors.py +$ python3 scripts/sensors.py asus Temperatures: asus 57.0°C (high=None°C, critical=None°C) diff --git a/scripts/temperatures.py b/scripts/temperatures.py index e83df440..f2dd51a7 100755 --- a/scripts/temperatures.py +++ b/scripts/temperatures.py @@ -8,7 +8,7 @@ """ A clone of 'sensors' utility on Linux printing hardware temperatures. -$ python scripts/sensors.py +$ python3 scripts/sensors.py asus asus 47.0 °C (high = None °C, critical = None °C) diff --git a/scripts/top.py b/scripts/top.py index 0b17471d..3c297d9a 100755 --- a/scripts/top.py +++ b/scripts/top.py @@ -9,7 +9,7 @@ A clone of top / htop. Author: Giampaolo Rodola' <g.rodola@gmail.com> -$ python scripts/top.py +$ python3 scripts/top.py CPU0 [|||| ] 10.9% CPU1 [||||| ] 13.1% CPU2 [||||| ] 12.8% @@ -33,7 +33,6 @@ PID USER NI VIRT RES CPU% MEM% TIME+ NAME ... """ -import atexit import datetime import sys import time @@ -46,30 +45,28 @@ import psutil from psutil._common import bytes2human -# --- curses stuff - -def tear_down(): - win.keypad(0) - curses.nocbreak() - curses.echo() - curses.endwin() - - win = curses.initscr() -atexit.register(tear_down) -curses.endwin() lineno = 0 +colors_map = dict( + green=3, + red=10, + yellow=4, +) -def print_line(line, highlight=False): +def printl(line, color=None, bold=False, highlight=False): """A thin wrapper around curses's addstr().""" global lineno try: + flags = 0 + if color: + flags |= curses.color_pair(colors_map[color]) + if bold: + flags |= curses.A_BOLD if highlight: line += " " * (win.getmaxyx()[1] - len(line)) - win.addstr(lineno, 0, line, curses.A_REVERSE) - else: - win.addstr(lineno, 0, line, 0) + flags |= curses.A_STANDOUT + win.addstr(lineno, 0, line, flags) except curses.error: lineno = 0 win.refresh() @@ -105,6 +102,15 @@ def poll(interval): return (processes, procs_status) +def get_color(perc): + if perc <= 30: + return "green" + elif perc <= 80: + return "yellow" + else: + return "red" + + def print_header(procs_status, num_procs): """Print system-related info, above the process list.""" @@ -117,8 +123,8 @@ def print_header(procs_status, num_procs): percs = psutil.cpu_percent(interval=0, percpu=True) for cpu_num, perc in enumerate(percs): dashes, empty_dashes = get_dashes(perc) - print_line(" CPU%-2s [%s%s] %5s%%" % (cpu_num, dashes, empty_dashes, - perc)) + line = " CPU%-2s [%s%s] %5s%%" % (cpu_num, dashes, empty_dashes, perc) + printl(line, color=get_color(perc)) mem = psutil.virtual_memory() dashes, empty_dashes = get_dashes(mem.percent) line = " Mem [%s%s] %5s%% %6s / %s" % ( @@ -127,7 +133,7 @@ def print_header(procs_status, num_procs): str(int(mem.used / 1024 / 1024)) + "M", str(int(mem.total / 1024 / 1024)) + "M" ) - print_line(line) + printl(line, color=get_color(mem.percent)) # swap usage swap = psutil.swap_memory() @@ -138,7 +144,7 @@ def print_header(procs_status, num_procs): str(int(swap.used / 1024 / 1024)) + "M", str(int(swap.total / 1024 / 1024)) + "M" ) - print_line(line) + printl(line, color=get_color(swap.percent)) # processes number and status st = [] @@ -146,14 +152,14 @@ def print_header(procs_status, num_procs): if y: st.append("%s=%s" % (x, y)) st.sort(key=lambda x: x[:3] in ('run', 'sle'), reverse=1) - print_line(" Processes: %s (%s)" % (num_procs, ', '.join(st))) + printl(" Processes: %s (%s)" % (num_procs, ', '.join(st))) # load average, uptime uptime = datetime.datetime.now() - \ datetime.datetime.fromtimestamp(psutil.boot_time()) av1, av2, av3 = psutil.getloadavg() line = " Load average: %.2f %.2f %.2f Uptime: %s" \ % (av1, av2, av3, str(uptime).split('.')[0]) - print_line(line) + printl(line) def refresh_window(procs, procs_status): @@ -164,8 +170,8 @@ def refresh_window(procs, procs_status): header = templ % ("PID", "USER", "NI", "VIRT", "RES", "CPU%", "MEM%", "TIME+", "NAME") print_header(procs_status, len(procs)) - print_line("") - print_line(header, highlight=True) + printl("") + printl(header, bold=True, highlight=True) for p in procs: # TIME+ column shows process CPU cumulative time and it # is expressed as: "mm:ss.ms" @@ -197,21 +203,42 @@ def refresh_window(procs, procs_status): p.dict['name'] or '', ) try: - print_line(line) + printl(line) except curses.error: break win.refresh() +def setup(): + curses.start_color() + curses.use_default_colors() + for i in range(0, curses.COLORS): + curses.init_pair(i + 1, i, -1) + curses.endwin() + win.nodelay(1) + + +def tear_down(): + win.keypad(0) + curses.nocbreak() + curses.echo() + curses.endwin() + + def main(): + setup() try: interval = 0 while True: + if win.getch() == ord('q'): + break args = poll(interval) refresh_window(*args) interval = 1 except (KeyboardInterrupt, SystemExit): pass + finally: + tear_down() if __name__ == '__main__': diff --git a/scripts/who.py b/scripts/who.py index c2299eb0..c1e40729 100755 --- a/scripts/who.py +++ b/scripts/who.py @@ -8,7 +8,7 @@ A clone of 'who' command; print information about users who are currently logged in. -$ python scripts/who.py +$ python3 scripts/who.py giampaolo console 2017-03-25 22:24 loginwindow giampaolo ttys000 2017-03-25 23:28 (10.0.2.2) sshd """ diff --git a/scripts/winservices.py b/scripts/winservices.py index 8792f752..5c710159 100755 --- a/scripts/winservices.py +++ b/scripts/winservices.py @@ -7,7 +7,7 @@ r""" List all Windows services installed. -$ python scripts/winservices.py +$ python3 scripts/winservices.py AeLookupSvc (Application Experience) status: stopped, start: manual, username: localSystem, pid: None binpath: C:\Windows\system32\svchost.exe -k netsvcs @@ -167,7 +167,7 @@ if WINDOWS: define_macros=macros, libraries=[ "psapi", "kernel32", "advapi32", "shell32", "netapi32", - "wtsapi32", "ws2_32", "PowrProf", "pdh", + "ws2_32", "PowrProf", "pdh", ], # extra_compile_args=["/W 4"], # extra_link_args=["/DEBUG"] |