summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2020-10-17 00:53:59 +0200
committerGiampaolo Rodola <g.rodola@gmail.com>2020-10-17 00:53:59 +0200
commita8cd5893a920adcd5b8922b80748461fbea8dc1c (patch)
treef74c5f7cfc8ae84319f201f21528c51c5ba553e7
parentbd4d2bf420e1dfa3298143daebd485b97335b256 (diff)
downloadpsutil-a8cd5893a920adcd5b8922b80748461fbea8dc1c.tar.gz
pypi download stats script
-rwxr-xr-x.ci/travis/install.sh12
-rw-r--r--.github/FUNDING.yml4
-rw-r--r--.github/lock.yml35
-rw-r--r--.github/workflows/build_wheel.yml50
-rw-r--r--CREDITS184
-rw-r--r--HISTORY.rst37
-rw-r--r--MANIFEST.in6
-rw-r--r--Makefile40
-rw-r--r--README.rst31
-rw-r--r--docs/index.rst35
-rw-r--r--psutil/__init__.py4
-rw-r--r--psutil/_psbsd.py27
-rw-r--r--psutil/_pslinux.py22
-rw-r--r--psutil/_psosx.py15
-rw-r--r--psutil/_psutil_bsd.c150
-rw-r--r--psutil/_psutil_common.c31
-rw-r--r--psutil/_psutil_common.h6
-rw-r--r--psutil/_psutil_linux.c2
-rw-r--r--psutil/_psutil_posix.c8
-rw-r--r--psutil/_psutil_windows.c66
-rw-r--r--psutil/arch/freebsd/specific.c4
-rw-r--r--psutil/arch/freebsd/sys_socks.c5
-rw-r--r--psutil/arch/openbsd/specific.c14
-rw-r--r--psutil/arch/windows/ntextapi.h126
-rw-r--r--psutil/arch/windows/wmi.c2
-rw-r--r--psutil/tests/__init__.py13
-rwxr-xr-xpsutil/tests/runner.py3
-rwxr-xr-xpsutil/tests/test_connections.py5
-rwxr-xr-xpsutil/tests/test_contracts.py6
-rwxr-xr-xpsutil/tests/test_linux.py80
-rwxr-xr-xpsutil/tests/test_misc.py2
-rwxr-xr-xpsutil/tests/test_process.py6
-rwxr-xr-xpsutil/tests/test_windows.py1
-rwxr-xr-xscripts/battery.py2
-rwxr-xr-xscripts/cpu_distribution.py2
-rwxr-xr-xscripts/disk_usage.py2
-rwxr-xr-xscripts/free.py2
-rwxr-xr-xscripts/ifconfig.py2
-rw-r--r--scripts/internal/download_wheels.py154
-rwxr-xr-xscripts/internal/download_wheels_appveyor.py (renamed from scripts/internal/win_download_wheels.py)26
-rwxr-xr-xscripts/internal/download_wheels_github.py81
-rwxr-xr-xscripts/internal/print_announce.py2
-rwxr-xr-xscripts/internal/print_downloads.py154
-rwxr-xr-xscripts/internal/print_wheels.py66
-rwxr-xr-xscripts/iotop.py46
-rwxr-xr-xscripts/meminfo.py2
-rwxr-xr-xscripts/netstat.py2
-rwxr-xr-xscripts/nettop.py57
-rwxr-xr-xscripts/pmap.py2
-rwxr-xr-xscripts/procinfo.py2
-rwxr-xr-xscripts/ps.py2
-rwxr-xr-xscripts/pstree.py2
-rwxr-xr-xscripts/sensors.py2
-rwxr-xr-xscripts/temperatures.py2
-rwxr-xr-xscripts/top.py79
-rwxr-xr-xscripts/who.py2
-rwxr-xr-xscripts/winservices.py2
-rwxr-xr-xsetup.py2
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
diff --git a/CREDITS b/CREDITS
index 690f5717..3c54d33a 100644
--- a/CREDITS
+++ b/CREDITS
@@ -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
diff --git a/Makefile b/Makefile
index e91dfd81..c603da8a 100644
--- a/Makefile
+++ b/Makefile
@@ -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
# ===================================================================
diff --git a/README.rst b/README.rst
index 2137a1a2..31119c31 100644
--- a/README.rst
+++ b/README.rst
@@ -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
diff --git a/setup.py b/setup.py
index 3c442302..893eb46b 100755
--- a/setup.py
+++ b/setup.py
@@ -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"]