summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2021-05-27 19:49:19 +0200
committerGiampaolo Rodola <g.rodola@gmail.com>2021-05-27 19:49:19 +0200
commit0682d554f85d9182e5e7f03a504fb768fa88d6d5 (patch)
tree25ca3632891926da65eaed813de56da5c93d19f4
parent805d781b919d18028b1418593d8b75c7576d2a75 (diff)
parent95db8bb96caf5540c45b9eff2229c0401b578c31 (diff)
downloadpsutil-0682d554f85d9182e5e7f03a504fb768fa88d6d5.tar.gz
merge from master
Signed-off-by: Giampaolo Rodola <g.rodola@gmail.com>
-rw-r--r--.ci/appveyor/run_with_compiler.cmd1
-rw-r--r--.ci/travis/README2
-rwxr-xr-x.ci/travis/install.sh36
-rwxr-xr-x.ci/travis/run.sh38
-rw-r--r--.cirrus.yml31
-rw-r--r--.flake87
-rw-r--r--.github/FUNDING.yml6
-rw-r--r--.github/ISSUE_TEMPLATE/bug.md20
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml5
-rw-r--r--.github/ISSUE_TEMPLATE/enhancement.md12
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md13
-rw-r--r--.github/no-response.yml10
-rw-r--r--.github/workflows/build.yml117
-rw-r--r--.github/workflows/issues.py332
-rw-r--r--.github/workflows/issues.yml28
-rw-r--r--.travis.yml30
-rw-r--r--CONTRIBUTING.md33
-rw-r--r--CREDITS216
-rw-r--r--HISTORY.rst113
-rw-r--r--INSTALL.rst147
-rw-r--r--MANIFEST.in16
-rw-r--r--Makefile53
-rw-r--r--README.rst206
-rw-r--r--appveyor.yml34
-rw-r--r--docs/DEVGUIDE.rst185
-rw-r--r--docs/DEVNOTES62
-rw-r--r--docs/_static/css/custom.css2
-rw-r--r--docs/_static/psutil-logo.pngbin0 -> 7228 bytes
-rw-r--r--docs/_static/tidelift-logo.pngbin0 -> 2271 bytes
-rw-r--r--docs/conf.py24
-rw-r--r--docs/index.rst277
-rw-r--r--psutil/__init__.py108
-rw-r--r--psutil/_common.py16
-rw-r--r--psutil/_compat.py17
-rw-r--r--psutil/_psaix.py8
-rw-r--r--psutil/_psbsd.py59
-rw-r--r--psutil/_pslinux.py190
-rw-r--r--psutil/_psosx.py27
-rw-r--r--psutil/_psposix.py4
-rw-r--r--psutil/_pssunos.py14
-rw-r--r--psutil/_psutil_aix.c6
-rw-r--r--psutil/_psutil_bsd.c156
-rw-r--r--psutil/_psutil_common.c63
-rw-r--r--psutil/_psutil_common.h19
-rw-r--r--psutil/_psutil_linux.c123
-rw-r--r--psutil/_psutil_osx.c198
-rw-r--r--psutil/_psutil_posix.c171
-rw-r--r--psutil/_psutil_posix.h1
-rw-r--r--psutil/_psutil_sunos.c12
-rw-r--r--psutil/_psutil_windows.c146
-rw-r--r--psutil/_pswindows.py28
-rw-r--r--psutil/arch/freebsd/cpu.c130
-rw-r--r--psutil/arch/freebsd/cpu.h11
-rw-r--r--psutil/arch/freebsd/specific.c188
-rw-r--r--psutil/arch/freebsd/specific.h12
-rw-r--r--psutil/arch/freebsd/sys_socks.c5
-rw-r--r--psutil/arch/netbsd/specific.c10
-rw-r--r--psutil/arch/openbsd/specific.c16
-rw-r--r--psutil/arch/osx/cpu.c140
-rw-r--r--psutil/arch/osx/cpu.h13
-rw-r--r--psutil/arch/osx/process_info.c115
-rw-r--r--psutil/arch/osx/process_info.h1
-rw-r--r--psutil/arch/windows/cpu.c7
-rw-r--r--psutil/arch/windows/cpu.h2
-rw-r--r--psutil/arch/windows/disk.c32
-rw-r--r--psutil/arch/windows/disk.h2
-rw-r--r--psutil/arch/windows/net.c64
-rw-r--r--psutil/arch/windows/ntextapi.h130
-rw-r--r--psutil/arch/windows/process_handles.c2
-rw-r--r--psutil/arch/windows/process_info.c115
-rw-r--r--psutil/arch/windows/process_utils.c28
-rw-r--r--psutil/arch/windows/process_utils.h1
-rw-r--r--psutil/arch/windows/wmi.c30
-rw-r--r--psutil/tests/README.rst9
-rw-r--r--psutil/tests/__init__.py474
-rwxr-xr-xpsutil/tests/runner.py19
-rwxr-xr-xpsutil/tests/test_aix.py10
-rwxr-xr-xpsutil/tests/test_bsd.py54
-rwxr-xr-xpsutil/tests/test_connections.py159
-rwxr-xr-xpsutil/tests/test_contracts.py132
-rwxr-xr-xpsutil/tests/test_linux.py206
-rwxr-xr-xpsutil/tests/test_memleaks.py (renamed from psutil/tests/test_memory_leaks.py)150
-rwxr-xr-xpsutil/tests/test_misc.py21
-rwxr-xr-xpsutil/tests/test_osx.py95
-rwxr-xr-xpsutil/tests/test_posix.py19
-rwxr-xr-xpsutil/tests/test_process.py204
-rwxr-xr-x[-rw-r--r--]psutil/tests/test_system.py71
-rwxr-xr-x[-rw-r--r--]psutil/tests/test_testutils.py80
-rwxr-xr-x[-rw-r--r--]psutil/tests/test_unicode.py89
-rwxr-xr-xpsutil/tests/test_windows.py82
-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
-rwxr-xr-xscripts/internal/convert_readme.py50
-rwxr-xr-xscripts/internal/download_wheels_appveyor.py (renamed from scripts/internal/win_download_wheels.py)54
-rwxr-xr-xscripts/internal/download_wheels_github.py102
-rwxr-xr-xscripts/internal/generate_manifest.py2
-rwxr-xr-xscripts/internal/print_announce.py18
-rwxr-xr-xscripts/internal/print_downloads.py161
-rwxr-xr-xscripts/internal/print_hashes.py35
-rwxr-xr-xscripts/internal/print_wheels.py97
-rwxr-xr-x[-rw-r--r--]scripts/internal/tidelift.py0
-rwxr-xr-xscripts/internal/winmake.py73
-rwxr-xr-xscripts/iotop.py46
-rwxr-xr-xscripts/meminfo.py2
-rwxr-xr-xscripts/netstat.py5
-rwxr-xr-xscripts/nettop.py57
-rwxr-xr-xscripts/pmap.py2
-rwxr-xr-xscripts/procinfo.py8
-rwxr-xr-xscripts/procsmem.py7
-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.py42
-rw-r--r--tox.ini28
121 files changed, 4519 insertions, 2721 deletions
diff --git a/.ci/appveyor/run_with_compiler.cmd b/.ci/appveyor/run_with_compiler.cmd
index 5da547c4..7965f865 100644
--- a/.ci/appveyor/run_with_compiler.cmd
+++ b/.ci/appveyor/run_with_compiler.cmd
@@ -29,6 +29,7 @@
:: The CALL lines at the end of this file look redundant, but if you move them
:: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y
:: case, I don't know why.
+
@ECHO OFF
SET COMMAND_TO_RUN=%*
diff --git a/.ci/travis/README b/.ci/travis/README
deleted file mode 100644
index d9d5f65a..00000000
--- a/.ci/travis/README
+++ /dev/null
@@ -1,2 +0,0 @@
-This directory contains support files for Travis, a continuous integration
-service which runs tests on Linux and Windows on every push.
diff --git a/.ci/travis/install.sh b/.ci/travis/install.sh
deleted file mode 100755
index f06e43d5..00000000
--- a/.ci/travis/install.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-uname -a
-python -c "import sys; print(sys.version)"
-
-if [[ "$(uname -s)" == 'Darwin' ]]; then
- brew update || brew update
- brew outdated pyenv || brew upgrade pyenv
- brew install pyenv-virtualenv
-
- if which pyenv > /dev/null; then
- eval "$(pyenv init -)"
- fi
-
- case "${PYVER}" in
- py27)
- pyenv install 2.7.16
- pyenv virtualenv 2.7.16 psutil
- ;;
- py36)
- pyenv install 3.6.6
- pyenv virtualenv 3.6.6 psutil
- ;;
- esac
- pyenv rehash
- pyenv activate psutil
-fi
-
-if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]] || [[ $PYVER == 'py27' ]]; then
- pip install -U ipaddress mock
-fi
-
-pip install -U coverage coveralls flake8 setuptools concurrencytest
diff --git a/.ci/travis/run.sh b/.ci/travis/run.sh
deleted file mode 100755
index 879e78a6..00000000
--- a/.ci/travis/run.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-PYVER=`python -c 'import sys; print(".".join(map(str, sys.version_info[:2])))'`
-
-# setup macOS
-if [[ "$(uname -s)" == 'Darwin' ]]; then
- if which pyenv > /dev/null; then
- eval "$(pyenv init -)"
- fi
- pyenv activate psutil
-fi
-
-# install psutil
-make clean
-python setup.py build
-python setup.py develop
-
-# run tests (with coverage)
-if [[ $PYVER == '2.7' ]] && [[ "$(uname -s)" != 'Darwin' ]]; then
- PSUTIL_TESTING=1 python -Wa -m coverage run psutil/tests/runner.py
-else
- PSUTIL_TESTING=1 python -Wa psutil/tests/runner.py
-fi
-
-if [ "$PYVER" == "2.7" ] || [ "$PYVER" == "3.6" ]; then
- # run mem leaks test
- PSUTIL_TESTING=1 python -Wa psutil/tests/test_memory_leaks.py
- # run linter (on Linux only)
- if [[ "$(uname -s)" != 'Darwin' ]]; then
- make lint PYTHON=python
- fi
-fi
-
-PSUTIL_TESTING=1 python -Wa scripts/internal/print_access_denied.py
-PSUTIL_TESTING=1 python -Wa scripts/internal/print_api_speed.py
diff --git a/.cirrus.yml b/.cirrus.yml
deleted file mode 100644
index a0b8f1f0..00000000
--- a/.cirrus.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-freebsd_13_py3_task:
- freebsd_instance:
- image: freebsd-12-1-release-amd64
- env:
- CIRRUS: 1
- install_script:
- - pkg install -y python3 gcc py37-pip
- script:
- - python3 -m pip install --user setuptools
- - make clean
- - make install
- - make test
- - make test-memleaks
- - make print-access-denied
- - make print-api-speed
-
-freebsd_11_py2_task:
- freebsd_instance:
- image: freebsd-12-1-release-amd64
- env:
- CIRRUS: 1
- install_script:
- - pkg install -y python gcc py27-pip
- script:
- - python2.7 -m pip install --user setuptools ipaddress mock
- - make clean
- - make install
- - make test
- - make test-memleaks
- - make print-access-denied
- - make print-api-speed
diff --git a/.flake8 b/.flake8
index 6581552a..1244cd48 100644
--- a/.flake8
+++ b/.flake8
@@ -3,8 +3,13 @@
# T001 = print() statement
[flake8]
+ignore =
+ # line break after binary operator
+ W504
per-file-ignores =
setup.py:T001
scripts/*:T001
+ scripts/internal/convert_readme.py:E501,T001
psutil/tests/runner.py:T001
- psutil/tests/test_memory_leaks.py:T001
+ psutil/tests/test_memleaks.py:T001
+ .github/workflows/*:T001
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index c39b2b61..03c7c77c 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,9 +1,9 @@
# These are supported funding model platforms
tidelift: "pypi/psutil"
-github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+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/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
index 67a9601b..24d01efa 100644
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -5,15 +5,17 @@ title: "[OS] title"
labels: 'bug'
---
-**Platform**
-* { OS version }
-* { psutil version: python3 -c "import psutil; print(psutil.__version__)" }
-* { python version }
+## Summary
+* OS: { type-or-version }
+* Architecture: { 64bit, 32bit, ARM, PowerPC, s390 }
+* Psutil version: { pip3 show psutil }
+* Python version: { python3 -V }
+* Type: { core, doc, performance, scripts, tests, wheels, new-api, installation }
-**Bug description**
-...
+## Description
-
-**Test results**
-{ output of `python -c psutil.tests` (failures only, not full result) }
+{{{
+ A clear explanation of the bug, including traceback message (if any). Please read the contributing guidelines before submit:
+ https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md
+}}}
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..39dc113f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Ask a question
+ url: https://groups.google.com/g/psutil
+ about: Use this to ask for support
diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md
index 7e7159f2..2f7d75a5 100644
--- a/.github/ISSUE_TEMPLATE/enhancement.md
+++ b/.github/ISSUE_TEMPLATE/enhancement.md
@@ -6,4 +6,14 @@ title: "[OS] title"
---
-{ a clear and concise description of what the enhancment is about }
+## Summary
+
+* OS: { type-or-version }
+* Type: { core, doc, performance, scripts, tests, wheels, new-api }
+
+## Description
+
+{{{
+ A clear explanation of your proposal. Please read the contributing guidelines before submit:
+ https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md
+}}}
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..e8bbb2a4
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,13 @@
+## Summary
+
+* OS: { type-or-version }
+* Bug fix: { yes/no }
+* Type: { core, doc, performance, scripts, tests, wheels, new-api }
+* Fixes: { comma-separated list of issues fixed by this PR, if any }
+
+## Description
+
+{{{
+ A clear explanation of your bugfix or enhancement. Please read the contributing guidelines before submit:
+ https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md
+}}}
diff --git a/.github/no-response.yml b/.github/no-response.yml
new file mode 100644
index 00000000..56457a28
--- /dev/null
+++ b/.github/no-response.yml
@@ -0,0 +1,10 @@
+# Configuration for probot-no-response: https://github.com/probot/no-response
+
+# Number of days of inactivity before an issue is closed for lack of response
+daysUntilClose: 14
+# Label requiring a response
+responseRequiredLabel: need-more-info
+# Comment to post when closing an Issue for lack of response.
+# Set to `false` to disable
+closeComment: >
+ This issue has been automatically closed because there has been no response for more information from the original author. Please reach out if you have or find the answers requested so that this can be investigated further.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..8427a929
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,117 @@
+# Executed on every push by GitHub Actions. This runs CI tests and
+# generates wheels (not all) on the following platforms:
+#
+# * Linux
+# * macOS
+# * Windows (commented)
+# * FreeBSD
+#
+# To skip certain builds see:
+# https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip
+#
+# External GH actions:
+# * https://github.com/actions/checkout
+# * https://github.com/actions/setup-python
+# * https://github.com/actions/upload-artifact
+# * https://github.com/marketplace/actions/cancel-workflow-action
+# * https://github.com/vmactions/freebsd-vm
+
+on: [push]
+name: build
+jobs:
+ linux-macos-win:
+ name: ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ # os: [ubuntu-latest, macos-latest, windows-latest]
+ os: [ubuntu-latest, macos-latest]
+ include:
+ - {name: Linux, python: '3.9', os: ubuntu-latest}
+ env:
+ CIBW_TEST_COMMAND:
+ PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_TESTING=1 PSUTIL_DEBUG=1 python {project}/psutil/tests/runner.py &&
+ PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_TESTING=1 PSUTIL_DEBUG=1 python {project}/psutil/tests/test_memleaks.py
+ CIBW_TEST_EXTRAS: test
+ CIBW_SKIP: cp35-* pp*
+
+ steps:
+ - name: Cancel previous runs
+ uses: styfle/cancel-workflow-action@0.6.0
+ with:
+ access_token: ${{ github.token }}
+
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ with:
+ python-version: 3.9
+
+ - name: Install cibuildwheel
+ run: pip install cibuildwheel
+
+ # - name: (Windows) install Visual C++ for Python 2.7
+ # if: matrix.os == 'windows-latest'
+ # run: |
+ # choco install vcpython27 -f -y
+
+ - name: Run tests
+ run: cibuildwheel .
+
+ - name: Create wheels
+ uses: actions/upload-artifact@v2
+ with:
+ name: wheels
+ path: wheelhouse
+
+ - name: Print hashes
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ make generate-manifest
+ python setup.py sdist
+ mv dist/psutil*.tar.gz wheelhouse/
+ python scripts/internal/print_hashes.py wheelhouse/
+
+ freebsd:
+ runs-on: macos-latest
+ steps:
+ - name: Cancel previous runs
+ uses: styfle/cancel-workflow-action@0.6.0
+ with:
+ access_token: ${{ github.token }}
+
+ - uses: actions/checkout@v2
+
+ - name: Run tests
+ id: test
+ uses: vmactions/freebsd-vm@v0.0.8
+ with:
+ usesh: true
+ prepare: pkg install -y gcc python3
+ run: |
+ set +e
+ export \
+ PYTHONUNBUFFERED=1 \
+ PYTHONWARNINGS=always \
+ PSUTIL_TESTING=1 \
+ PSUTIL_DEBUG=1
+ python3 -m pip install --user setuptools
+ python3 setup.py install
+ python3 psutil/tests/runner.py
+ python3 psutil/tests/test_memleaks.py
+
+ linters:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ - name: 'Run linters'
+ run: |
+ python2 -m pip install flake8
+ python3 -m pip install flake8
+ python2 -m flake8 .
+ python3 -m flake8 .
+ echo "flake8 linting OK"
+ find . -type f \( -iname "*.c" -o -iname "*.h" \) | xargs python3 scripts/internal/clinter.py
+ echo "C linting OK"
diff --git a/.github/workflows/issues.py b/.github/workflows/issues.py
new file mode 100644
index 00000000..964e5192
--- /dev/null
+++ b/.github/workflows/issues.py
@@ -0,0 +1,332 @@
+#!/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.
+
+"""
+Bot triggered by Github Actions every time a new issue, PR or comment
+is created. Assign labels, provide replies, closes issues, etc. depending
+on the situation.
+"""
+
+import functools
+import json
+import os
+import re
+from pprint import pprint as pp
+
+from github import Github
+
+
+ROOT_DIR = os.path.realpath(
+ os.path.join(os.path.dirname(__file__), '..', '..'))
+SCRIPTS_DIR = os.path.join(ROOT_DIR, 'scripts')
+
+
+# --- constants
+
+
+LABELS_MAP = {
+ # platforms
+ "linux": [
+ "linux", "ubuntu", "redhat", "mint", "centos", "red hat", "archlinux",
+ "debian", "alpine", "gentoo", "fedora", "slackware", "suse", "RHEL",
+ "opensuse", "manylinux", "apt ", "apt-", "rpm", "yum", "kali",
+ "/sys/class", "/proc/net", "/proc/disk", "/proc/smaps",
+ "/proc/vmstat",
+ ],
+ "windows": [
+ "windows", "win32", "WinError", "WindowsError", "win10", "win7",
+ "win ", "mingw", "msys", "studio", "microsoft", "make.bat",
+ "CloseHandle", "GetLastError", "NtQuery", "DLL", "MSVC", "TCHAR",
+ "WCHAR", ".bat", "OpenProcess", "TerminateProcess", "appveyor",
+ "windows error", "NtWow64", "NTSTATUS", "Visual Studio",
+ ],
+ "macos": [
+ "macos", "mac ", "osx", "os x", "mojave", "sierra", "capitan",
+ "yosemite", "catalina", "mojave", "big sur", "xcode", "darwin",
+ "dylib",
+ ],
+ "aix": ["aix"],
+ "cygwin": ["cygwin"],
+ "freebsd": ["freebsd"],
+ "netbsd": ["netbsd"],
+ "openbsd": ["openbsd"],
+ "sunos": ["sunos", "solaris"],
+ "wsl": ["wsl"],
+ "unix": [
+ "psposix", "_psutil_posix", "waitpid", "statvfs", "/dev/tty",
+ "/dev/pts",
+ ],
+ "pypy": ["pypy"],
+ # types
+ "enhancement": ["enhancement"],
+ "memleak": ["memory leak", "leaks memory", "memleak", "mem leak"],
+ "api": ["idea", "proposal", "api", "feature"],
+ "performance": ["performance", "speedup", "speed up", "slow", "fast"],
+ "wheels": ["wheel", "wheels"],
+ "scripts": [
+ "example script", "examples script", "example dir", "scripts/",
+ ],
+ # bug
+ "bug": [
+ "fail", "can't execute", "can't install", "cannot execute",
+ "cannot install", "install error", "crash", "critical",
+ ],
+ # doc
+ "doc": [
+ "doc ", "document ", "documentation", "readthedocs", "pythonhosted",
+ "HISTORY", "README", "dev guide", "devguide", "sphinx", "docfix",
+ "index.rst",
+ ],
+ # tests
+ "tests": [
+ " test ", "tests", "travis", "coverage", "cirrus", "appveyor",
+ "continuous integration", "unittest", "pytest", "unit test",
+ ],
+ # critical errors
+ "priority-high": [
+ "WinError", "WindowsError", "RuntimeError", "ZeroDivisionError",
+ "SystemError", "MemoryError", "core dumped",
+ "segfault", "segmentation fault",
+ ],
+}
+
+LABELS_MAP['scripts'].extend(
+ [x for x in os.listdir(SCRIPTS_DIR) if x.endswith('.py')])
+
+OS_LABELS = [
+ "linux", "windows", "macos", "freebsd", "openbsd", "netbsd", "openbsd",
+ "bsd", "sunos", "unix", "wsl", "aix", "cygwin",
+]
+
+ILLOGICAL_PAIRS = [
+ ('bug', 'enhancement'),
+ ('doc', 'tests'),
+ ('scripts', 'doc'),
+ ('scripts', 'tests'),
+ ('bsd', 'freebsd'),
+ ('bsd', 'openbsd'),
+ ('bsd', 'netbsd'),
+]
+
+# --- replies
+
+REPLY_MISSING_PYTHON_HEADERS = """\
+It looks like you're missing `Python.h` headers. This usually means you have \
+to install them first, then retry psutil installation.
+Please read \
+[INSTALL](https://github.com/giampaolo/psutil/blob/master/INSTALL.rst) \
+instructions for your platform. \
+This is an auto-generated response based on the text you submitted. \
+If this was a mistake or you think there's a bug with psutil installation \
+process, please add a comment to reopen this issue.
+"""
+
+# REPLY_UPDATE_CHANGELOG = """\
+# """
+
+
+# --- github API utils
+
+
+def is_pr(issue):
+ return issue.pull_request is not None
+
+
+def has_label(issue, label):
+ assigned = [x.name for x in issue.labels]
+ return label in assigned
+
+
+def has_os_label(issue):
+ labels = set([x.name for x in issue.labels])
+ for label in OS_LABELS:
+ if label in labels:
+ return True
+ return False
+
+
+def get_repo():
+ repo = os.environ['GITHUB_REPOSITORY']
+ token = os.environ['GITHUB_TOKEN']
+ return Github(token).get_repo(repo)
+
+
+# --- event utils
+
+
+@functools.lru_cache()
+def _get_event_data():
+ ret = json.load(open(os.environ["GITHUB_EVENT_PATH"]))
+ pp(ret)
+ return ret
+
+
+def is_event_new_issue():
+ data = _get_event_data()
+ try:
+ return data['action'] == 'opened' and 'issue' in data
+ except KeyError:
+ return False
+
+
+def is_event_new_pr():
+ data = _get_event_data()
+ try:
+ return data['action'] == 'opened' and 'pull_request' in data
+ except KeyError:
+ return False
+
+
+def get_issue():
+ data = _get_event_data()
+ try:
+ num = data['issue']['number']
+ except KeyError:
+ num = data['pull_request']['number']
+ return get_repo().get_issue(number=num)
+
+
+# --- actions
+
+
+def log(msg):
+ if '\n' in msg or "\r\n" in msg:
+ print(">>>\n%s\n<<<" % msg)
+ else:
+ print(">>> %s <<<" % msg)
+
+
+def add_label(issue, label):
+ def should_add(issue, label):
+ if has_label(issue, label):
+ log("already has label %r" % (label))
+ return False
+
+ for left, right in ILLOGICAL_PAIRS:
+ if label == left and has_label(issue, right):
+ log("already has label" % (label))
+ return False
+
+ return not has_label(issue, label)
+
+ if not should_add(issue, label):
+ log("should not add label %r" % label)
+ return
+
+ log("add label %r" % label)
+ issue.add_to_labels(label)
+
+
+def _guess_labels_from_text(issue, text):
+ for label, keywords in LABELS_MAP.items():
+ for keyword in keywords:
+ if keyword.lower() in text.lower():
+ yield (label, keyword)
+
+
+def add_labels_from_text(issue, text):
+ for label, keyword in _guess_labels_from_text(issue, text):
+ add_label(issue, label)
+
+
+def add_labels_from_new_body(issue, text):
+ log("start searching for template lines in new issue/PR body")
+ # add os label
+ r = re.search(r"\* OS:.*?\n", text)
+ log("search for 'OS: ...' line")
+ if r:
+ log("found")
+ add_labels_from_text(issue, r.group(0))
+ else:
+ log("not found")
+
+ # add bug/enhancement label
+ log("search for 'Bug fix: y/n' line")
+ r = re.search(r"\* Bug fix:.*?\n", text)
+ if is_pr(issue) and \
+ r is not None and \
+ not has_label(issue, "bug") and \
+ not has_label(issue, "enhancement"):
+ log("found")
+ s = r.group(0).lower()
+ if 'yes' in s:
+ add_label(issue, 'bug')
+ else:
+ add_label(issue, 'enhancement')
+ else:
+ log("not found")
+
+ # add type labels
+ log("search for 'Type: ...' line")
+ r = re.search(r"\* Type:.*?\n", text)
+ if r:
+ log("found")
+ s = r.group(0).lower()
+ if 'doc' in s:
+ add_label(issue, 'doc')
+ if 'performance' in s:
+ add_label(issue, 'performance')
+ if 'scripts' in s:
+ add_label(issue, 'scripts')
+ if 'tests' in s:
+ add_label(issue, 'tests')
+ if 'wheels' in s:
+ add_label(issue, 'wheels')
+ if 'new-api' in s:
+ add_label(issue, 'new-api')
+ if 'new-platform' in s:
+ add_label(issue, 'new-platform')
+ else:
+ log("not found")
+
+
+# --- events
+
+
+def on_new_issue(issue):
+ def has_text(text):
+ return text in issue.title.lower() or text in issue.body.lower()
+
+ log("searching for missing Python.h")
+ if has_text("missing python.h") or \
+ has_text("python.h: no such file or directory") or \
+ "#include<Python.h>\n^~~~" in issue.body.replace(' ', '') or \
+ "#include<Python.h>\r\n^~~~" in issue.body.replace(' ', ''):
+ log("found")
+ issue.create_comment(REPLY_MISSING_PYTHON_HEADERS)
+ issue.edit(state='closed')
+ return
+
+
+def on_new_pr(issue):
+ pass
+ # pr = get_repo().get_pull(issue.number)
+ # files = [x.filename for x in list(pr.get_files())]
+ # if "HISTORY.rst" not in files:
+ # issue.create_comment(REPLY_UPDATE_CHANGELOG)
+
+
+def main():
+ issue = get_issue()
+ stype = "PR" if is_pr(issue) else "issue"
+ log("running issue bot for %s %r" % (stype, issue))
+
+ if is_event_new_issue():
+ log("created new issue %s" % issue)
+ add_labels_from_text(issue, issue.title)
+ add_labels_from_new_body(issue, issue.body)
+ on_new_issue(issue)
+ elif is_event_new_pr():
+ log("created new PR %s" % issue)
+ add_labels_from_text(issue, issue.title)
+ add_labels_from_new_body(issue, issue.body)
+ on_new_pr(issue)
+ else:
+ log("unhandled event")
+
+
+if __name__ == '__main__':
+ main()
diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml
new file mode 100644
index 00000000..fa739eab
--- /dev/null
+++ b/.github/workflows/issues.yml
@@ -0,0 +1,28 @@
+# Fired by Github Actions every time an issue, PR or comment is created.
+name: issues
+on:
+ issues:
+ types: [opened]
+ pull_request:
+ typed: [opened]
+ issue_comment:
+ types: [created]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ # install python
+ - uses: actions/checkout@v2
+ - name: Install Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.8
+ # install deps
+ - name: Install deps
+ run: python -m pip install --upgrade pip PyGithub
+ # run
+ - name: Run
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ PYTHONUNBUFFERED=1 python .github/workflows/issues.py
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index d49f2cf1..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-language: python
-cache: pip
-matrix:
- include:
- # macOS
- - language: generic
- os: osx
- env: PYVER=py27
- - language: generic
- os: osx
- env: PYVER=py36
- # Linux
- - python: 2.7
- - python: 3.5
- - python: 3.6
- - python: 3.7
- - python: 3.8
- # pypy
- # - python: pypy
- - python: pypy3
-install:
- - ./.ci/travis/install.sh
-script:
- - ./.ci/travis/run.sh
-after_success:
- - |
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]] && [[ "$(uname -s)" != 'Darwin' ]]; then
- echo "sending test coverage results to coveralls.io"
- coveralls
- fi
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..69c9dd94
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Contributing to psutil project
+==============================
+
+Issues
+------
+
+* The issue tracker is for reporting problems or proposing enhancements related
+ to the **program code**.
+* Please do not open issues **asking for support**. Instead, use the forum at:
+ https://groups.google.com/g/psutil.
+* Before submitting a new issue, **search** if there are existing issues for
+ the same topic.
+* **Be clear** in describing what the problem is and try to be accurate in
+ editing the default issue **template**. There is a bot which automatically
+ assigns **labels** based on issue's title and body format. Labels help
+ keeping the issues properly organized and searchable (by OS, issue type, etc.).
+* To report a **security vulnerability**, use the
+ [Tidelift security contact](https://tidelift.com/security).
+ Tidelift will coordinate the fix and the disclosure of the reported problem.
+
+Pull Requests
+-------------
+
+* The PR system is for fixing bugs or make enhancements related to the
+ **program code**.
+* If you whish to implement a new feature or add support for a new platform it's
+ better to **discuss it first**, either on the issue tracker, the forum or via
+ private email.
+* In order to get acquainted with the code base and tooling, take a look at the
+ **[Development Guide](https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst)**.
+* If you can, remember to update
+ [HISTORY.rst](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
+ and [CREDITS](https://github.com/giampaolo/psutil/blob/master/CREDITS) file.
diff --git a/CREDITS b/CREDITS
index 690f5717..3f388854 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,122 @@ W: https://github.com/ryoon
D: NetBSD implementation (co-author).
I: 557
+Donations
+-------------------------------------------------------------------------------
+
+N: aristocratos
+W: https://github.com/aristocratos
+
+N: Daniel Widdis
+C: Washington, USA
+W: https://github.com/dbwiddis
+
+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 +226,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 +277,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 +298,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 +338,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 +353,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 +387,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 +405,6 @@ I: 578, 581, 587
N: spacewanderlzx
C: Guangzhou,China
-E: spacewanderlzx@gmail.com
I: 555
N: Fabian Groffen
@@ -373,8 +421,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 +452,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,12 +471,11 @@ 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
+W: https://github.com/jomann09
+I: 816, 775, 1874
N: Jeremy Humble
W: https://github.com/jhumble
@@ -448,7 +491,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 +508,6 @@ I: 936, 1133
N: Pierre Fersing
C: France
-E: pierre.fersing@bleemeo.com
I: 950
N: Thiago Borges Abdnur
@@ -504,7 +545,7 @@ I: 1042, 1079, 1070
N: Oleksii Shevchuk
W: https://github.com/alxchk
-I: 1077, 1093, 1091, 1220, 1346
+I: 1077, 1093, 1091, 1220, 1346, 1904
N: Prodesire
W: https://github.com/Prodesire
@@ -609,9 +650,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 +708,39 @@ 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
+
+N: Vincent A. Arcila
+W: https://github.com/jandrovins
+I: 1620, 1727
+
+N: Tim Schlueter
+W: https://github.com/modelrockettier
+I: 1822
+
+N: marxin
+W: https://github.com/marxin
+I: 1851
+
+N: guille
+W: https://github.com/guille
+I: 1913
+
+N: David Knaack
+W: https://github.com/davidkna
+I: 1921
diff --git a/HISTORY.rst b/HISTORY.rst
index 24186c3d..c826f47b 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,12 +1,104 @@
*Bug tracker at https://github.com/giampaolo/psutil/issues*
-5.7.1 (unreleased)
-==================
+5.8.1 (IN DEVELOPMENT)
+======================
XXXX-XX-XX
**Enhancements**
+- 1851_: [Linux] cpu_freq() is slow on systems with many CPUs. Read current
+ frequency values for all CPUs from /proc/cpuinfo instead of opening many
+ files in /sys fs. (patch by marxin)
+
+**Bug fixes**
+
+- 1456_: [macOS] psutil.cpu_freq()'s min and max are set to 0 if can't be
+ determined (instead of crashing).
+- 1512_: [macOS] sometimes Process.connections() will crash with EOPNOTSUPP
+ for one connection; this is now ignored.
+- 1892_: [macOS] psutil.cpu_freq() broken on Apple M1.
+- 1904_: [Windows] OpenProcess fails with ERROR_SUCCESS due to GetLastError()
+ called after sprintf(). (patch by alxchk)
+- 1874_: [Solaris] swap output error due to incorrect range.
+- 1913_: [Linux] wait_procs seemingly ignoring timeout, TimeoutExpired thrown
+- 1921_: [Windows] psutil.swap_memory() shows committed memory instead of swap
+
+5.8.0
+=====
+
+2020-12-19
+
+**Enhancements**
+
+- 1863_: `disk_partitions()` exposes 2 extra fields: `maxfile` and `maxpath`,
+ which are the maximum file name and path name length.
+- 1872_: [Windows] added support for PyPy 2.7.
+- 1879_: provide pre-compiled wheels for Linux and macOS (yey!).
+- 1880_: get rid of Travis and Cirrus CI services (they are no longer free).
+ CI testing is now done by GitHub Actions on Linux, macOS and FreeBSD (yes).
+ AppVeyor is still being used for Windows CI.
+
+**Bug fixes**
+
+- 1708_: [Linux] get rid of sensors_temperatures() duplicates. (patch by Tim
+ Schlueter).
+- 1839_: [Windows] always raise AccessDenied when failing to query 64 processes
+ from 32 bit ones (NtWoW64 APIs).
+- 1866_: [Windows] process exe(), cmdline(), environ() may raise "invalid
+ access to memory location" on Python 3.9.
+- 1874_: [Solaris] wrong swap output given when encrypted column is present.
+- 1875_: [Windows] process username() may raise ERROR_NONE_MAPPED if the SID
+ has no corresponding account name. In this case AccessDenied is now raised.
+- 1877_: [Windows] OpenProcess may fail with ERROR_SUCCESS. Turn it into
+ AccessDenied or NoSuchProcess depending on whether the PID is alive.
+- 1886_: [macOS] EIO error may be raised on cmdline() and environment(). Now
+ it gets translated into AccessDenied.
+- 1891_: [macOS] get rid of deprecated getpagesize().
+
+5.7.3
+=====
+
+2020-10-23
+
+**Enhancements**
+
+- 809_: [FreeBSD] add support for `Process.rlimit()`.
+- 893_: [BSD] add support for `Process.environ()` (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**
+
+- 1620_: [Linux] cpu_count(logical=False) result is incorrect on systems with
+ more than one CPU socket. (patch by Vincent A. Arcila)
+- 1738_: [macOS] Process.exe() may raise FileNotFoundError if process is still
+ alive but the exe file which launched it got deleted.
+- 1791_: [macOS] fix missing include for getpagesize().
+- 1823_: [Windows] Process.open_files() may cause a segfault due to a NULL
+ pointer.
+- 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.
@@ -24,11 +116,18 @@ XXXX-XX-XX
>>> proc
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
=====
@@ -170,7 +269,7 @@ XXXX-XX-XX
average calculation, including on Windows (emulated). (patch by Ammar Askar)
- 1404_: [Linux] cpu_count(logical=False) uses a second method (read from
`/sys/devices/system/cpu/cpu[0-9]/topology/core_id`) in order to determine
- the number of physical CPUs in case /proc/cpuinfo does not provide this info.
+ the number of CPU cores in case /proc/cpuinfo does not provide this info.
- 1458_: provide coloured test output. Also show failures on KeyboardInterrupt.
- 1464_: various docfixes (always point to python3 doc, fix links, etc.).
- 1476_: [Windows] it is now possible to set process high I/O priority
@@ -416,7 +515,7 @@ XXXX-XX-XX
- 694_: [SunOS] cmdline() could be truncated at the 15th character when
reading it from /proc. An extra effort is made by reading it from process
address space first. (patch by Georg Sauthoff)
-- 771_: [Windows] cpu_count() (both logical and physical) return a wrong
+- 771_: [Windows] cpu_count() (both logical and cores) return a wrong
(smaller) number on systems using process groups (> 64 cores).
- 771_: [Windows] cpu_times(percpu=True) return fewer CPUs on systems using
process groups (> 64 cores).
@@ -1220,8 +1319,8 @@ XXXX-XX-XX
- 593_: [FreeBSD] Process().memory_maps() segfaults.
- 606_: Process.parent() may swallow NoSuchProcess exceptions.
- 611_: [SunOS] net_io_counters has send and received swapped
-- 614_: [Linux]: cpu_count(logical=False) return the number of physical CPUs
- instead of physical cores.
+- 614_: [Linux]: cpu_count(logical=False) return the number of sockets instead
+ of cores.
- 618_: [SunOS] swap tests fail on Solaris when run as normal user
- 628_: [Linux] Process.name() truncates process name in case it contains
spaces or parentheses.
@@ -1338,7 +1437,7 @@ XXXX-XX-XX
**Enhancements**
- 424_: [Windows] installer for Python 3.X 64 bit.
-- 427_: number of logical and physical CPUs (psutil.cpu_count()).
+- 427_: number of logical CPUs and physical cores (psutil.cpu_count()).
- 447_: psutil.wait_procs() timeout parameter is now optional.
- 452_: make Process instances hashable and usable with set()s.
- 453_: tests on Python < 2.7 require unittest2 module.
diff --git a/INSTALL.rst b/INSTALL.rst
index c3b9e91c..22693b56 100644
--- a/INSTALL.rst
+++ b/INSTALL.rst
@@ -1,140 +1,119 @@
-Install pip
-===========
-
-pip is the easiest way to install psutil. It is shipped by default with Python
-2.7.9+ and 3.4+. For other Python versions you can install it manually.
-On Linux or via wget::
-
- wget https://bootstrap.pypa.io/get-pip.py -O - | python
-
-On macOS or via curl::
-
- python < <(curl -s https://bootstrap.pypa.io/get-pip.py)
-
-On Windows, `download pip <https://pip.pypa.io/en/latest/installing/>`__, open
-cmd.exe and install it::
-
- C:\Python27\python.exe get-pip.py
-
-Permission issues (UNIX)
-========================
+Install psutil
+==============
-The commands below assume you're running as root.
-If you aren't or you bump into permission errors you can either install psutil
-for your user only::
+Linux, Windows, macOS (wheels)
+------------------------------
- pip3 install --user psutil
+Pre-compiled wheels are distributed for these platforms, so you won't have to
+install a C compiler. All you have to do is::
-...or prepend ``sudo`` and install it globally, e.g.::
+ pip install psutil
- sudo pip3 install psutil
+If wheels are not available for your platform or architecture, or you whish to
+build & install psutil from sources, keep reading.
-Linux
-=====
+Linux (build)
+-------------
Ubuntu / Debian::
sudo apt-get install gcc python3-dev
- pip3 install psutil
+ pip install --no-binary :all: psutil
RedHat / CentOS::
sudo yum install gcc python3-devel
- pip3 install psutil
+ pip install --no-binary :all: psutil
-If you're on Python 2 use ``python-dev`` instead.
-
-macOS
-=====
-
-Install `Xcode <https://developer.apple.com/downloads/?name=Xcode>`__ then run::
-
- pip3 install psutil
-
-Windows
-=======
-
-Open a cmd.exe shell and run::
-
- python3 -m pip install psutil
+Windows (build)
+---------------
-This assumes "python" is in your PATH. If not, specify the full python.exe
-path.
+In order to install psutil from sources on Windows you need Visual Studio
+(MinGW is not supported).
+Here's a couple of guides describing how to do it: `link <https://blog.ionelmc.ro/2014/12/21/compiling-python-extensions-on-windows/>`__
+and `link <https://cpython-core-tutorial.readthedocs.io/en/latest/build_cpython_windows.html>`__.
+Once VS is installed do::
-In order to compile psutil from sources you'll need **Visual Studio** (Mingw32
-is not supported).
-This `blog post <https://blog.ionelmc.ro/2014/12/21/compiling-python-extensions-on-windows/>`__
-provides numerous info on how to properly set up VS. The needed VS versions are:
+ pip install --no-binary :all: psutil
-* Python 2.6, 2.7: `VS-2008 <http://www.microsoft.com/en-us/download/details.aspx?id=44266>`__
-* Python 3.4: `VS-2010 <http://www.visualstudio.com/downloads/download-visual-studio-vs#d-2010-express>`__
-* Python 3.5+: `VS-2015 <http://www.visualstudio.com/en-au/news/vs2015-preview-vs>`__
+macOS (build)
+-------------
-Compiling 64 bit versions of Python 2.6 and 2.7 with VS 2008 requires
-`Windows SDK and .NET Framework 3.5 SP1 <https://www.microsoft.com/en-us/download/details.aspx?id=3138>`__.
-Once installed run `vcvars64.bat`
-(see `here <http://stackoverflow.com/questions/11072521/>`__).
-Once VS is setup open a cmd.exe shell, cd into psutil directory and run::
+Install `Xcode <https://developer.apple.com/downloads/?name=Xcode>`__ then run::
- python3 setup.py build
- python3 setup.py install
+ pip install --no-binary :all: psutil
FreeBSD
-=======
+-------
::
pkg install python3 gcc
- python -m pip3 install psutil
+ python3 -m pip install psutil
OpenBSD
-=======
+-------
::
export PKG_PATH=http://ftp.eu.openbsd.org/pub/OpenBSD/`uname -r`/packages/`uname -m`/
pkg_add -v python gcc
- python3 -m pip install psutil
+ pip install psutil
NetBSD
-======
+------
::
export PKG_PATH="ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/`uname -m`/`uname -r`/All"
pkg_add -v pkgin
pkgin install python3 gcc
- python3 -m pip install psutil
+ pip install psutil
-Solaris
-=======
+Sun Solaris
+-----------
-If ``cc`` compiler is not installed create a symlink to ``gcc``::
+If ``cc`` compiler is not installed create a symbolic link to ``gcc``::
sudo ln -s /usr/bin/gcc /usr/local/bin/cc
Install::
pkg install gcc
- python3 -m pip install psutil
+ pip install psutil
-Install from sources
-====================
+Troubleshooting
+===============
-::
+Install pip
+-----------
- git clone https://github.com/giampaolo/psutil.git
- cd psutil
- python3 setup.py install
+Pip is shipped by default with Python 2.7.9+ and 3.4+.
+If you don't have pip you can install with wget::
-Testing installation
-====================
+ wget https://bootstrap.pypa.io/get-pip.py -O - | python3
-::
+...ow with curl::
+
+ python3 < <(curl -s https://bootstrap.pypa.io/get-pip.py)
+
+On Windows, `download pip <https://pip.pypa.io/en/latest/installing/>`__, open
+cmd.exe and install it with::
+
+ py get-pip.py
+
+"pip not found"
+---------------
+
+Sometimes pip is installed but it's not available in your ``PATH``
+("pip command not found" or similar). Try this::
+
+ python3 -m pip install psutil
- python3 -m psutil.tests
+Permission errors (UNIX)
+------------------------
-Dev Guide
-=========
+If you want to install psutil system-wide and you bump into permission errors
+either run as root user or prepend ``sudo``::
-See: `dev guide <https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst>`__.
+ sudo pip install psutil
diff --git a/MANIFEST.in b/MANIFEST.in
index d0d75240..e9c20d81 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,7 +1,7 @@
-include .cirrus.yml
include .coveragerc
include .flake8
include .gitignore
+include CONTRIBUTING.md
include CREDITS
include HISTORY.rst
include INSTALL.rst
@@ -48,6 +48,8 @@ include psutil/arch/aix/ifaddrs.h
include psutil/arch/aix/net_connections.c
include psutil/arch/aix/net_connections.h
include psutil/arch/aix/net_kernel_structs.h
+include psutil/arch/freebsd/cpu.c
+include psutil/arch/freebsd/cpu.h
include psutil/arch/freebsd/proc_socks.c
include psutil/arch/freebsd/proc_socks.h
include psutil/arch/freebsd/specific.c
@@ -60,6 +62,8 @@ include psutil/arch/netbsd/specific.c
include psutil/arch/netbsd/specific.h
include psutil/arch/openbsd/specific.c
include psutil/arch/openbsd/specific.h
+include psutil/arch/osx/cpu.c
+include psutil/arch/osx/cpu.h
include psutil/arch/osx/process_info.c
include psutil/arch/osx/process_info.h
include psutil/arch/solaris/environ.c
@@ -96,7 +100,7 @@ include psutil/tests/test_bsd.py
include psutil/tests/test_connections.py
include psutil/tests/test_contracts.py
include psutil/tests/test_linux.py
-include psutil/tests/test_memory_leaks.py
+include psutil/tests/test_memleaks.py
include psutil/tests/test_misc.py
include psutil/tests/test_osx.py
include psutil/tests/test_posix.py
@@ -117,16 +121,21 @@ 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/convert_readme.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_hashes.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
@@ -145,4 +154,3 @@ include scripts/top.py
include scripts/who.py
include scripts/winservices.py
include setup.py
-include tox.ini
diff --git a/Makefile b/Makefile
index 2b2a98ef..087ddd78 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
@@ -46,7 +47,7 @@ all: test
# ===================================================================
clean: ## Remove all build files.
- rm -rf `find . -type d -name __pycache__ \
+ @rm -rfv `find . -type d -name __pycache__ \
-o -type f -name \*.bak \
-o -type f -name \*.orig \
-o -type f -name \*.pyc \
@@ -56,13 +57,12 @@ clean: ## Remove all build files.
-o -type f -name \*.so \
-o -type f -name \*.~ \
-o -type f -name \*\$testfn`
- rm -rf \
+ @rm -rfv \
*.core \
*.egg-info \
*\@psutil-* \
.coverage \
.failed-tests.txt \
- .tox \
build/ \
dist/ \
docs/_build/ \
@@ -88,7 +88,7 @@ uninstall: ## Uninstall this package via pip.
$(PYTHON) scripts/internal/purge_installation.py
install-pip: ## Install pip (no-op if already installed).
- $(PYTHON) -c \
+ @$(PYTHON) -c \
"import sys, ssl, os, pkgutil, tempfile, atexit; \
sys.exit(0) if pkgutil.find_loader('pip') else None; \
pyexc = 'from urllib.request import urlopen' if sys.version_info[0] == 3 else 'from urllib2 import urlopen'; \
@@ -102,7 +102,7 @@ install-pip: ## Install pip (no-op if already installed).
f.write(data); \
f.flush(); \
print('downloaded %s' % f.name); \
- code = os.system('%s %s --user' % (sys.executable, f.name)); \
+ code = os.system('%s %s --user --upgrade' % (sys.executable, f.name)); \
f.close(); \
sys.exit(code);"
@@ -162,7 +162,7 @@ test-platform: ## Run specific platform tests only.
test-memleaks: ## Memory leak tests.
${MAKE} build
- $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_memory_leaks.py
+ $(TEST_PREFIX) $(PYTHON) $(TSCRIPT) $(ARGS) psutil/tests/test_memleaks.py
test-by-name: ## e.g. make test-by-name ARGS=psutil.tests.test_system.TestSystemAPIs
${MAKE} build
@@ -208,6 +208,19 @@ install-git-hooks: ## Install GIT pre-commit hook.
chmod +x .git/hooks/pre-commit
# ===================================================================
+# Wheels
+# ===================================================================
+
+download-wheels-github: ## Download latest wheels hosted on github.
+ $(PYTHON) scripts/internal/download_wheels_github.py --tokenfile=~/.github.token
+
+download-wheels-appveyor: ## Download latest wheels hosted on appveyor.
+ $(PYTHON) scripts/internal/download_wheels_appveyor.py
+
+print-wheels: ## Print downloaded wheels
+ $(PYTHON) scripts/internal/print_wheels.py
+
+# ===================================================================
# Distribution
# ===================================================================
@@ -218,18 +231,13 @@ git-tag-release: ## Git-tag a new release.
sdist: ## Create tar.gz source distribution.
${MAKE} generate-manifest
$(PYTHON) setup.py sdist
-
-wheel: ## Generate wheel.
- $(PYTHON) setup.py bdist_wheel
-
-win-download-wheels: ## Download wheels hosted on appveyor.
- $(TEST_PREFIX) $(PYTHON) scripts/internal/win_download_wheels.py --user giampaolo --project psutil
+ $(PYTHON) -m twine check dist/*.tar.gz
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
@@ -250,8 +258,12 @@ 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} sdist
+ ${MAKE} download-wheels-github
+ ${MAKE} download-wheels-appveyor
+ ${MAKE} print-hashes
+ ${MAKE} print-wheels
+ $(PYTHON) -m twine check dist/*
$(PYTHON) -c \
"from psutil import __version__ as ver; \
doc = open('docs/index.rst').read(); \
@@ -259,10 +271,9 @@ pre-release: ## Check if we're ready to produce a new release.
assert ver in doc, '%r not in docs/index.rst' % ver; \
assert ver in history, '%r not in HISTORY.rst' % ver; \
assert 'XXXX' not in history, 'XXXX in HISTORY.rst';"
- $(PYTHON) -c "import subprocess, sys; out = subprocess.check_output('git diff --quiet && git diff --cached --quiet', shell=True).strip(); sys.exit('there are uncommitted changes:\n%s' % out) if out else 0 ;"
release: ## Create a release (down/uploads tar.gz, wheels, git tag release).
- ${MAKE} pre-release
+ $(PYTHON) -c "import subprocess, sys; out = subprocess.check_output('git diff --quiet && git diff --cached --quiet', shell=True).strip(); sys.exit('there are uncommitted changes:\n%s' % out) if out else 0 ;"
$(PYTHON) -m twine upload dist/* # upload tar.gz and Windows wheels on PyPI
${MAKE} git-tag-release
${MAKE} tidelift-relnotes
@@ -291,6 +302,12 @@ 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
+
+print-hashes: ## Prints hashes of files in dist/ directory
+ $(PYTHON) scripts/internal/print_hashes.py dist/
+
# ===================================================================
# Misc
# ===================================================================
diff --git a/README.rst b/README.rst
index 2137a1a2..22f0f8c0 100644
--- a/README.rst
+++ b/README.rst
@@ -1,6 +1,6 @@
| |downloads| |stars| |forks| |contributors| |coverage| |quality|
| |version| |py-versions| |packages| |license|
-| |travis| |appveyor| |cirrus| |doc| |twitter| |tidelift|
+| |github-actions| |appveyor| |doc| |twitter| |tidelift|
.. |downloads| image:: https://img.shields.io/pypi/dm/psutil.svg
:target: https://pepy.tech/project/psutil
@@ -22,24 +22,20 @@
:target: https://www.codacy.com/app/g-rodola/psutil?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=giampaolo/psutil&amp;utm_campaign=Badge_Grade
:alt: Code quality
-.. |travis| image:: https://img.shields.io/travis/giampaolo/psutil/master.svg?maxAge=3600&label=Linux,%20OSX,%20PyPy
- :target: https://travis-ci.org/giampaolo/psutil
- :alt: Linux tests (Travis)
+.. |github-actions| image:: https://img.shields.io/github/workflow/status/giampaolo/psutil/CI?label=Linux%2C%20macOS%2C%20FreeBSD
+ :target: https://github.com/giampaolo/psutil/actions?query=workflow%3Abuild
+ :alt: Linux, macOS, Windows tests
.. |appveyor| image:: https://img.shields.io/appveyor/ci/giampaolo/psutil/master.svg?maxAge=3600&label=Windows
:target: https://ci.appveyor.com/project/giampaolo/psutil
:alt: Windows tests (Appveyor)
-.. |cirrus| image:: https://img.shields.io/cirrus/github/giampaolo/psutil?label=FreeBSD
- :target: https://cirrus-ci.com/github/giampaolo/psutil-cirrus-ci
- :alt: FreeBSD tests (Cirrus-Ci)
-
-.. |coverage| image:: https://img.shields.io/coveralls/github/giampaolo/psutil.svg?label=test%20coverage
+.. |coverage| image:: https://coveralls.io/repos/github/giampaolo/psutil/badge.svg?branch=master
:target: https://coveralls.io/github/giampaolo/psutil?branch=master
:alt: Test coverage (coverall.io)
.. |doc| image:: https://readthedocs.org/projects/psutil/badge/?version=latest
- :target: http://psutil.readthedocs.io/en/latest/?badge=latest
+ :target: https://psutil.readthedocs.io/en/latest/
:alt: Documentation Status
.. |version| image:: https://img.shields.io/pypi/v/psutil.svg?label=pypi
@@ -47,7 +43,6 @@
:alt: Latest version
.. |py-versions| image:: https://img.shields.io/pypi/pyversions/psutil.svg
- :target: https://pypi.org/project/psutil
:alt: Supported Python versions
.. |packages| image:: https://repology.org/badge/tiny-repos/python:psutil.svg
@@ -68,18 +63,21 @@
-----
-Quick links
-===========
-
-- `Home page <https://github.com/giampaolo/psutil>`_
-- `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_
-- `Documentation <http://psutil.readthedocs.io>`_
-- `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>`_
-- `Development guide <https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst>`_
-- `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`_
+.. raw:: html
+
+ <div align="center">
+ <a href="https://github.com/giampaolo/psutil"><img src="https://github.com/giampaolo/psutil/raw/master/docs/_static/psutil-logo.png" /></a>
+ <br />
+ <br />
+ <a href="https://github.com/giampaolo/psutil"><b>Home</b></a>&nbsp;&nbsp;&nbsp;
+ <a href="https://github.com/giampaolo/psutil/blob/master/INSTALL.rst"><b>Install</b></a>&nbsp;&nbsp;&nbsp;
+ <a href="https://psutil.readthedocs.io/"><b>Documentation</b></a>&nbsp;&nbsp;&nbsp;
+ <a href="https://pypi.org/project/psutil/#files"><b>Download</b></a>&nbsp;&nbsp;&nbsp;
+ <a href="https://groups.google.com/g/psutil"><b>Forum</b></a>&nbsp;&nbsp;&nbsp;
+ <a href="https://gmpy.dev/tags/psutil"><b>Blog</b></a>&nbsp;&nbsp;&nbsp;
+ <a href="#funding"><b>Funding</b></a>&nbsp;&nbsp;&nbsp;
+ <a href="https://github.com/giampaolo/psutil/blob/master/HISTORY.rst"><b>What's new</b></a>&nbsp;&nbsp;&nbsp;
+ </div>
Summary
=======
@@ -100,81 +98,55 @@ 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.
-
-psutil for enterprise
-=====================
-
-.. |tideliftlogo| image:: https://nedbatchelder.com/pix/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White_small.png
- :width: 150
- :alt: Tidelift
- :target: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme
-
-.. list-table::
- :widths: 10 150
+Supported Python versions are **2.6**, **2.7**, **3.4+** and
+`PyPy <http://pypy.org/>`__.
- * - |tideliftlogo|
- - The maintainer of psutil and thousands of other packages are working
- with Tidelift to deliver commercial support and maintenance for the open
- source dependencies you use to build your applications. Save time,
- reduce risk, and improve code health, while paying the maintainers of
- the exact dependencies you use.
- `Learn more <https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`__.
-
- By subscribing to Tidelift you will help me (`Giampaolo Rodola`_) support
- psutil future development. Alternatively consider making a small
- `donation`_.
+Funding
+=======
-Security
+While psutil is free software and will always be, the project would benefit
+immensely from some funding.
+Keeping up with bug reports and maintenance has become hardly sustainable for
+me alone in terms of time.
+If you're a company that's making significant use of psutil you can consider
+becoming a sponsor via `GitHub Sponsors <https://github.com/sponsors/giampaolo>`__,
+`Open Collective <https://opencollective.com/psutil>`__ or
+`PayPal <https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8>`__
+and have your logo displayed in here and psutil `doc <https://psutil.readthedocs.io>`__.
+
+Sponsors
========
-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 |
-+------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+
-
-Also see `scripts directory <https://github.com/giampaolo/psutil/tree/master/scripts>`__
-and `doc recipes <http://psutil.readthedocs.io/#recipes/>`__.
-
-Projects using psutil
-=====================
-
-psutil has roughly the following monthly downloads:
+.. raw:: html
-.. image:: https://img.shields.io/pypi/dm/psutil.svg
- :target: https://pepy.tech/project/psutil
- :alt: Downloads
+ <div>
+ <a href="https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme">
+ <img src="https://github.com/giampaolo/psutil/raw/master/docs/_static/tidelift-logo.png" />
+ </a>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <a href="https://sansec.io/">
+ <img src="https://sansec.io/assets/images/logo.svg" />
+ </a>
+ </div>
+ <sup><a href="https://github.com/sponsors/giampaolo">add your logo</a></sup>
-There are over
-`10.000 open source projects <https://libraries.io/pypi/psutil/dependent_repositories?page=1>`__
-on github which depend from psutil.
-Here's some I find particularly interesting:
+Supporters
+==========
-- https://github.com/google/grr
-- https://github.com/facebook/osquery/
-- https://github.com/nicolargo/glances
-- https://github.com/Jahaja/psdash
-- https://github.com/ajenti/ajenti
-- https://github.com/home-assistant/home-assistant/
+.. raw:: html
+ <div>
+ <a href="https://github.com/dbwiddis"><img height="40" width="40" title="Daniel Widdis" src="https://avatars1.githubusercontent.com/u/9291703?s=88&amp;v=4" /></a>
+ <a href="https://github.com/aristocratos"><img height="40" width="40" title="aristocratos" src="https://avatars3.githubusercontent.com/u/59659483?s=96&amp;v=4" /></a>
+ <a href="https://github.com/cybersecgeek"><img height="40" width="40" title="cybersecgeek" src="https://avatars.githubusercontent.com/u/12847926?v=4" /></a>
+ </div>
+ <sup><a href="https://github.com/sponsors/giampaolo">add your avatar</a></sup>
-Portings
-========
-- Go: https://github.com/shirou/gopsutil
-- C: https://github.com/hamon-in/cpslib
-- Rust: https://github.com/borntyping/rust-psutil
-- Nim: https://github.com/johnscillieri/psutil-nim
+Contributing
+============
+See `contributing guidelines <https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md>`__.
Example usages
==============
@@ -243,8 +215,8 @@ Disks
.. code-block:: python
>>> psutil.disk_partitions()
- [sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid'),
- sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw')]
+ [sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid', maxfile=255, maxpath=4096),
+ sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw', maxfile=255, maxpath=4096)]
>>>
>>> psutil.disk_usage('/')
sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5)
@@ -262,7 +234,7 @@ Network
{'eth0': netio(bytes_sent=485291293, bytes_recv=6004858642, packets_sent=3251564, packets_recv=4787798, errin=0, errout=0, dropin=0, dropout=0),
'lo': netio(bytes_sent=2838627, bytes_recv=2838627, packets_sent=30567, packets_recv=30567, errin=0, errout=0, dropin=0, dropout=0)}
>>>
- >>> psutil.net_connections()
+ >>> psutil.net_connections(kind='tcp')
[sconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED', pid=1254),
sconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING', pid=2987),
...]
@@ -320,18 +292,20 @@ Process management
>>> import psutil
>>> psutil.pids()
- [1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, 268, 1215, 1216, 1220, 1221, 1243, 1244,
- 1301, 1601, 2237, 2355, 2637, 2774, 3932, 4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, 4263, 4282,
- 4306, 4311, 4312, 4313, 4314, 4337, 4339, 4357, 4358, 4363, 4383, 4395, 4408, 4433, 4443, 4445, 4446,
- 5167, 5234, 5235, 5252, 5318, 5424, 5644, 6987, 7054, 7055, 7071]
+ [1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, 268, 1215,
+ 1216, 1220, 1221, 1243, 1244, 1301, 1601, 2237, 2355, 2637, 2774, 3932,
+ 4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, 4263, 4282, 4306, 4311,
+ 4312, 4313, 4314, 4337, 4339, 4357, 4358, 4363, 4383, 4395, 4408, 4433,
+ 4443, 4445, 4446, 5167, 5234, 5235, 5252, 5318, 5424, 5644, 6987, 7054,
+ 7055, 7071]
>>>
>>> p = psutil.Process(7055)
>>> p
psutil.Process(pid=7055, name='python3', status='running', started='09:04:44')
>>> p.name()
- 'python'
+ 'python3'
>>> p.exe()
- '/usr/bin/python'
+ '/usr/bin/python3'
>>> p.cwd()
'/home/giampaolo'
>>> p.cmdline()
@@ -396,7 +370,7 @@ Process management
[popenfile(path='/home/giampaolo/monit.py', fd=3, position=0, mode='r', flags=32768),
popenfile(path='/var/log/monit.log', fd=4, position=235542, mode='a', flags=33793)]
>>>
- >>> p.connections()
+ >>> p.connections(kind='tcp')
[pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED'),
pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING')]
>>>
@@ -474,23 +448,6 @@ Further process APIs
>>> gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate)
>>>
-Popen wrapper:
-
-.. code-block:: python
-
- >>> import psutil
- >>> from subprocess import PIPE
- >>> p = psutil.Popen(["/usr/bin/python", "-c", "print('hello')"], stdout=PIPE)
- >>> p.name()
- 'python'
- >>> p.username()
- 'giampaolo'
- >>> p.communicate()
- ('hello\n', None)
- >>> p.wait(timeout=2)
- 0
- >>>
-
Windows services
----------------
@@ -513,9 +470,22 @@ Windows services
'status': 'stopped',
'username': 'NT AUTHORITY\\LocalService'}
+Projects using psutil
+=====================
+
+Here's some I find particularly interesting:
+
+- https://github.com/google/grr
+- https://github.com/facebook/osquery/
+- https://github.com/nicolargo/glances
+- https://github.com/Jahaja/psdash
+- https://github.com/ajenti/ajenti
+- https://github.com/home-assistant/home-assistant/
-.. _`Giampaolo Rodola`: http://grodola.blogspot.com/p/about.html
-.. _`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
+Portings
+========
+- Go: https://github.com/shirou/gopsutil
+- C: https://github.com/hamon-in/cpslib
+- Rust: https://github.com/rust-psutil/rust-psutil
+- Nim: https://github.com/johnscillieri/psutil-nim
diff --git a/appveyor.yml b/appveyor.yml
index d22c1cb4..aaf7de30 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,13 +1,23 @@
# Build: 3 (bump this up by 1 to force an appveyor run)
os: Visual Studio 2015
-
+# avoid 2 builds when pushing on PRs
+skip_branch_with_pr: true
+# avoid build on new GIT tag
+skip_tags: true
+matrix:
+ # stop build on first failure
+ fast_finish: true
environment:
global:
# SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
# /E:ON and /V:ON options are not enabled in the batch script intepreter
# See: http://stackoverflow.com/a/13751649/163740
WITH_COMPILER: "cmd /E:ON /V:ON /C .\\.ci\\appveyor\\run_with_compiler.cmd"
+ PYTHONWARNINGS: always
+ PYTHONUNBUFFERED: 1
+ PSUTIL_TESTING: 1
+ PSUTIL_DEBUG: 1
matrix:
# 32 bits
@@ -15,10 +25,6 @@ environment:
PYTHON_VERSION: "2.7.x"
PYTHON_ARCH: "32"
- - PYTHON: "C:\\Python35"
- PYTHON_VERSION: "3.5.x"
- PYTHON_ARCH: "32"
-
- PYTHON: "C:\\Python36"
PYTHON_VERSION: "3.6.x"
PYTHON_ARCH: "32"
@@ -31,16 +37,17 @@ environment:
PYTHON_VERSION: "3.8.x"
PYTHON_ARCH: "32"
+ - PYTHON: "C:\\Python39"
+ PYTHON_VERSION: "3.9.x"
+ APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
+ PYTHON_ARCH: "32"
+
# 64 bits
- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.x"
PYTHON_ARCH: "64"
- - PYTHON: "C:\\Python35-x64"
- PYTHON_VERSION: "3.5.x"
- PYTHON_ARCH: "64"
-
- PYTHON: "C:\\Python36-x64"
PYTHON_VERSION: "3.6.x"
PYTHON_ARCH: "64"
@@ -53,6 +60,11 @@ environment:
PYTHON_VERSION: "3.8.x"
PYTHON_ARCH: "64"
+ - PYTHON: "C:\\Python39-x64"
+ PYTHON_VERSION: "3.9.x"
+ APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
+ PYTHON_ARCH: "64"
+
init:
- "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%"
@@ -69,9 +81,11 @@ build: off
test_script:
- "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/winmake.py test"
+ - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/winmake.py test-memleaks"
after_test:
- "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/winmake.py wheel"
+ - "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/print_hashes.py dist"
- "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/print_access_denied.py"
- "%WITH_COMPILER% %PYTHON%/python.exe scripts/internal/print_api_speed.py"
@@ -85,7 +99,7 @@ cache:
# - might want to upload the content of dist/*.whl to a public wheelhouse
skip_commits:
- message: skip-ci
+ message: skip-appveyor
# run build only if one of the following files is modified on commit
only_commits:
diff --git a/docs/DEVGUIDE.rst b/docs/DEVGUIDE.rst
index 2e8272f0..cb9545bc 100644
--- a/docs/DEVGUIDE.rst
+++ b/docs/DEVGUIDE.rst
@@ -1,180 +1,136 @@
+psutil development guide
+========================
+
Build, setup and running tests
-===============================
+..............................
+
+psutil makes extensive use of C extension modules, meaning a C compiler is
+required, see
+`install instructions <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`__.
-Make sure to `install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`__
-a C compiler first, then:
+Once you have a compiler installed:
.. code-block:: bash
- git clone git@github.com:giampaolo/psutil.git
- make setup-dev-env
- make test
+ git clone git@github.com:giampaolo/psutil.git
+ make setup-dev-env # install useful dev libs (flake8, coverage, ...)
+ make build
+ make install
+ make test
-- bear in mind that ``make``(see `Makefile`_) is the designated tool to run
- tests, build, install etc. and that it is also available on Windows (see
+- ``make`` (see `Makefile`_) is the designated tool to run tests, build, install
+ try new features you're developing, etc. This also includes Windows (see
`make.bat`_ ).
-- do not use ``sudo``; ``make install`` installs psutil as a limited user in
- "edit" mode; also ``make setup-dev-env`` installs deps as a limited user.
-- use `make help` to see the list of available commands.
-
-Coding style
-============
-
-- python code strictly follows `PEP-8`_ styling guides and this is enforced by
- a commit GIT hook installed via ``make install-git-hooks``.
-- C code follows `PEP-7`_ styling guides.
-
-Makefile
-========
-
-Some useful make commands:
+ Some useful commands are:
.. code-block:: bash
- make install
- make setup-dev-env # install useful dev libs (flake8, unittest2, etc.)
- make test # run unit tests
make test-parallel # faster
make test-memleaks
make test-coverage
make lint # Python (PEP8) and C linters
+ make uninstall
+ make help
-There are some differences between ``make`` on UNIX and Windows.
-For instance, to run a specific Python version. On UNIX:
+- if you're working on a new feature and you whish to compile & test it "on the
+ fly" from a test script, this is a quick & dirty way to do it:
.. code-block:: bash
- make test PYTHON=python3.5
-
-On Windows:
+ make test TSCRIPT=test_script.py # on UNIX
+ make test test_script.py # on Windows
-.. code-block:: bat
+- do not use ``sudo``. ``make install`` installs psutil as a limited user in
+ "edit" mode, meaning you can edit psutil code on the fly while you develop.
- make -p C:\python35\python.exe test
-
-If you want to modify psutil and run a script on the fly which uses it do
-(on UNIX):
+- if you want to target a specific Python version:
.. code-block:: bash
- make test TSCRIPT=foo.py
-
-On Windows:
+ make test PYTHON=python3.5 # UNIX
+ make test -p C:\python35\python.exe # Windows
-.. code-block:: bat
-
- make test foo.py
+Coding style
+------------
-Adding a new feature
-====================
+- python code strictly follows `PEP-8`_ styling guides and this is enforced by
+ a commit GIT hook installed via ``make install-git-hooks`` which will reject
+ commits if code is not PEP-8 complieant.
+- C code should follow `PEP-7`_ styling guides.
-Usually the files involved when adding a new functionality are:
+Code organization
+-----------------
.. code-block:: bash
- psutil/__init__.py # main psutil namespace
- psutil/_ps{platform}.py # python platform wrapper
- psutil/_psutil_{platform}.c # C platform extension
- psutil/_psutil_{platform}.h # C header file
+ psutil/__init__.py # main psutil namespace ("import psutil")
+ psutil/_ps{platform}.py # platform-specific python wrapper
+ psutil/_psutil_{platform}.c # platform-specific C extension
psutil/tests/test_process|system.py # main test suite
- psutil/tests/test_{platform}.py # platform specific test suite
+ psutil/tests/test_{platform}.py # platform-specific test suite
+
+Adding a new API
+----------------
-Typical process occurring when adding a new functionality (API):
+Typically, this is what you do:
-- define the new function in `psutil/__init__.py`_.
+- define the new API in `psutil/__init__.py`_.
- write the platform specific implementation in ``psutil/_ps{platform}.py``
(e.g. `psutil/_pslinux.py`_).
-- if the change requires C, write the C implementation in
+- if the change requires C code, write the C implementation in
``psutil/_psutil_{platform}.c`` (e.g. `psutil/_psutil_linux.c`_).
- write a generic test in `psutil/tests/test_system.py`_ or
`psutil/tests/test_process.py`_.
-- if possible, write a platform specific test in
+- if possible, write a platform-specific test in
``psutil/tests/test_{platform}.py`` (e.g. `psutil/tests/test_linux.py`_).
- This usually means testing the return value of the new feature against
+ This usually means testing the return value of the new API against
a system CLI tool.
-- update doc in ``doc/index.py``.
-- update ``HISTORY.rst``.
+- update the doc in ``doc/index.py``.
+- update `HISTORY.rst`_ and `CREDITS`_ files.
- make a pull request.
Make a pull request
-===================
+-------------------
- fork psutil (go to https://github.com/giampaolo/psutil and click on "fork")
-- git clone your fork locally: ``git clone git@github.com:YOUR-USERNAME/psutil.git``)
-- create your feature branch:``git checkout -b new-feature``
+- git clone the fork locally: ``git clone git@github.com:YOUR-USERNAME/psutil.git``
+- create a branch:``git checkout -b new-feature``
- commit your changes: ``git commit -am 'add some feature'``
-- push to the branch: ``git push origin new-feature``
-- create a new pull request by via github web interface
-- remember to update `HISTORY.rst`_ and `CREDITS`_ files.
+- push the branch: ``git push origin new-feature``
+- create a new PR via the GitHub web interface and sign-off your work (see
+ `CONTRIBUTING.md`_ guidelines)
Continuous integration
-======================
+----------------------
-All of the services listed below are automatically run on ``git push``.
+Unit tests are automatically run on every ``git push`` on **Linux**, **macOS**,
+**Windows** and **FreeBSD** by using:
-Unit tests
-----------
-
-Tests are automatically run for every GIT push on **Linux**, **macOS** and
-**Windows** by using:
-
-- `Travis`_ (Linux, macOS)
+- `Github Actions`_ (Linux, macOS, Windows)
- `Appveyor`_ (Windows)
-Test files controlling these are `.travis.yml`_ and `appveyor.yml`_.
-Both services run psutil test suite against all supported python version
-(2.6 - 3.6).
-Two icons in the home page (README) always show the build status:
-
-.. image:: https://img.shields.io/travis/giampaolo/psutil/master.svg?maxAge=3600&label=Linux,%20OSX,%20PyPy
- :target: https://travis-ci.org/giampaolo/psutil
- :alt: Linux, macOS and PyPy3 tests (Travis)
+.. image:: https://img.shields.io/github/workflow/status/giampaolo/psutil/CI?label=Linux%2C%20macOS%2C%20FreeBSD
+ :target: https://github.com/giampaolo/psutil/actions?query=workflow%3ACI
.. image:: https://img.shields.io/appveyor/ci/giampaolo/psutil/master.svg?maxAge=3600&label=Windows
:target: https://ci.appveyor.com/project/giampaolo/psutil
- :alt: Windows tests (Appveyor)
-
-.. image:: https://img.shields.io/cirrus/github/giampaolo/psutil?label=FreeBSD
- :target: https://cirrus-ci.com/github/giampaolo/psutil-cirrus-ci
- :alt: FreeBSD tests (Cirrus-CI)
-
-BSD, AIX and Solaris are currently tested manually.
-
-Test coverage
--------------
-
-Test coverage is provided by `coveralls.io`_ and it is controlled via
-`.travis.yml`_.
-An icon in the home page (README) always shows the last coverage percentage:
-.. image:: https://coveralls.io/repos/giampaolo/psutil/badge.svg?branch=master&service=github
- :target: https://coveralls.io/github/giampaolo/psutil?branch=master
- :alt: Test coverage (coverall.io)
+OpenBSD, NetBSD, AIX and Solaris does not have continuos test integration.
Documentation
-=============
+-------------
- doc source code is written in a single file: `/docs/index.rst`_.
-- it uses `RsT syntax`_
- and it's built with `sphinx`_.
- doc can be built with ``make setup-dev-env; cd docs; make html``.
-- public doc is hosted on http://psutil.readthedocs.io/
-
-Releasing a new version
-=======================
-
-These are notes for myself (Giampaolo):
-
-- ``make release``
-- post announce (``make print-announce``) on psutil and python-announce mailing
- lists, twitter, g+, blog.
+- public doc is hosted at https://psutil.readthedocs.io
-
-.. _`.travis.yml`: https://github.com/giampaolo/psutil/blob/master/.travis.ym
-.. _`appveyor.yml`: https://github.com/giampaolo/psutil/blob/master/appveyor.ym
+.. _`appveyor.yml`: https://github.com/giampaolo/psutil/blob/master/appveyor.yml
.. _`Appveyor`: https://ci.appveyor.com/project/giampaolo/psuti
.. _`coveralls.io`: https://coveralls.io/github/giampaolo/psuti
+.. _`CREDITS`: https://github.com/giampaolo/psutil/blob/master/CREDITS
+.. _`CONTRIBUTING.md`: https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md
.. _`doc/index.rst`: https://github.com/giampaolo/psutil/blob/master/doc/index.rst
+.. _`Github Actions`: https://github.com/giampaolo/psutil/actions
.. _`HISTORY.rst`: https://github.com/giampaolo/psutil/blob/master/HISTORY.rst
.. _`make.bat`: https://github.com/giampaolo/psutil/blob/master/make.bat
.. _`Makefile`: https://github.com/giampaolo/psutil/blob/master/Makefile
@@ -188,6 +144,3 @@ These are notes for myself (Giampaolo):
.. _`psutil/tests/test_system.py`: https://github.com/giampaolo/psutil/blob/master/psutil/tests/test_system.py
.. _`RsT syntax`: http://docutils.sourceforge.net/docs/user/rst/quickref.htm
.. _`sphinx`: http://sphinx-doc.org
-.. _`Travis`: https://travis-ci.org/giampaolo/psuti
-.. _`HISTORY.rst`: https://github.com/giampaolo/psutil/blob/master/HISTORY.rst
-.. _`CREDITS`: https://github.com/giampaolo/psutil/blob/master/CREDITS
diff --git a/docs/DEVNOTES b/docs/DEVNOTES
index 7fe14f7d..1748bfda 100644
--- a/docs/DEVNOTES
+++ b/docs/DEVNOTES
@@ -5,51 +5,11 @@ A collection of ideas and notes about stuff to implement in future versions.
"#NNN" occurrences refer to bug tracker issues at:
https://github.com/giampaolo/psutil/issues
-PLATFORMS
-=========
-
-- #355: Android (with patch)
-- #82: Cygwin (PR at #998)
-- #276: GNU/Hurd
-- #693: Windows Nano
-- #1251: Windows bash
-- DragonFlyBSD
-- HP-UX
-
FEATURES
========
-- set process name/title
-
-- #1115: users() idle time.
-
-- #1102: Process.is64bit().
-
-- #371: sensors_temperatures() at least for macOS.
-
-- #669: Windows / net_if_addrs(): return broadcast addr.
-
-- #550: CPU info (frequency, architecture, threads per core, cores per socket,
- sockets, ...)
-
-- #772: extended net_io_counters() metrics.
-
-- #900: wheels for macOS and Linux.
-
-- #922: extended net_io_stats() info.
-
-- #914: extended platform specific process info.
-
-- #898: wifi stats
-
-- #893: (BSD) process environ
-
-- #809: (BSD) per-process resource limits (rlimit()).
-
- (UNIX) process root (different from cwd)
-- #782: (UNIX) process num of signals received.
-
- (Linux) locked files via /proc/locks:
https://www.centos.org/docs/5/html/5.2/Deployment_Guide/s2-proc-locks.html
@@ -58,16 +18,14 @@ FEATURES
Linux: yes
Others: ?
-- Process.threads(): thread names; patch for macOS available at:
- https://code.google.com/p/plcrashreporter/issues/detail?id=65
- Sample code:
- https://github.com/janmojzis/pstree/blob/master/proc_kvm.c
-
- Asynchronous psutil.Popen (see http://bugs.python.org/issue1191964)
- (Windows) fall back on using WMIC for Process methods returning AccessDenied
-- #613: thread names.
+- #613: thread names; patch for macOS available at:
+ https://code.google.com/p/plcrashreporter/issues/detail?id=65
+ Sample code:
+ https://github.com/janmojzis/pstree/blob/master/proc_kvm.c
- scripts/taskmgr-gui.py (using tk).
@@ -139,20 +97,8 @@ FEATURES
NoSuchProcess and AccessDenied? Not that we need it, but currently we
cannot raise a TimeoutExpired exception with a specific error string.
-- process_iter() might grow an "attrs" parameter similar to Process.as_dict()
- invoke the necessary methods and include the results into a "cache"
- attribute attached to the returned Process instances so that one can avoid
- catching NSP and AccessDenied:
- for p in process_iter(attrs=['cpu_percent']):
- print(p.cache['cpu_percent'])
- This also leads questions as whether we should introduce a sorting order.
-
- round Process.memory_percent() result?
-- #550: number of threads per core.
-
-- cpu_percent() and cpu_times_percent() use global vars so are not thread safe.
-
BUGFIXES
========
diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css
index c5c201e4..e88f5307 100644
--- a/docs/_static/css/custom.css
+++ b/docs/_static/css/custom.css
@@ -4,7 +4,7 @@
}
.rst-content dl:not(.docutils) {
- margin: 0px 0px 0px 0px;
+ margin: 0px 0px 0px 0px !important;
}
.data dd {
diff --git a/docs/_static/psutil-logo.png b/docs/_static/psutil-logo.png
new file mode 100644
index 00000000..e1c67233
--- /dev/null
+++ b/docs/_static/psutil-logo.png
Binary files differ
diff --git a/docs/_static/tidelift-logo.png b/docs/_static/tidelift-logo.png
new file mode 100644
index 00000000..9e33757f
--- /dev/null
+++ b/docs/_static/tidelift-logo.png
Binary files differ
diff --git a/docs/conf.py b/docs/conf.py
index b056d20f..f4a32b98 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -267,21 +267,21 @@ htmlhelp_basename = '%s-doc' % PROJECT_NAME
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
- # The paper size ('letterpaper' or 'a4paper').
- #
- # 'papersize': 'letterpaper',
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
- # The font size ('10pt', '11pt' or '12pt').
- #
- # 'pointsize': '10pt',
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
- # Additional stuff for the LaTeX preamble.
- #
- # 'preamble': '',
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
- # Latex figure (float) alignment
- #
- # 'figure_align': 'htbp',
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
diff --git a/docs/index.rst b/docs/index.rst
index 699ea1f1..cc1c8dd1 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -10,9 +10,10 @@ Quick links
- `Home page <https://github.com/giampaolo/psutil>`__
- `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_
-- `Blog <http://grodola.blogspot.com/search/label/psutil>`__
- `Forum <http://groups.google.com/group/psutil/topics>`__
- `Download <https://pypi.org/project/psutil/#files>`__
+- `Blog <https://gmpy.dev/tags/psutil>`__
+- `Contributing <https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md>`__
- `Development guide <https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst>`_
- `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`__
@@ -42,40 +43,55 @@ Supported Python versions are **2.6**, **2.7** and **3.4+**.
The psutil documentation you're reading is distributed as a single HTML page.
+Funding
+=======
-Professional support
---------------------
-
-.. image:: https://nedbatchelder.com/pix/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White_small.png
- :width: 80px
- :align: left
-
-Professional support for psutil is available as part of the `Tidelift Subscription`_.
-Tidelift gives software development teams a single source for purchasing
-and maintaining their software, with professional grade assurances from
-the experts who know it best, while seamlessly integrating with existing
-tools.
-By subscribing you will help me (`Giampaolo Rodola`_) support psutil
-future development. Alternatively consider making a small `donation`_.
-To report a security vulnerability, please use the `Tidelift security
-contact`_. Tidelift will coordinate the fix and disclosure.
-
-Install
--------
-
-Linux Ubuntu / Debian::
+While psutil is free software and will always be, the project would benefit
+immensely from some funding.
+Keeping up with bug reports and maintenance has become hardly sustainable for
+me alone in terms of time.
+If you're a company that's making significant use of psutil you can consider
+becoming a sponsor via `GitHub <https://github.com/sponsors/giampaolo>`__,
+`Open Collective <https://opencollective.com/psutil>`__ or
+`PayPal <https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8>`__
+and have your logo displayed in here and psutil `doc <https://psutil.readthedocs.io>`__.
+
+Sponsors
+--------
+
+.. raw:: html
+
+ <div>
+ <a href="https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme">
+ <img src="https://github.com/giampaolo/psutil/raw/master/docs/_static/tidelift-logo.png" />
+ </a>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <a href="https://sansec.io/">
+ <img src="https://sansec.io/assets/images/logo.svg" />
+ </a>
+ </div>
+
+ <sup><a href="https://github.com/sponsors/giampaolo">add your logo</a></sup>
+
+Supporters
+----------
- sudo apt-get install gcc python3-dev
- sudo pip3 install psutil
+.. raw:: html
-Linux Redhat::
+ <div>
+ <a href="https://github.com/dbwiddis"><img height="40" width="40" title="Daniel Widdis" src="https://avatars1.githubusercontent.com/u/9291703?s=88&amp;v=4" /></a>
+ <a href="https://github.com/aristocratos"><img height="40" width="40" title="aristocratos" src="https://avatars3.githubusercontent.com/u/59659483?s=96&amp;v=4" /></a>
+ <a href="https://github.com/cybersecgeek"><img height="40" width="40" title="cybersecgeek" src="https://avatars.githubusercontent.com/u/12847926?v=4" /></a>
+ </div>
+ <br />
+ <sup><a href="https://github.com/sponsors/giampaolo">add your avatar</a></sup>
- sudo yum install gcc python3-devel
- sudo pip3 install psutil
+Install
+=======
-Windows::
+On Linux, Windows, macOS::
- pip3 install psutil
+ pip install psutil
For other platforms see more detailed
`install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_
@@ -131,6 +147,12 @@ CPU
.. versionchanged:: 4.1.0 added *interrupt* and *dpc* fields on Windows.
+ .. warning::
+ CPU times are always supposed to increase over time, or at least remain
+ the same, and that's because time cannot go backwards.
+ Surprisingly sometimes this might not be the case (at least on Windows
+ and Linux), see `#1210 <https://github.com/giampaolo/psutil/issues/1210#issuecomment-363046156>`__.
+
.. function:: cpu_percent(interval=None, percpu=False)
Return a float representing the current system-wide CPU utilization as a
@@ -186,13 +208,13 @@ CPU
Return the number of logical CPUs in the system (same as `os.cpu_count`_
in Python 3.4) or ``None`` if undetermined.
- *logical* cores means the number of physical cores multiplied by the number
+ "logical CPUs" means the number of physical cores multiplied by the number
of threads that can run on each core (this is known as Hyper Threading).
- If *logical* is ``False`` return the number of physical cores only (Hyper
- Thread CPUs are excluded) or ``None`` if undetermined.
+ If *logical* is ``False`` return the number of physical cores only, or
+ ``None`` if undetermined.
On OpenBSD and NetBSD ``psutil.cpu_count(logical=False)`` always return
``None``.
- Example on a system having 2 physical hyper-thread CPU cores:
+ Example on a system having 2 cores + Hyper Threading:
>>> import psutil
>>> psutil.cpu_count()
@@ -200,11 +222,11 @@ CPU
>>> psutil.cpu_count(logical=False)
2
- Note that this number is not equivalent to the number of CPUs the current
- process can actually use.
+ Note that ``psutil.cpu_count()`` may not necessarily be equivalent to the
+ actual number of CPUs the current process can use.
That can vary in case process CPU affinity has been changed, Linux cgroups
- are being used or on Windows systems using processor groups or having more
- than 64 CPUs.
+ are being used or (in case of Windows) on systems using processor groups or
+ having more than 64 CPUs.
The number of usable CPUs can be obtained with:
>>> len(psutil.Process().cpu_affinity())
@@ -268,16 +290,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
@@ -391,19 +414,27 @@ Disks
(e.g. pseudo, memory, duplicate, inaccessible filesystems).
Note that this may not be fully reliable on all systems (e.g. on BSD this
parameter is ignored).
- Named tuple's **fstype** field is a string which varies depending on the
- platform.
- On Linux it can be one of the values found in /proc/filesystems (e.g.
- ``'ext3'`` for an ext3 hard drive o ``'iso9660'`` for the CD-ROM drive).
- On Windows it is determined via `GetDriveType`_ and can be either
- ``"removable"``, ``"fixed"``, ``"remote"``, ``"cdrom"``, ``"unmounted"`` or
- ``"ramdisk"``. On macOS and BSD it is retrieved via `getfsstat`_ syscall.
See `disk_usage.py`_ script providing an example usage.
+ Returns a list of namedtuples with the following fields:
+
+ * **device**: the device path (e.g. ``"/dev/hda1"``). On Windows this is the
+ drive letter (e.g. ``"C:\\"``).
+ * **mountpoint**: the mount point path (e.g. ``"/"``). On Windows this is the
+ drive letter (e.g. ``"C:\\"``).
+ * **fstype**: the partition filesystem (e.g. ``"ext3"`` on UNIX or ``"NTFS"``
+ on Windows).
+ * **opts**: a comma-separated string indicating different mount options for
+ the drive/partition. Platform-dependent.
+ * **maxfile**: the maximum length a file name can have.
+ * **maxpath**: the maximum length a path name (directory name + base file
+ name) can have.
- >>> import psutil
- >>> psutil.disk_partitions()
- [sdiskpart(device='/dev/sda3', mountpoint='/', fstype='ext4', opts='rw,errors=remount-ro'),
- sdiskpart(device='/dev/sda7', mountpoint='/home', fstype='ext4', opts='rw')]
+ >>> import psutil
+ >>> psutil.disk_partitions()
+ [sdiskpart(device='/dev/sda3', mountpoint='/', fstype='ext4', opts='rw,errors=remount-ro', maxfile=255, maxpath=4096),
+ sdiskpart(device='/dev/sda7', mountpoint='/home', fstype='ext4', opts='rw', maxfile=255, maxpath=4096)]
+
+ .. versionchanged:: 5.7.4 added *maxfile* and *maxpath* fields
.. function:: disk_usage(path)
@@ -686,7 +717,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 +737,7 @@ Network
.. versionadded:: 3.0.0
+ .. versionchanged:: 5.7.3 `isup` on UNIX also checks whether the NIC is running.
Sensors
-------
@@ -741,7 +774,7 @@ Sensors
Return hardware fans speed. Each entry is a named tuple representing a
certain hardware sensor fan.
- Fan speed is expressed in RPM (rounds per minute).
+ Fan speed is expressed in RPM (revolutions per minute).
If sensors are not supported by the OS an empty dict is returned.
Example::
@@ -819,7 +852,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,16 +1176,15 @@ 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()
The process creation time as a floating point number expressed in seconds
- since the epoch, in UTC. The return value is cached after first call.
+ since the epoch. The return value is cached after first call.
>>> import psutil, datetime
>>> p = psutil.Process()
@@ -1328,16 +1360,17 @@ Process class
>>> import psutil
>>> p = psutil.Process()
- >>> # process may open no more than 128 file descriptors
- >>> p.rlimit(psutil.RLIMIT_NOFILE, (128, 128))
- >>> # process may create files no bigger than 1024 bytes
- >>> p.rlimit(psutil.RLIMIT_FSIZE, (1024, 1024))
- >>> # get
- >>> p.rlimit(psutil.RLIMIT_FSIZE)
+ >>> p.rlimit(psutil.RLIMIT_NOFILE, (128, 128)) # process can open max 128 file descriptors
+ >>> p.rlimit(psutil.RLIMIT_FSIZE, (1024, 1024)) # can create files no bigger than 1024 bytes
+ >>> p.rlimit(psutil.RLIMIT_FSIZE) # get
(1024, 1024)
>>>
- Availability: Linux
+ Also see `procinfo.py`_ script.
+
+ Availability: Linux, FreeBSD
+
+ .. versionchanged:: 5.7.3 added FreeBSD support
.. method:: io_counters()
@@ -1424,7 +1457,7 @@ Process class
- **system**: time spent in kernel mode.
- **children_user**: user time of all child processes (always ``0`` on
Windows and macOS).
- - **system_user**: user time of all child processes (always ``0`` on
+ - **children_system**: system time of all child processes (always ``0`` on
Windows and macOS).
- **iowait**: (Linux) time spent waiting for blocking I/O to complete.
This value is excluded from `user` and `system` times count (because the
@@ -1626,7 +1659,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.
@@ -2227,29 +2260,43 @@ Process priority constants
Process resources constants
---------------------------
-.. data:: RLIM_INFINITY
-.. data:: RLIMIT_AS
-.. data:: RLIMIT_CORE
-.. data:: RLIMIT_CPU
-.. data:: RLIMIT_DATA
-.. data:: RLIMIT_FSIZE
-.. data:: RLIMIT_LOCKS
-.. data:: RLIMIT_MEMLOCK
-.. data:: RLIMIT_MSGQUEUE
-.. data:: RLIMIT_NICE
-.. data:: RLIMIT_NOFILE
-.. data:: RLIMIT_NPROC
-.. data:: RLIMIT_RSS
-.. data:: RLIMIT_RTPRIO
-.. data:: RLIMIT_RTTIME
-.. data:: RLIMIT_SIGPENDING
-.. data:: RLIMIT_STACK
-
- Constants used for getting and setting process resource limits to be used in
- conjunction with :meth:`psutil.Process.rlimit()`. See `man prlimit`_ for
- further information.
+Linux / FreeBSD:
- Availability: Linux
+ .. data:: RLIM_INFINITY
+ .. data:: RLIMIT_AS
+ .. data:: RLIMIT_CORE
+ .. data:: RLIMIT_CPU
+ .. data:: RLIMIT_DATA
+ .. data:: RLIMIT_FSIZE
+ .. data:: RLIMIT_MEMLOCK
+ .. data:: RLIMIT_NOFILE
+ .. data:: RLIMIT_NPROC
+ .. data:: RLIMIT_RSS
+ .. data:: RLIMIT_STACK
+
+Linux specific:
+
+ .. data:: RLIMIT_LOCKS
+ .. data:: RLIMIT_MSGQUEUE
+ .. data:: RLIMIT_NICE
+ .. data:: RLIMIT_RTPRIO
+ .. data:: RLIMIT_RTTIME
+ .. data:: RLIMIT_SIGPENDING
+
+FreeBSD specific:
+
+ .. data:: RLIMIT_SWAP
+ .. data:: RLIMIT_SBSIZE
+ .. data:: RLIMIT_NPTS
+
+Constants used for getting and setting process resource limits to be used in
+conjunction with :meth:`psutil.Process.rlimit()`. See `resource.getrlimit`_
+for further information.
+
+Availability: Linux, FreeBSD
+
+.. versionchanged:: 5.7.3 added FreeBSD support, added ``RLIMIT_SWAP``,
+ ``RLIMIT_SBSIZE``, ``RLIMIT_NPTS``.
Connections constants
---------------------
@@ -2369,7 +2416,7 @@ Kill process tree
timeout=None, on_terminate=None):
"""Kill a process tree (including grandchildren) with signal
"sig" and return a (gone, still_alive) tuple.
- "on_terminate", if specified, is a callabck function which is
+ "on_terminate", if specified, is a callback function which is
called as soon as a child terminates.
"""
assert pid != os.getpid(), "won't kill myself"
@@ -2378,7 +2425,10 @@ Kill process tree
if include_parent:
children.append(parent)
for p in children:
- p.send_signal(sig)
+ try:
+ p.send_signal(sig)
+ except psutil.NoSuchProcess:
+ pass
gone, alive = psutil.wait_procs(children, timeout=timeout,
callback=on_terminate)
return (gone, alive)
@@ -2472,10 +2522,12 @@ FAQs
Unfortunately there's not much you can do about this except running the
Python process with higher privileges.
On Unix you may run the Python process as root or use the SUID bit
- (this is the trick used by tools such as ``ps`` and ``netstat``).
+ (``ps`` and ``netstat`` does this).
On Windows you may run the Python process as NT AUTHORITY\\SYSTEM or install
- the Python script as a Windows service (this is the trick used by tools
- such as ProcessHacker).
+ the Python script as a Windows service (ProcessHacker does this).
+
+* Q: is MinGW supported on Windows?
+* A: no, you should Visual Studio (see `development guide`_).
Running tests
=============
@@ -2484,6 +2536,12 @@ Running tests
$ python3 -m psutil.tests
+Security
+========
+
+To report a security vulnerability, please use the `Tidelift security
+contact`_. Tidelift will coordinate the fix and disclosure.
+
Development guide
=================
@@ -2492,8 +2550,10 @@ If you want to develop psutil take a look at the `development guide`_.
Platforms support history
=========================
+* psutil 5.8.0 (2020-12): **PyPy 2** on Windows
+* 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.7.0 (2020-02): **PyPy 3** on Windows
* psutil 5.4.0 (2017-11): **AIX**
* psutil 3.4.1 (2016-01): **NetBSD**
* psutil 3.3.0 (2015-11): **OpenBSD**
@@ -2506,6 +2566,22 @@ Supported Python versions are 2.6, 2.7, 3.4+ and PyPy3.
Timeline
========
+- 2020-12-19:
+ `5.8.0 <https://pypi.org/project/psutil/5.8.0/#files>`__ -
+ `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#580>`__ -
+ `diff <https://github.com/giampaolo/psutil/compare/release-5.7.3...release-5.8.0#files_bucket>`__
+- 2020-10-23:
+ `5.7.3 <https://pypi.org/project/psutil/5.7.3/#files>`__ -
+ `what's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst#573>`__ -
+ `diff <https://github.com/giampaolo/psutil/compare/release-5.7.2...release-5.7.3#files_bucket>`__
+- 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#572>`__ -
+ `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>`__ -
@@ -2831,14 +2907,13 @@ Timeline
.. _`cpu_distribution.py`: https://github.com/giampaolo/psutil/blob/master/scripts/cpu_distribution.py
.. _`development guide`: https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst
.. _`disk_usage.py`: https://github.com/giampaolo/psutil/blob/master/scripts/disk_usage.py
-.. _`donation`: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8
.. _`enum`: https://docs.python.org/3/library/enum.html#module-enum
.. _`fans.py`: https://github.com/giampaolo/psutil/blob/master/scripts/fans.py
.. _`GetDriveType`: https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getdrivetypea
+.. _`GetExitCodeProcess`: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
.. _`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
@@ -2865,6 +2940,7 @@ Timeline
.. _`os.times`: https://docs.python.org//library/os.html#os.times
.. _`pmap.py`: https://github.com/giampaolo/psutil/blob/master/scripts/pmap.py
.. _`PROCESS_MEMORY_COUNTERS_EX`: https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-_process_memory_counters_ex
+.. _`procinfo.py`: https://github.com/giampaolo/psutil/blob/master/scripts/procinfo.py
.. _`procsmem.py`: https://github.com/giampaolo/psutil/blob/master/scripts/procsmem.py
.. _`resource.getrlimit`: https://docs.python.org/3/library/resource.html#resource.getrlimit
.. _`resource.setrlimit`: https://docs.python.org/3/library/resource.html#resource.setrlimit
@@ -2877,9 +2953,10 @@ Timeline
.. _`SOCK_SEQPACKET`: https://docs.python.org/3/library/socket.html#socket.SOCK_SEQPACKET
.. _`SOCK_STREAM`: https://docs.python.org/3/library/socket.html#socket.SOCK_STREAM
.. _`socket.fromfd`: https://docs.python.org/3/library/socket.html#socket.fromfd
-.. _`subprocess.Popen`: https://docs.python.org/3/library/subprocess.html#subprocess.Popen
.. _`subprocess.Popen.wait`: https://docs.python.org/3/library/subprocess.html#subprocess.Popen.wait
+.. _`subprocess.Popen`: https://docs.python.org/3/library/subprocess.html#subprocess.Popen
.. _`temperatures.py`: https://github.com/giampaolo/psutil/blob/master/scripts/temperatures.py
.. _`TerminateProcess`: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-terminateprocess
.. _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
+.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme
diff --git a/psutil/__init__.py b/psutil/__init__.py
index bf669801..5f9fa65e 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -47,6 +47,7 @@ from ._common import ZombieProcess
from ._compat import long
from ._compat import PermissionError
from ._compat import ProcessLookupError
+from ._compat import SubprocessTimeoutExpired as _SubprocessTimeoutExpired
from ._compat import PY3 as _PY3
from ._common import CONN_CLOSE
@@ -102,44 +103,6 @@ if LINUX:
from ._pslinux import IOPRIO_CLASS_IDLE # NOQA
from ._pslinux import IOPRIO_CLASS_NONE # NOQA
from ._pslinux import IOPRIO_CLASS_RT # NOQA
- # Linux >= 2.6.36
- if _psplatform.HAS_PRLIMIT:
- from ._psutil_linux import RLIM_INFINITY # NOQA
- from ._psutil_linux import RLIMIT_AS # NOQA
- from ._psutil_linux import RLIMIT_CORE # NOQA
- from ._psutil_linux import RLIMIT_CPU # NOQA
- from ._psutil_linux import RLIMIT_DATA # NOQA
- from ._psutil_linux import RLIMIT_FSIZE # NOQA
- from ._psutil_linux import RLIMIT_LOCKS # NOQA
- from ._psutil_linux import RLIMIT_MEMLOCK # NOQA
- from ._psutil_linux import RLIMIT_NOFILE # NOQA
- from ._psutil_linux import RLIMIT_NPROC # NOQA
- from ._psutil_linux import RLIMIT_RSS # NOQA
- from ._psutil_linux import RLIMIT_STACK # NOQA
- # Kinda ugly but considerably faster than using hasattr() and
- # setattr() against the module object (we are at import time:
- # speed matters).
- from . import _psutil_linux
- try:
- RLIMIT_MSGQUEUE = _psutil_linux.RLIMIT_MSGQUEUE
- except AttributeError:
- pass
- try:
- RLIMIT_NICE = _psutil_linux.RLIMIT_NICE
- except AttributeError:
- pass
- try:
- RLIMIT_RTPRIO = _psutil_linux.RLIMIT_RTPRIO
- except AttributeError:
- pass
- try:
- RLIMIT_RTTIME = _psutil_linux.RLIMIT_RTTIME
- except AttributeError:
- pass
- try:
- RLIMIT_SIGPENDING = _psutil_linux.RLIMIT_SIGPENDING
- except AttributeError:
- pass
elif WINDOWS:
from . import _pswindows as _psplatform
@@ -197,6 +160,7 @@ __all__ = [
"CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
"CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
"CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE",
+ # "CONN_IDLE", "CONN_BOUND",
"AF_LINK",
@@ -207,6 +171,11 @@ __all__ = [
"BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "MACOS", "OSX", "POSIX",
"SUNOS", "WINDOWS", "AIX",
+ # "RLIM_INFINITY", "RLIMIT_AS", "RLIMIT_CORE", "RLIMIT_CPU", "RLIMIT_DATA",
+ # "RLIMIT_FSIZE", "RLIMIT_LOCKS", "RLIMIT_MEMLOCK", "RLIMIT_NOFILE",
+ # "RLIMIT_NPROC", "RLIMIT_RSS", "RLIMIT_STACK", "RLIMIT_MSGQUEUE",
+ # "RLIMIT_NICE", "RLIMIT_RTPRIO", "RLIMIT_RTTIME", "RLIMIT_SIGPENDING",
+
# classes
"Process", "Popen",
@@ -222,11 +191,26 @@ __all__ = [
"users", "boot_time", # others
]
-AF_LINK = _psplatform.AF_LINK
__all__.extend(_psplatform.__extra__all__)
+
+# Linux, FreeBSD
+if hasattr(_psplatform.Process, "rlimit"):
+ # Populate global namespace with RLIM* constants.
+ from . import _psutil_posix
+
+ _globals = globals()
+ _name = None
+ for _name in dir(_psutil_posix):
+ if _name.startswith('RLIM') and _name.isupper():
+ _globals[_name] = getattr(_psutil_posix, _name)
+ __all__.append(_name)
+ del _globals, _name
+
+AF_LINK = _psplatform.AF_LINK
+
__author__ = "Giampaolo Rodola'"
-__version__ = "5.7.1"
+__version__ = "5.8.1"
version_info = tuple([int(num) for num in __version__.split('.')])
_timer = getattr(time, 'monotonic', time.time)
@@ -264,7 +248,7 @@ if (int(__version__.replace('.', '')) !=
if hasattr(_psplatform, 'ppid_map'):
# Faster version (Windows and Linux).
_ppid_map = _psplatform.ppid_map
-else:
+else: # pragma: no cover
def _ppid_map():
"""Return a {pid: ppid, ...} dict for all running processes in
one shot. Used to speed up Process.children().
@@ -393,7 +377,7 @@ class Process(object):
def __str__(self):
try:
info = collections.OrderedDict()
- except AttributeError:
+ except AttributeError: # pragma: no cover
info = {} # Python 2.6
info["pid"] = self.pid
if self._name:
@@ -720,7 +704,7 @@ class Process(object):
def create_time(self):
"""The process creation time as a floating point number
- expressed in seconds since the epoch, in UTC.
+ expressed in seconds since the epoch.
The return value is cached after first call.
"""
if self._create_time is None:
@@ -801,7 +785,7 @@ class Process(object):
else:
return self._proc.ionice_set(ioclass, value)
- # Linux only
+ # Linux / FreeBSD only
if hasattr(_psplatform.Process, "rlimit"):
def rlimit(self, resource, limits=None):
@@ -809,15 +793,12 @@ class Process(object):
tuple.
*resource* is one of the RLIMIT_* constants.
- *limits* is supposed to be a (soft, hard) tuple.
+ *limits* is supposed to be a (soft, hard) tuple.
See "man prlimit" for further info.
- Available on Linux only.
+ Available on Linux and FreeBSD only.
"""
- if limits is None:
- return self._proc.rlimit(resource)
- else:
- return self._proc.rlimit(resource, limits)
+ return self._proc.rlimit(resource, limits)
# Windows, Linux and FreeBSD only
if hasattr(_psplatform.Process, "cpu_affinity_get"):
@@ -831,7 +812,7 @@ class Process(object):
(Windows, Linux and BSD only).
"""
if cpus is None:
- return list(set(self._proc.cpu_affinity_get()))
+ return sorted(set(self._proc.cpu_affinity_get()))
else:
if not cpus:
if hasattr(self._proc, "_get_eligible_cpus"):
@@ -853,7 +834,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):
@@ -1525,6 +1506,8 @@ def wait_procs(procs, timeout=None, callback=None):
returncode = proc.wait(timeout=timeout)
except TimeoutExpired:
pass
+ except _SubprocessTimeoutExpired:
+ pass
else:
if returncode is not None or not proc.is_running():
# Set new Process instance attribute.
@@ -1595,7 +1578,7 @@ def cpu_count(logical=True):
if logical:
ret = _psplatform.cpu_count_logical()
else:
- ret = _psplatform.cpu_count_physical()
+ ret = _psplatform.cpu_count_cores()
if ret is not None and ret < 1:
ret = None
return ret
@@ -1741,7 +1724,6 @@ def cpu_percent(interval=None, percpu=False):
def calculate(t1, t2):
times_delta = _cpu_times_deltas(t1, t2)
-
all_delta = _cpu_tot_time(times_delta)
busy_delta = _cpu_busy_time(times_delta)
@@ -2036,7 +2018,23 @@ def disk_partitions(all=False):
If *all* parameter is False return physical devices only and ignore
all others.
"""
- return _psplatform.disk_partitions(all)
+ def pathconf(path, name):
+ try:
+ return os.pathconf(path, name)
+ except (OSError, AttributeError):
+ pass
+
+ ret = _psplatform.disk_partitions(all)
+ if POSIX:
+ new = []
+ for item in ret:
+ nt = item._replace(
+ maxfile=pathconf(item.mountpoint, 'PC_NAME_MAX'),
+ maxpath=pathconf(item.mountpoint, 'PC_PATH_MAX'))
+ new.append(nt)
+ return new
+ else:
+ return ret
def disk_io_counters(perdisk=False, nowrap=True):
diff --git a/psutil/_common.py b/psutil/_common.py
index b97bb01d..771461d6 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -178,7 +178,8 @@ sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
'read_bytes', 'write_bytes',
'read_time', 'write_time'])
# psutil.disk_partitions()
-sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts'])
+sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts',
+ 'maxfile', 'maxpath'])
# psutil.net_io_counters()
snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv',
'packets_sent', 'packets_recv',
@@ -303,9 +304,6 @@ class NoSuchProcess(Error):
details = "(pid=%s)" % self.pid
self.msg = "process no longer exists " + details
- def __path__(self):
- return 'xxx'
-
class ZombieProcess(NoSuchProcess):
"""Exception raised when querying a zombie process. This is
@@ -766,7 +764,7 @@ else:
@memoize
-def term_supports_colors(file=sys.stdout):
+def term_supports_colors(file=sys.stdout): # pragma: no cover
if os.name == 'nt':
return True
try:
@@ -780,12 +778,13 @@ def term_supports_colors(file=sys.stdout):
return True
-def hilite(s, color="green", bold=False):
+def hilite(s, color=None, bold=False): # pragma: no cover
"""Return an highlighted version of 'string'."""
if not term_supports_colors():
return s
attr = []
- colors = dict(green='32', red='91', brown='33', yellow='93')
+ colors = dict(green='32', red='91', brown='33', yellow='93', blue='34',
+ violet='35', lightblue='36', grey='37', darkgrey='30')
colors[None] = '29'
try:
color = colors[color]
@@ -798,7 +797,8 @@ def hilite(s, color="green", bold=False):
return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
-def print_color(s, color="green", bold=False, file=sys.stdout):
+def print_color(
+ s, color=None, bold=False, file=sys.stdout): # pragma: no cover
"""Print a colorized version of string."""
if not term_supports_colors():
print(s, file=file) # NOQA
diff --git a/psutil/_compat.py b/psutil/_compat.py
index 145fb71d..90938687 100644
--- a/psutil/_compat.py
+++ b/psutil/_compat.py
@@ -8,7 +8,6 @@ Python 3 way of doing things).
"""
import collections
-import contextlib
import errno
import functools
import os
@@ -23,7 +22,7 @@ __all__ = [
# literals
"u", "b",
# collections module
- "lru_cache", "redirect_stderr",
+ "lru_cache",
# shutil module
"which", "get_terminal_size",
# python 3 exceptions
@@ -425,15 +424,9 @@ except ImportError:
return fallback
-# python 3.4
+# python 3.3
try:
- from contextlib import redirect_stderr
+ from subprocess import TimeoutExpired as SubprocessTimeoutExpired
except ImportError:
- @contextlib.contextmanager
- def redirect_stderr(target):
- original = sys.stderr
- try:
- sys.stderr = target
- yield
- finally:
- sys.stderr = original
+ class SubprocessTimeoutExpired:
+ pass
diff --git a/psutil/_psaix.py b/psutil/_psaix.py
index 994366aa..3e3a3d14 100644
--- a/psutil/_psaix.py
+++ b/psutil/_psaix.py
@@ -46,7 +46,7 @@ HAS_THREADS = hasattr(cext, "proc_threads")
HAS_NET_IO_COUNTERS = hasattr(cext, "net_io_counters")
HAS_PROC_IO_COUNTERS = hasattr(cext, "proc_io_counters")
-PAGE_SIZE = os.sysconf('SC_PAGE_SIZE')
+PAGE_SIZE = cext_posix.getpagesize()
AF_LINK = cext_posix.AF_LINK
PROC_STATUSES = {
@@ -143,7 +143,7 @@ def cpu_count_logical():
return None
-def cpu_count_physical():
+def cpu_count_cores():
cmd = "lsdev -Cc processor"
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@@ -189,7 +189,9 @@ def disk_partitions(all=False):
# filter by filesystem having a total size > 0.
if not disk_usage(mountpoint).total:
continue
- ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+ maxfile = maxpath = None # set later
+ ntuple = _common.sdiskpart(device, mountpoint, fstype, opts,
+ maxfile, maxpath)
retlist.append(ntuple)
return retlist
diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py
index 49ad1e99..bdcfc1e6 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 = {
@@ -99,10 +98,7 @@ TCP_STATUSES = {
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
}
-if NETBSD:
- PAGESIZE = os.sysconf("SC_PAGESIZE")
-else:
- PAGESIZE = os.sysconf("SC_PAGE_SIZE")
+PAGESIZE = cext_posix.getpagesize()
AF_LINK = cext_posix.AF_LINK
HAS_PER_CPU_TIMES = hasattr(cext, "per_cpu_times")
@@ -253,19 +249,19 @@ def cpu_count_logical():
if OPENBSD or NETBSD:
- def cpu_count_physical():
+ def cpu_count_cores():
# OpenBSD and NetBSD do not implement this.
return 1 if cpu_count_logical() == 1 else None
else:
- def cpu_count_physical():
- """Return the number of physical CPUs in the system."""
+ def cpu_count_cores():
+ """Return the number of CPU cores in the system."""
# From the C module we'll get an XML string similar to this:
# http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html
# We may get None in case "sysctl kern.sched.topology_spec"
# is not supported on this BSD version, in which case we'll mimic
# os.cpu_count() and return None.
ret = None
- s = cext.cpu_count_phys()
+ s = cext.cpu_topology()
if s is not None:
# get rid of padding chars appended at the end of the string
index = s.rfind("</groups>")
@@ -278,8 +274,7 @@ else:
# needed otherwise it will memleak
root.clear()
if not ret:
- # If logical CPUs are 1 it's obvious we'll have only 1
- # physical CPU.
+ # If logical CPUs == 1 it's obvious we' have only 1 core.
if cpu_count_logical() == 1:
return 1
return ret
@@ -329,7 +324,9 @@ def disk_partitions(all=False):
partitions = cext.disk_partitions()
for partition in partitions:
device, mountpoint, fstype, opts = partition
- ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+ maxfile = maxpath = None # set later
+ ntuple = _common.sdiskpart(device, mountpoint, fstype, opts,
+ maxfile, maxpath)
retlist.append(ntuple)
return retlist
@@ -354,7 +351,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 +548,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 +573,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 +667,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()
@@ -901,3 +902,15 @@ class Process(object):
@wrap_exceptions
def memory_maps(self):
return cext.proc_memory_maps(self.pid)
+
+ @wrap_exceptions
+ def rlimit(self, resource, limits=None):
+ if limits is None:
+ return cext.proc_getrlimit(self.pid, resource)
+ else:
+ if len(limits) != 2:
+ raise ValueError(
+ "second argument must be a (soft, hard) tuple, "
+ "got %s" % repr(limits))
+ soft, hard = limits
+ return cext.proc_setrlimit(self.pid, resource, soft, hard)
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 07090a16..f032ee0d 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -75,20 +75,13 @@ __extra__all__ = [
POWER_SUPPLY_PATH = "/sys/class/power_supply"
HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
-HAS_PRLIMIT = hasattr(cext, "linux_prlimit")
HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get")
HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get")
_DEFAULT = object()
-# RLIMIT_* constants, not guaranteed to be present on all kernels
-if HAS_PRLIMIT:
- for name in dir(cext):
- if name.startswith('RLIM'):
- __extra__all__.append(name)
-
# Number of clock ticks per second
CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
-PAGESIZE = os.sysconf("SC_PAGE_SIZE")
+PAGESIZE = cext_posix.getpagesize()
BOOT_TIME = None # set later
# Used when reading "big" files, namely /proc/{pid}/smaps and /proc/net/*.
# On Python 2, using a buffer with open() for such files may result in a
@@ -321,13 +314,61 @@ def cat(fname, fallback=_DEFAULT, binary=True):
try:
set_scputimes_ntuple("/proc")
-except Exception:
+except Exception: # pragma: no cover
# Don't want to crash at import time.
traceback.print_exc()
scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0)
# =====================================================================
+# --- prlimit
+# =====================================================================
+
+# Backport of resource.prlimit() for Python 2. Originally this was done
+# in C, but CentOS-6 which we use to create manylinux wheels is too old
+# and does not support prlimit() syscall. As such the resulting wheel
+# would not include prlimit(), even when installed on newer systems.
+# This is the only part of psutil using ctypes.
+
+prlimit = None
+try:
+ from resource import prlimit # python >= 3.4
+except ImportError:
+ import ctypes
+
+ libc = ctypes.CDLL(None, use_errno=True)
+
+ if hasattr(libc, "prlimit"):
+
+ def prlimit(pid, resource_, limits=None):
+ class StructRlimit(ctypes.Structure):
+ _fields_ = [('rlim_cur', ctypes.c_longlong),
+ ('rlim_max', ctypes.c_longlong)]
+
+ current = StructRlimit()
+ if limits is None:
+ # get
+ ret = libc.prlimit(pid, resource_, None, ctypes.byref(current))
+ else:
+ # set
+ new = StructRlimit()
+ new.rlim_cur = limits[0]
+ new.rlim_max = limits[1]
+ ret = libc.prlimit(
+ pid, resource_, ctypes.byref(new), ctypes.byref(current))
+
+ if ret != 0:
+ errno = ctypes.get_errno()
+ raise OSError(errno, os.strerror(errno))
+ return (current.rlim_cur, current.rlim_max)
+
+
+if prlimit is not None:
+ __extra__all__.extend(
+ [x for x in dir(cext) if x.startswith('RLIM') and x.isupper()])
+
+
+# =====================================================================
# --- system memory
# =====================================================================
@@ -633,15 +674,21 @@ def cpu_count_logical():
return num
-def cpu_count_physical():
- """Return the number of physical cores in the system."""
+def cpu_count_cores():
+ """Return the number of CPU cores in the system."""
# Method #1
- core_ids = set()
- for path in glob.glob(
- "/sys/devices/system/cpu/cpu[0-9]*/topology/core_id"):
+ ls = set()
+ # These 2 files are the same but */core_cpus_list is newer while
+ # */thread_siblings_list is deprecated and may disappear in the future.
+ # https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
+ # https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964
+ # https://lkml.org/lkml/2019/2/26/41
+ p1 = "/sys/devices/system/cpu/cpu[0-9]*/topology/core_cpus_list"
+ p2 = "/sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"
+ for path in glob.glob(p1) or glob.glob(p2):
with open_binary(path) as f:
- core_ids.add(int(f.read()))
- result = len(core_ids)
+ ls.add(f.read().strip())
+ result = len(ls)
if result != 0:
return result
@@ -653,15 +700,15 @@ def cpu_count_physical():
line = line.strip().lower()
if not line:
# new section
- if (b'physical id' in current_info and
- b'cpu cores' in current_info):
+ try:
mapping[current_info[b'physical id']] = \
current_info[b'cpu cores']
+ except KeyError:
+ pass
current_info = {}
else:
# ongoing section
- if (line.startswith(b'physical id') or
- line.startswith(b'cpu cores')):
+ if line.startswith((b'physical id', b'cpu cores')):
key, value = line.split(b'\t:', 1)
current_info[key] = int(value)
@@ -690,6 +737,17 @@ def cpu_stats():
ctx_switches, interrupts, soft_interrupts, syscalls)
+def _cpu_get_cpuinfo_freq():
+ """Return current CPU frequency from cpuinfo if available.
+ """
+ ret = []
+ with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
+ for line in f:
+ if line.lower().startswith(b'cpu mhz'):
+ ret.append(float(line.split(b':', 1)[1]))
+ return ret
+
+
if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or \
os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq"):
def cpu_freq():
@@ -697,20 +755,19 @@ if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or \
Contrarily to other OSes, Linux updates these values in
real-time.
"""
- def get_path(num):
- for p in ("/sys/devices/system/cpu/cpufreq/policy%s" % num,
- "/sys/devices/system/cpu/cpu%s/cpufreq" % num):
- if os.path.exists(p):
- return p
-
+ cpuinfo_freqs = _cpu_get_cpuinfo_freq()
+ paths = sorted(
+ glob.glob("/sys/devices/system/cpu/cpufreq/policy[0-9]*") or
+ glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq"))
ret = []
- for n in range(cpu_count_logical()):
- path = get_path(n)
- if not path:
- continue
-
- pjoin = os.path.join
- curr = cat(pjoin(path, "scaling_cur_freq"), fallback=None)
+ pjoin = os.path.join
+ for i, path in enumerate(paths):
+ if len(paths) == len(cpuinfo_freqs):
+ # take cached value from cpuinfo if available, see:
+ # https://github.com/giampaolo/psutil/issues/1851
+ curr = cpuinfo_freqs[i]
+ else:
+ curr = cat(pjoin(path, "scaling_cur_freq"), fallback=None)
if curr is None:
# Likely an old RedHat, see:
# https://github.com/giampaolo/psutil/issues/1071
@@ -724,24 +781,12 @@ if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or \
ret.append(_common.scpufreq(curr, min_, max_))
return ret
-elif os.path.exists("/proc/cpuinfo"):
+else:
def cpu_freq():
"""Alternate implementation using /proc/cpuinfo.
min and max frequencies are not available and are set to None.
"""
- ret = []
- with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
- for line in f:
- if line.lower().startswith(b'cpu mhz'):
- key, value = line.split(b':', 1)
- ret.append(_common.scpufreq(float(value), 0., 0.))
- return ret
-
-else:
- def cpu_freq():
- """Dummy implementation when none of the above files are present.
- """
- return []
+ return [_common.scpufreq(x, 0., 0.) for x in _cpu_get_cpuinfo_freq()]
# =====================================================================
@@ -861,10 +906,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
@@ -1049,7 +1090,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
@@ -1193,7 +1234,9 @@ def disk_partitions(all=False):
if not all:
if device == '' or fstype not in fstypes:
continue
- ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+ maxfile = maxpath = None # set later
+ ntuple = _common.sdiskpart(device, mountpoint, fstype, opts,
+ maxfile, maxpath)
retlist.append(ntuple)
return retlist
@@ -1223,10 +1266,20 @@ def sensors_temperatures():
# https://github.com/giampaolo/psutil/issues/971
# https://github.com/nicolargo/glances/issues/1060
basenames.extend(glob.glob('/sys/class/hwmon/hwmon*/device/temp*_*'))
- basenames.extend(glob.glob(
- '/sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_*'))
basenames = sorted(set([x.split('_')[0] for x in basenames]))
+ # Only add the coretemp hwmon entries if they're not already in
+ # /sys/class/hwmon/
+ # https://github.com/giampaolo/psutil/issues/1708
+ # https://github.com/giampaolo/psutil/pull/1648
+ basenames2 = glob.glob(
+ '/sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_*')
+ repl = re.compile('/sys/devices/platform/coretemp.*/hwmon/')
+ for name in basenames2:
+ altname = repl.sub('/sys/class/hwmon/', name)
+ if altname not in basenames:
+ basenames.append(name)
+
for base in basenames:
try:
path = base + '_input'
@@ -1358,7 +1411,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
@@ -1376,12 +1430,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:
@@ -1413,11 +1466,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)
@@ -1746,7 +1805,7 @@ class Process(object):
# According to documentation, starttime is in field 21 and the
# unit is jiffies (clock ticks).
# We first divide it for clock ticks and then add uptime returning
- # seconds since the epoch, in UTC.
+ # seconds since the epoch.
# Also use cached value if available.
bt = BOOT_TIME or boot_time()
return (ctime / CLOCK_TICKS) + bt
@@ -2004,10 +2063,10 @@ class Process(object):
raise ValueError("value not in 0-7 range")
return cext.proc_ioprio_set(self.pid, ioclass, value)
- if HAS_PRLIMIT:
+ if prlimit is not None:
@wrap_exceptions
- def rlimit(self, resource, limits=None):
+ def rlimit(self, resource_, limits=None):
# If pid is 0 prlimit() applies to the calling process and
# we don't want that. We should never get here though as
# PID 0 is not supported on Linux.
@@ -2016,15 +2075,14 @@ class Process(object):
try:
if limits is None:
# get
- return cext.linux_prlimit(self.pid, resource)
+ return prlimit(self.pid, resource_)
else:
# set
if len(limits) != 2:
raise ValueError(
"second argument must be a (soft, hard) tuple, "
"got %s" % repr(limits))
- soft, hard = limits
- cext.linux_prlimit(self.pid, resource, soft, hard)
+ prlimit(self.pid, resource_, limits)
except OSError as err:
if err.errno == errno.ENOSYS and pid_exists(self.pid):
# I saw this happening on Travis:
diff --git a/psutil/_psosx.py b/psutil/_psosx.py
index e4296495..d948cc15 100644
--- a/psutil/_psosx.py
+++ b/psutil/_psosx.py
@@ -35,7 +35,7 @@ __extra__all__ = []
# =====================================================================
-PAGESIZE = os.sysconf("SC_PAGE_SIZE")
+PAGESIZE = cext_posix.getpagesize()
AF_LINK = cext_posix.AF_LINK
TCP_STATUSES = {
@@ -159,9 +159,9 @@ def cpu_count_logical():
return cext.cpu_count_logical()
-def cpu_count_physical():
- """Return the number of physical CPUs in the system."""
- return cext.cpu_count_phys()
+def cpu_count_cores():
+ """Return the number of CPU cores in the system."""
+ return cext.cpu_count_cores()
def cpu_stats():
@@ -201,7 +201,9 @@ def disk_partitions(all=False):
if not all:
if not os.path.isabs(device) or not os.path.exists(device):
continue
- ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+ maxfile = maxpath = None # set later
+ ntuple = _common.sdiskpart(device, mountpoint, fstype, opts,
+ maxfile, maxpath)
retlist.append(ntuple)
return retlist
@@ -262,7 +264,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 +326,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 +343,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/_psposix.py b/psutil/_psposix.py
index 2e6711a3..706dab9a 100644
--- a/psutil/_psposix.py
+++ b/psutil/_psposix.py
@@ -65,7 +65,7 @@ if enum is not None and hasattr(signal, "Signals"):
return Negsignal(num)
except ValueError:
return num
-else:
+else: # pragma: no cover
def negsig_to_enum(num):
return num
@@ -167,7 +167,7 @@ def disk_usage(path):
"""
if PY3:
st = os.statvfs(path)
- else:
+ else: # pragma: no cover
# os.statvfs() does not support unicode on Python 2:
# - https://github.com/giampaolo/psutil/issues/416
# - http://bugs.python.org/issue18695
diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py
index 62362b89..84d78814 100644
--- a/psutil/_pssunos.py
+++ b/psutil/_pssunos.py
@@ -43,7 +43,7 @@ __extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"]
# =====================================================================
-PAGE_SIZE = os.sysconf('SC_PAGE_SIZE')
+PAGE_SIZE = cext_posix.getpagesize()
AF_LINK = cext_posix.AF_LINK
IS_64_BIT = sys.maxsize > 2**32
@@ -155,7 +155,7 @@ def swap_memory():
total = free = 0
for line in lines:
line = line.split()
- t, f = line[-2:]
+ t, f = line[3:5]
total += int(int(t) * 512)
free += int(int(f) * 512)
used = total - free
@@ -190,9 +190,9 @@ def cpu_count_logical():
return None
-def cpu_count_physical():
- """Return the number of physical CPUs in the system."""
- return cext.cpu_count_phys()
+def cpu_count_cores():
+ """Return the number of CPU cores in the system."""
+ return cext.cpu_count_cores()
def cpu_stats():
@@ -233,7 +233,9 @@ def disk_partitions(all=False):
# https://github.com/giampaolo/psutil/issues/1674
debug("skipping %r: %r" % (mountpoint, err))
continue
- ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+ maxfile = maxpath = None # set later
+ ntuple = _common.sdiskpart(device, mountpoint, fstype, opts,
+ maxfile, maxpath)
retlist.append(ntuple)
return retlist
diff --git a/psutil/_psutil_aix.c b/psutil/_psutil_aix.c
index cf79d307..a80bed70 100644
--- a/psutil/_psutil_aix.c
+++ b/psutil/_psutil_aix.c
@@ -679,7 +679,7 @@ psutil_net_if_stats(PyObject* self, PyObject* args) {
if (sock == -1)
goto error;
- strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
+ PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
// is up?
ret = ioctl(sock, SIOCGIFFLAGS, &ifr);
@@ -876,7 +876,7 @@ error:
static PyObject *
psutil_virtual_mem(PyObject *self, PyObject *args) {
int rc;
- int pagesize = getpagesize();
+ long pagesize = psutil_getpagesize();
perfstat_memory_total_t memory;
rc = perfstat_memory_total(
@@ -902,7 +902,7 @@ psutil_virtual_mem(PyObject *self, PyObject *args) {
static PyObject *
psutil_swap_mem(PyObject *self, PyObject *args) {
int rc;
- int pagesize = getpagesize();
+ long pagesize = psutil_getpagesize();
perfstat_memory_total_t memory;
rc = perfstat_memory_total(
diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c
index 953fcd08..69ce6e8e 100644
--- a/psutil/_psutil_bsd.c
+++ b/psutil/_psutil_bsd.c
@@ -57,11 +57,13 @@
#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"
#ifdef PSUTIL_FREEBSD
+ #include "arch/freebsd/cpu.h"
#include "arch/freebsd/specific.h"
#include "arch/freebsd/sys_socks.h"
#include "arch/freebsd/proc_socks.h"
@@ -188,7 +190,7 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
long memstack;
int oncpu;
kinfo_proc kp;
- long pagesize = sysconf(_SC_PAGESIZE);
+ long pagesize = psutil_getpagesize();
char str[1000];
PyObject *py_name;
PyObject *py_ppid;
@@ -392,6 +394,140 @@ 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 -> EPERM");
+ break;
+ case ESRCH:
+ NoSuchProcess("kvm_getenvv -> ESRCH");
+ 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) -> 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 +753,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 +765,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 +971,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
@@ -953,9 +1093,15 @@ static PyMethodDef mod_methods[] = {
"Return process CPU affinity."},
{"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS,
"Set process CPU affinity."},
- {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS,
- "Return an XML string to determine the number physical CPUs."},
+ {"proc_getrlimit", psutil_proc_getrlimit, METH_VARARGS,
+ "Get process resource limits."},
+ {"proc_setrlimit", psutil_proc_setrlimit, METH_VARARGS,
+ "Set process resource limits."},
+ {"cpu_topology", psutil_cpu_topology, METH_VARARGS,
+ "Return CPU topology as an XML string."},
#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..ff060a51 100644
--- a/psutil/_psutil_common.c
+++ b/psutil/_psutil_common.c
@@ -23,9 +23,8 @@ int PSUTIL_TESTING = 0;
// ====================================================================
// PyPy on Windows
-#if defined(PSUTIL_WINDOWS) && \
- defined(PYPY_VERSION) && \
- !defined(PyErr_SetFromWindowsErrWithFilename)
+#if defined(PSUTIL_WINDOWS) && defined(PYPY_VERSION)
+#if !defined(PyErr_SetFromWindowsErrWithFilename)
PyObject *
PyErr_SetFromWindowsErrWithFilename(int winerr, const char *filename) {
PyObject *py_exc = NULL;
@@ -58,7 +57,17 @@ error:
Py_XDECREF(py_winerr);
return NULL;
}
-#endif // PYPY on Windows
+#endif // !defined(PyErr_SetFromWindowsErrWithFilename)
+
+
+// PyPy 2.7
+#if !defined(PyErr_SetFromWindowsErr)
+PyObject *
+PyErr_SetFromWindowsErr(int winerr) {
+ return PyErr_SetFromWindowsErrWithFilename(winerr, "");
+}
+#endif // !defined(PyErr_SetFromWindowsErr)
+#endif // defined(PSUTIL_WINDOWS) && defined(PYPY_VERSION)
// ====================================================================
@@ -74,8 +83,9 @@ PyErr_SetFromOSErrnoWithSyscall(const char *syscall) {
char fullmsg[1024];
#ifdef PSUTIL_WINDOWS
+ DWORD dwLastError = GetLastError();
sprintf(fullmsg, "(originated from %s)", syscall);
- PyErr_SetFromWindowsErrWithFilename(GetLastError(), fullmsg);
+ PyErr_SetFromWindowsErrWithFilename(dwLastError, fullmsg);
#else
PyObject *exc;
sprintf(fullmsg, "%s (originated from %s)", strerror(errno), syscall);
@@ -96,7 +106,7 @@ NoSuchProcess(const char *syscall) {
PyObject *exc;
char msg[1024];
- sprintf(msg, "No such process (originated from %s)", syscall);
+ sprintf(msg, "assume no such process (originated from %s)", syscall);
exc = PyObject_CallFunction(PyExc_OSError, "(is)", ESRCH, msg);
PyErr_SetObject(PyExc_OSError, exc);
Py_XDECREF(exc);
@@ -113,7 +123,7 @@ AccessDenied(const char *syscall) {
PyObject *exc;
char msg[1024];
- sprintf(msg, "Access denied (originated from %s)", syscall);
+ sprintf(msg, "assume access denied (originated from %s)", syscall);
exc = PyObject_CallFunction(PyExc_OSError, "(is)", EACCES, msg);
PyErr_SetObject(PyExc_OSError, exc);
Py_XDECREF(exc);
@@ -133,6 +143,7 @@ AccessDenied(const char *syscall) {
PyObject *
psutil_set_testing(PyObject *self, PyObject *args) {
PSUTIL_TESTING = 1;
+ PSUTIL_DEBUG = 1;
Py_INCREF(Py_None);
return Py_None;
}
@@ -167,6 +178,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
// ====================================================================
@@ -179,13 +210,6 @@ int PSUTIL_WINVER;
SYSTEM_INFO PSUTIL_SYSTEM_INFO;
CRITICAL_SECTION PSUTIL_CRITICAL_SECTION;
-#define NT_FACILITY_MASK 0xfff
-#define NT_FACILITY_SHIFT 16
-#define NT_FACILITY(Status) \
- ((((ULONG)(Status)) >> NT_FACILITY_SHIFT) & NT_FACILITY_MASK)
-#define NT_NTWIN32(status) (NT_FACILITY(Status) == FACILITY_WIN32)
-#define WIN32_FROM_NTSTATUS(Status) (((ULONG)(Status)) & 0xffff)
-
// A wrapper around GetModuleHandle and GetProcAddress.
PVOID
@@ -264,10 +288,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 +340,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 34c428c0..cb0b399d 100644
--- a/psutil/_psutil_common.h
+++ b/psutil/_psutil_common.h
@@ -15,6 +15,11 @@ extern int PSUTIL_DEBUG;
// a signaler for connections without an actual status
static const int PSUTIL_CONN_NONE = 128;
+// strncpy() variant which appends a null terminator.
+#define PSUTIL_STRNCPY(dst, src, n) \
+ strncpy(dst, src, n - 1); \
+ dst[n - 1] = '\0'
+
// ====================================================================
// --- Backward compatibility with missing Python.h APIs
// ====================================================================
@@ -100,6 +105,12 @@ void psutil_debug(const char* format, ...);
int psutil_setup(void);
// ====================================================================
+// --- BSD
+// ====================================================================
+
+void convert_kvm_err(const char *syscall, char *errbuf);
+
+// ====================================================================
// --- Windows
// ====================================================================
@@ -123,6 +134,14 @@ int psutil_setup(void);
#define MALLOC_ZERO(x) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
+ #define _NT_FACILITY_MASK 0xfff
+ #define _NT_FACILITY_SHIFT 16
+ #define _NT_FACILITY(status) \
+ ((((ULONG)(status)) >> _NT_FACILITY_SHIFT) & _NT_FACILITY_MASK)
+
+ #define NT_NTWIN32(status) (_NT_FACILITY(status) == FACILITY_WIN32)
+ #define WIN32_FROM_NTSTATUS(status) (((ULONG)(status)) & 0xffff)
+
#define LO_T 1e-7
#define HI_T 429.4967296
diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c
index 4faa36ad..5e21c03c 100644
--- a/psutil/_psutil_linux.c
+++ b/psutil/_psutil_linux.c
@@ -24,7 +24,7 @@
#include <linux/sockios.h>
#include <linux/if.h>
#include <malloc.h>
-
+#include <sys/resource.h>
// see: https://github.com/giampaolo/psutil/issues/659
#ifdef PSUTIL_ETHTOOL_MISSING_TYPES
@@ -44,18 +44,6 @@ static const int NCPUS_START = sizeof(unsigned long) * CHAR_BIT;
// Linux >= 2.6.13
#define PSUTIL_HAVE_IOPRIO defined(__NR_ioprio_get) && defined(__NR_ioprio_set)
-// Linux >= 2.6.36 (supposedly) and glibc >= 13
-#define PSUTIL_HAVE_PRLIMIT \
- (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)) && \
- (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 13) && \
- defined(__NR_prlimit64)
-
-#if PSUTIL_HAVE_PRLIMIT
- #define _FILE_OFFSET_BITS 64
- #include <time.h>
- #include <sys/resource.h>
-#endif
-
// Should exist starting from CentOS 6 (year 2011).
#ifdef CPU_ALLOC
#define PSUTIL_HAVE_CPU_AFFINITY
@@ -136,68 +124,6 @@ psutil_proc_ioprio_set(PyObject *self, PyObject *args) {
#endif
-#if PSUTIL_HAVE_PRLIMIT
-/*
- * A wrapper around prlimit(2); sets process resource limits.
- * This can be used for both get and set, in which case extra
- * 'soft' and 'hard' args must be provided.
- */
-static PyObject *
-psutil_linux_prlimit(PyObject *self, PyObject *args) {
- pid_t pid;
- int ret, resource;
- struct rlimit old, new;
- struct rlimit *newp = NULL;
- PyObject *py_soft = NULL;
- PyObject *py_hard = NULL;
-
- if (! PyArg_ParseTuple(args, _Py_PARSE_PID "i|OO", &pid, &resource,
- &py_soft, &py_hard)) {
- return NULL;
- }
-
- // get
- if (py_soft == NULL && py_hard == NULL) {
- ret = prlimit(pid, resource, NULL, &old);
- if (ret == -1)
- return PyErr_SetFromErrno(PyExc_OSError);
-#if defined(PSUTIL_HAVE_LONG_LONG)
- if (sizeof(old.rlim_cur) > sizeof(long)) {
- return Py_BuildValue("LL",
- (PY_LONG_LONG)old.rlim_cur,
- (PY_LONG_LONG)old.rlim_max);
- }
-#endif
- return Py_BuildValue("ll", (long)old.rlim_cur, (long)old.rlim_max);
- }
-
- // set
- else {
-#if defined(PSUTIL_HAVE_LARGEFILE_SUPPORT)
- new.rlim_cur = PyLong_AsLongLong(py_soft);
- if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred())
- return NULL;
- new.rlim_max = PyLong_AsLongLong(py_hard);
- if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred())
- return NULL;
-#else
- new.rlim_cur = PyLong_AsLong(py_soft);
- if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred())
- return NULL;
- new.rlim_max = PyLong_AsLong(py_hard);
- if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred())
- return NULL;
-#endif
- newp = &new;
- ret = prlimit(pid, resource, newp, &old);
- if (ret == -1)
- return PyErr_SetFromErrno(PyExc_OSError);
- Py_RETURN_NONE;
- }
-}
-#endif
-
-
/*
* Return disk mounted partitions as a list of tuples including device,
* mount point and filesystem type
@@ -503,7 +429,7 @@ psutil_net_if_duplex_speed(PyObject* self, PyObject* args) {
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1)
return PyErr_SetFromOSErrnoWithSyscall("socket()");
- strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
+ PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
// duplex and speed
memset(&ethcmd, 0, sizeof ethcmd);
@@ -632,7 +558,6 @@ static PyMethodDef mod_methods[] = {
void init_psutil_linux(void)
#endif /* PY_MAJOR_VERSION */
{
- PyObject *v;
#if PY_MAJOR_VERSION >= 3
PyObject *mod = PyModule_Create(&moduledef);
#else
@@ -642,52 +567,12 @@ static PyMethodDef mod_methods[] = {
INITERR;
if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION)) INITERR;
-#if PSUTIL_HAVE_PRLIMIT
- if (PyModule_AddIntConstant(mod, "RLIMIT_AS", RLIMIT_AS)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_CORE", RLIMIT_CORE)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_CPU", RLIMIT_CPU)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_DATA", RLIMIT_DATA)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_FSIZE", RLIMIT_FSIZE)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_LOCKS", RLIMIT_LOCKS)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_MEMLOCK", RLIMIT_MEMLOCK)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_NOFILE", RLIMIT_NOFILE)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_NPROC", RLIMIT_NPROC)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_RSS", RLIMIT_RSS)) INITERR;
- if (PyModule_AddIntConstant(mod, "RLIMIT_STACK", RLIMIT_STACK)) INITERR;
-
-#if defined(HAVE_LONG_LONG)
- if (sizeof(RLIM_INFINITY) > sizeof(long)) {
- v = PyLong_FromLongLong((PY_LONG_LONG) RLIM_INFINITY);
- } else
-#endif
- {
- v = PyLong_FromLong((long) RLIM_INFINITY);
- }
- if (v) {
- PyModule_AddObject(mod, "RLIM_INFINITY", v);
- }
-
-#ifdef RLIMIT_MSGQUEUE
- if (PyModule_AddIntConstant(mod, "RLIMIT_MSGQUEUE", RLIMIT_MSGQUEUE)) INITERR;
-#endif
-#ifdef RLIMIT_NICE
- if (PyModule_AddIntConstant(mod, "RLIMIT_NICE", RLIMIT_NICE)) INITERR;
-#endif
-#ifdef RLIMIT_RTPRIO
- if (PyModule_AddIntConstant(mod, "RLIMIT_RTPRIO", RLIMIT_RTPRIO)) INITERR;
-#endif
-#ifdef RLIMIT_RTTIME
- if (PyModule_AddIntConstant(mod, "RLIMIT_RTTIME", RLIMIT_RTTIME)) INITERR;
-#endif
-#ifdef RLIMIT_SIGPENDING
- if (PyModule_AddIntConstant(mod, "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING))
- INITERR;
-#endif
-#endif
if (PyModule_AddIntConstant(mod, "DUPLEX_HALF", DUPLEX_HALF)) INITERR;
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_osx.c b/psutil/_psutil_osx.c
index c51c1c78..5a77de14 100644
--- a/psutil/_psutil_osx.c
+++ b/psutil/_psutil_osx.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+ * Copyright (c) 2009, Jay Loden, 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.
*
@@ -14,20 +14,14 @@
#include <stdio.h>
#include <utmpx.h>
#include <sys/sysctl.h>
-#include <sys/vmmeter.h>
#include <libproc.h>
#include <sys/proc_info.h>
#include <netinet/tcp_fsm.h>
#include <arpa/inet.h>
#include <net/if_dl.h>
#include <pwd.h>
-
+#include <unistd.h>
#include <mach/mach.h>
-#include <mach/task.h>
-#include <mach/mach_init.h>
-#include <mach/host_info.h>
-#include <mach/mach_host.h>
-#include <mach/mach_traps.h>
#include <mach/mach_vm.h>
#include <mach/shared_region.h>
@@ -44,6 +38,7 @@
#include "_psutil_common.h"
#include "_psutil_posix.h"
#include "arch/osx/process_info.h"
+#include "arch/osx/cpu.h"
#define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0)
@@ -98,7 +93,8 @@ psutil_task_for_pid(pid_t pid, mach_port_t *task)
if (psutil_pid_exists(pid) == 0)
NoSuchProcess("task_for_pid");
else if (psutil_is_zombie(pid) == 1)
- PyErr_SetString(ZombieProcessError, "task_for_pid() failed");
+ PyErr_SetString(ZombieProcessError,
+ "task_for_pid -> psutil_is_zombie -> 1");
else {
psutil_debug(
"task_for_pid() failed (pid=%ld, err=%i, errno=%i, msg='%s'); "
@@ -297,11 +293,21 @@ psutil_proc_exe(PyObject *self, PyObject *args) {
errno = 0;
ret = proc_pidpath(pid, &buf, sizeof(buf));
if (ret == 0) {
- if (pid == 0)
+ if (pid == 0) {
AccessDenied("automatically set for PID 0");
- else
+ return NULL;
+ }
+ else if (errno == ENOENT) {
+ // It may happen (file not found error) if the process is
+ // still alive but the executable which launched it got
+ // deleted, see:
+ // https://github.com/giampaolo/psutil/issues/1738
+ return Py_BuildValue("s", "");
+ }
+ else {
psutil_raise_for_pid(pid, "proc_pidpath()");
- return NULL;
+ return NULL;
+ }
}
return PyUnicode_DecodeFSDefault(buf);
}
@@ -342,50 +348,6 @@ psutil_proc_environ(PyObject *self, PyObject *args) {
/*
- * Return the number of logical CPUs in the system.
- * XXX this could be shared with BSD.
- */
-static PyObject *
-psutil_cpu_count_logical(PyObject *self, PyObject *args) {
- /*
- int mib[2];
- int ncpu;
- size_t len;
- mib[0] = CTL_HW;
- mib[1] = HW_NCPU;
- len = sizeof(ncpu);
-
- if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1)
- Py_RETURN_NONE; // mimic os.cpu_count()
- else
- return Py_BuildValue("i", ncpu);
- */
- int num;
- size_t size = sizeof(int);
-
- if (sysctlbyname("hw.logicalcpu", &num, &size, NULL, 2))
- Py_RETURN_NONE; // mimic os.cpu_count()
- else
- return Py_BuildValue("i", num);
-}
-
-
-/*
- * Return the number of physical CPUs in the system.
- */
-static PyObject *
-psutil_cpu_count_phys(PyObject *self, PyObject *args) {
- int num;
- size_t size = sizeof(int);
-
- if (sysctlbyname("hw.physicalcpu", &num, &size, NULL, 0))
- Py_RETURN_NONE; // mimic os.cpu_count()
- else
- return Py_BuildValue("i", num);
-}
-
-
-/*
* Indicates if the given virtual address on the given architecture is in the
* shared VM region.
*/
@@ -429,7 +391,7 @@ psutil_proc_memory_uss(PyObject *self, PyObject *args) {
mach_vm_size_t size = 0;
mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT;
kern_return_t kr;
- vm_size_t page_size;
+ long pagesize = psutil_getpagesize();
mach_vm_address_t addr = MACH_VM_MIN_ADDRESS;
mach_port_t task = MACH_PORT_NULL;
vm_region_top_info_data_t info;
@@ -493,11 +455,7 @@ psutil_proc_memory_uss(PyObject *self, PyObject *args) {
}
mach_port_deallocate(mach_task_self(), task);
-
- if (host_page_size(mach_host_self(), &page_size) != KERN_SUCCESS)
- page_size = PAGE_SIZE;
-
- return Py_BuildValue("K", private_pages * page_size);
+ return Py_BuildValue("K", private_pages * pagesize);
}
@@ -513,7 +471,7 @@ psutil_virtual_mem(PyObject *self, PyObject *args) {
uint64_t total;
size_t len = sizeof(total);
vm_statistics_data_t vm;
- int pagesize = getpagesize();
+ long pagesize = psutil_getpagesize();
// physical mem
mib[0] = CTL_HW;
mib[1] = HW_MEMSIZE;
@@ -553,7 +511,7 @@ psutil_swap_mem(PyObject *self, PyObject *args) {
size_t size;
struct xsw_usage totals;
vm_statistics_data_t vmstat;
- int pagesize = getpagesize();
+ long pagesize = psutil_getpagesize();
mib[0] = CTL_VM;
mib[1] = VM_SWAPUSAGE;
@@ -580,36 +538,6 @@ psutil_swap_mem(PyObject *self, PyObject *args) {
/*
- * Return a Python tuple representing user, kernel and idle CPU times
- */
-static PyObject *
-psutil_cpu_times(PyObject *self, PyObject *args) {
- mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT;
- kern_return_t error;
- host_cpu_load_info_data_t r_load;
-
- mach_port_t host_port = mach_host_self();
- error = host_statistics(host_port, HOST_CPU_LOAD_INFO,
- (host_info_t)&r_load, &count);
- if (error != KERN_SUCCESS) {
- return PyErr_Format(
- PyExc_RuntimeError,
- "host_statistics(HOST_CPU_LOAD_INFO) syscall failed: %s",
- mach_error_string(error));
- }
- mach_port_deallocate(mach_task_self(), host_port);
-
- return Py_BuildValue(
- "(dddd)",
- (double)r_load.cpu_ticks[CPU_STATE_USER] / CLK_TCK,
- (double)r_load.cpu_ticks[CPU_STATE_NICE] / CLK_TCK,
- (double)r_load.cpu_ticks[CPU_STATE_SYSTEM] / CLK_TCK,
- (double)r_load.cpu_ticks[CPU_STATE_IDLE] / CLK_TCK
- );
-}
-
-
-/*
* Return a Python list of tuple representing per-cpu times
*/
static PyObject *
@@ -676,37 +604,6 @@ error:
/*
- * Retrieve CPU frequency.
- */
-static PyObject *
-psutil_cpu_freq(PyObject *self, PyObject *args) {
- int64_t curr;
- int64_t min;
- int64_t max;
- size_t size = sizeof(int64_t);
-
- if (sysctlbyname("hw.cpufrequency", &curr, &size, NULL, 0)) {
- return PyErr_SetFromOSErrnoWithSyscall(
- "sysctlbyname('hw.cpufrequency')");
- }
- if (sysctlbyname("hw.cpufrequency_min", &min, &size, NULL, 0)) {
- return PyErr_SetFromOSErrnoWithSyscall(
- "sysctlbyname('hw.cpufrequency_min')");
- }
- if (sysctlbyname("hw.cpufrequency_max", &max, &size, NULL, 0)) {
- return PyErr_SetFromOSErrnoWithSyscall(
- "sysctlbyname('hw.cpufrequency_max')");
- }
-
- return Py_BuildValue(
- "KKK",
- curr / 1000 / 1000,
- min / 1000 / 1000,
- max / 1000 / 1000);
-}
-
-
-/*
* Return a Python float indicating the system boot time expressed in
* seconds since the epoch.
*/
@@ -1130,9 +1027,18 @@ psutil_proc_connections(PyObject *self, PyObject *args) {
PROC_PIDFDSOCKETINFO, &si, sizeof(si));
// --- errors checking
- if ((nb <= 0) || (nb < sizeof(si))) {
+ if ((nb <= 0) || (nb < sizeof(si)) || (errno != 0)) {
if (errno == EBADF) {
// let's assume socket has been closed
+ psutil_debug("proc_pidfdinfo(PROC_PIDFDSOCKETINFO) -> "
+ "EBADF (ignored)");
+ continue;
+ }
+ else if (errno == EOPNOTSUPP) {
+ // may happen sometimes, see:
+ // https://github.com/giampaolo/psutil/issues/1512
+ psutil_debug("proc_pidfdinfo(PROC_PIDFDSOCKETINFO) -> "
+ "EOPNOTSUPP (ignored)");
continue;
}
else {
@@ -1166,11 +1072,6 @@ psutil_proc_connections(PyObject *self, PyObject *args) {
if (inseq == 0)
continue;
- if (errno != 0) {
- PyErr_SetFromErrno(PyExc_OSError);
- goto error;
- }
-
if ((family == AF_INET) || (family == AF_INET6)) {
if (family == AF_INET) {
inet_ntop(AF_INET,
@@ -1624,37 +1525,6 @@ error:
/*
- * Return CPU statistics.
- */
-static PyObject *
-psutil_cpu_stats(PyObject *self, PyObject *args) {
- struct vmmeter vmstat;
- kern_return_t ret;
- mach_msg_type_number_t count = sizeof(vmstat) / sizeof(integer_t);
- mach_port_t mport = mach_host_self();
-
- ret = host_statistics(mport, HOST_VM_INFO, (host_info_t)&vmstat, &count);
- if (ret != KERN_SUCCESS) {
- PyErr_Format(
- PyExc_RuntimeError,
- "host_statistics(HOST_VM_INFO) failed: %s",
- mach_error_string(ret));
- return NULL;
- }
- mach_port_deallocate(mach_task_self(), mport);
-
- return Py_BuildValue(
- "IIIII",
- vmstat.v_swtch, // ctx switches
- vmstat.v_intr, // interrupts
- vmstat.v_soft, // software interrupts
- vmstat.v_syscall, // syscalls
- vmstat.v_trap // traps
- );
-}
-
-
-/*
* Return battery information.
*/
static PyObject *
@@ -1778,8 +1648,8 @@ static PyMethodDef mod_methods[] = {
"Returns a list of PIDs currently running on the system"},
{"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS,
"Return number of logical CPUs on the system"},
- {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS,
- "Return number of physical CPUs on the system"},
+ {"cpu_count_cores", psutil_cpu_count_cores, METH_VARARGS,
+ "Return number of CPU cores on the system"},
{"virtual_mem", psutil_virtual_mem, METH_VARARGS,
"Return system virtual memory stats"},
{"swap_mem", psutil_swap_mem, METH_VARARGS,
diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c
index 38483b3b..305cec76 100644
--- a/psutil/_psutil_posix.c
+++ b/psutil/_psutil_posix.c
@@ -15,6 +15,7 @@
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
+#include <unistd.h>
#ifdef PSUTIL_SUNOS10
#include "arch/solaris/v10/ifaddrs.h"
@@ -28,22 +29,60 @@
#include <netdb.h>
#include <linux/types.h>
#include <linux/if_packet.h>
-#elif defined(PSUTIL_BSD) || defined(PSUTIL_OSX)
+#endif
+#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX)
#include <netdb.h>
#include <netinet/in.h>
#include <net/if_dl.h>
#include <sys/sockio.h>
#include <net/if_media.h>
#include <net/if.h>
-#elif defined(PSUTIL_SUNOS)
+#endif
+#if defined(PSUTIL_SUNOS)
#include <netdb.h>
#include <sys/sockio.h>
-#elif defined(PSUTIL_AIX)
+#endif
+#if defined(PSUTIL_AIX)
#include <netdb.h>
#endif
+#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD)
+ #include <sys/resource.h>
+#endif
#include "_psutil_common.h"
+
+// ====================================================================
+// --- Utils
+// ====================================================================
+
+
+/*
+ * From "man getpagesize" on Linux, https://linux.die.net/man/2/getpagesize:
+ *
+ * > In SUSv2 the getpagesize() call is labeled LEGACY, and in POSIX.1-2001
+ * > it has been dropped.
+ * > Portable applications should employ sysconf(_SC_PAGESIZE) instead
+ * > of getpagesize().
+ * > Most systems allow the synonym _SC_PAGE_SIZE for _SC_PAGESIZE.
+ * > Whether getpagesize() is present as a Linux system call depends on the
+ * > architecture.
+ */
+long
+psutil_getpagesize(void) {
+#ifdef _SC_PAGESIZE
+ // recommended POSIX
+ return sysconf(_SC_PAGESIZE);
+#elif _SC_PAGE_SIZE
+ // alias
+ return sysconf(_SC_PAGE_SIZE);
+#else
+ // legacy
+ return (long) getpagesize();
+#endif
+}
+
+
/*
* Check if PID exists. Return values:
* 1: exists
@@ -117,6 +156,18 @@ psutil_raise_for_pid(long pid, char *syscall) {
}
+// ====================================================================
+// --- Python wrappers
+// ====================================================================
+
+
+// Exposed so we can test it against Python's stdlib.
+static PyObject *
+psutil_getpagesize_pywrapper(PyObject *self, PyObject *args) {
+ return Py_BuildValue("l", psutil_getpagesize());
+}
+
+
/*
* Given a PID return process priority as a Python integer.
*/
@@ -356,10 +407,10 @@ psutil_net_if_mtu(PyObject *self, PyObject *args) {
goto error;
#ifdef PSUTIL_SUNOS10
- strncpy(lifr.lifr_name, nic_name, sizeof(lifr.lifr_name));
+ PSUTIL_STRNCPY(lifr.lifr_name, nic_name, sizeof(lifr.lifr_name));
ret = ioctl(sock, SIOCGIFMTU, &lifr);
#else
- strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
+ PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
ret = ioctl(sock, SIOCGIFMTU, &ifr);
#endif
if (ret == -1)
@@ -385,7 +436,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;
@@ -398,13 +449,13 @@ psutil_net_if_flags(PyObject *self, PyObject *args) {
if (sock == -1)
goto error;
- strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
+ PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
ret = ioctl(sock, SIOCGIFFLAGS, &ifr);
if (ret == -1)
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);
@@ -578,7 +629,7 @@ psutil_net_if_duplex_speed(PyObject *self, PyObject *args) {
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1)
return PyErr_SetFromErrno(PyExc_OSError);
- strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
+ PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
// speed / duplex
memset(&ifmed, 0, sizeof(struct ifmediareq));
@@ -621,8 +672,10 @@ 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."},
+ {"getpagesize", psutil_getpagesize_pywrapper, METH_VARARGS,
+ "Return memory page size."},
#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX)
{"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS,
"Return NIC stats."},
@@ -668,6 +721,102 @@ static PyMethodDef mod_methods[] = {
if (PyModule_AddIntConstant(mod, "AF_LINK", AF_LINK)) INITERR;
#endif
+#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD)
+ PyObject *v;
+
+#ifdef RLIMIT_AS
+ if (PyModule_AddIntConstant(mod, "RLIMIT_AS", RLIMIT_AS)) INITERR;
+#endif
+
+#ifdef RLIMIT_CORE
+ if (PyModule_AddIntConstant(mod, "RLIMIT_CORE", RLIMIT_CORE)) INITERR;
+#endif
+
+#ifdef RLIMIT_CPU
+ if (PyModule_AddIntConstant(mod, "RLIMIT_CPU", RLIMIT_CPU)) INITERR;
+#endif
+
+#ifdef RLIMIT_DATA
+ if (PyModule_AddIntConstant(mod, "RLIMIT_DATA", RLIMIT_DATA)) INITERR;
+#endif
+
+#ifdef RLIMIT_FSIZE
+ if (PyModule_AddIntConstant(mod, "RLIMIT_FSIZE", RLIMIT_FSIZE)) INITERR;
+#endif
+
+#ifdef RLIMIT_MEMLOCK
+ if (PyModule_AddIntConstant(mod, "RLIMIT_MEMLOCK", RLIMIT_MEMLOCK)) INITERR;
+#endif
+
+#ifdef RLIMIT_NOFILE
+ if (PyModule_AddIntConstant(mod, "RLIMIT_NOFILE", RLIMIT_NOFILE)) INITERR;
+#endif
+
+#ifdef RLIMIT_NPROC
+ if (PyModule_AddIntConstant(mod, "RLIMIT_NPROC", RLIMIT_NPROC)) INITERR;
+#endif
+
+#ifdef RLIMIT_RSS
+ if (PyModule_AddIntConstant(mod, "RLIMIT_RSS", RLIMIT_RSS)) INITERR;
+#endif
+
+#ifdef RLIMIT_STACK
+ if (PyModule_AddIntConstant(mod, "RLIMIT_STACK", RLIMIT_STACK)) INITERR;
+#endif
+
+// Linux specific
+
+#ifdef RLIMIT_LOCKS
+ if (PyModule_AddIntConstant(mod, "RLIMIT_LOCKS", RLIMIT_LOCKS)) INITERR;
+#endif
+
+#ifdef RLIMIT_MSGQUEUE
+ if (PyModule_AddIntConstant(mod, "RLIMIT_MSGQUEUE", RLIMIT_MSGQUEUE)) INITERR;
+#endif
+
+#ifdef RLIMIT_NICE
+ if (PyModule_AddIntConstant(mod, "RLIMIT_NICE", RLIMIT_NICE)) INITERR;
+#endif
+
+#ifdef RLIMIT_RTPRIO
+ if (PyModule_AddIntConstant(mod, "RLIMIT_RTPRIO", RLIMIT_RTPRIO)) INITERR;
+#endif
+
+#ifdef RLIMIT_RTTIME
+ if (PyModule_AddIntConstant(mod, "RLIMIT_RTTIME", RLIMIT_RTTIME)) INITERR;
+#endif
+
+#ifdef RLIMIT_SIGPENDING
+ if (PyModule_AddIntConstant(mod, "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING)) INITERR;
+#endif
+
+// Free specific
+
+#ifdef RLIMIT_SWAP
+ if (PyModule_AddIntConstant(mod, "RLIMIT_SWAP", RLIMIT_SWAP)) INITERR;
+#endif
+
+#ifdef RLIMIT_SBSIZE
+ if (PyModule_AddIntConstant(mod, "RLIMIT_SBSIZE", RLIMIT_SBSIZE)) INITERR;
+#endif
+
+#ifdef RLIMIT_NPTS
+ if (PyModule_AddIntConstant(mod, "RLIMIT_NPTS", RLIMIT_NPTS)) INITERR;
+#endif
+
+#if defined(HAVE_LONG_LONG)
+ if (sizeof(RLIM_INFINITY) > sizeof(long)) {
+ v = PyLong_FromLongLong((PY_LONG_LONG) RLIM_INFINITY);
+ } else
+#endif
+ {
+ v = PyLong_FromLong((long) RLIM_INFINITY);
+ }
+ if (v) {
+ PyModule_AddObject(mod, "RLIM_INFINITY", v);
+ }
+#endif // defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD)
+
if (mod == NULL)
INITERR;
#if PY_MAJOR_VERSION >= 3
diff --git a/psutil/_psutil_posix.h b/psutil/_psutil_posix.h
index 59b9e532..5a37e48b 100644
--- a/psutil/_psutil_posix.h
+++ b/psutil/_psutil_posix.h
@@ -4,5 +4,6 @@
* found in the LICENSE file.
*/
+long psutil_getpagesize(void);
int psutil_pid_exists(pid_t pid);
void psutil_raise_for_pid(pid_t pid, char *msg);
diff --git a/psutil/_psutil_sunos.c b/psutil/_psutil_sunos.c
index 6548640b..342798a8 100644
--- a/psutil/_psutil_sunos.c
+++ b/psutil/_psutil_sunos.c
@@ -1024,7 +1024,7 @@ psutil_net_io_counters(PyObject *self, PyObject *args) {
goto next;
// check if this is a network interface by sending a ioctl
- strncpy(ifr.lifr_name, ksp->ks_name, sizeof(ifr.lifr_name));
+ PSUTIL_STRNCPY(ifr.lifr_name, ksp->ks_name, sizeof(ifr.lifr_name));
ret = ioctl(sock, SIOCGLIFFLAGS, &ifr);
if (ret == -1)
goto next;
@@ -1437,10 +1437,10 @@ psutil_boot_time(PyObject *self, PyObject *args) {
/*
- * Return the number of physical CPU cores on the system.
+ * Return the number of CPU cores on the system.
*/
static PyObject *
-psutil_cpu_count_phys(PyObject *self, PyObject *args) {
+psutil_cpu_count_cores(PyObject *self, PyObject *args) {
kstat_ctl_t *kc;
kstat_t *ksp;
int ncpus = 0;
@@ -1515,7 +1515,7 @@ psutil_net_if_stats(PyObject* self, PyObject* args) {
if (strcmp(ksp->ks_class, "net") != 0)
continue;
- strncpy(ifr.lifr_name, ksp->ks_name, sizeof(ifr.lifr_name));
+ PSUTIL_STRNCPY(ifr.lifr_name, ksp->ks_name, sizeof(ifr.lifr_name));
ret = ioctl(sock, SIOCGLIFFLAGS, &ifr);
if (ret == -1)
continue; // not a network interface
@@ -1669,8 +1669,8 @@ PsutilMethods[] = {
"Return a Python dict of tuples for network I/O statistics."},
{"boot_time", psutil_boot_time, METH_VARARGS,
"Return system boot time in seconds since the EPOCH."},
- {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS,
- "Return the number of physical CPUs on the system."},
+ {"cpu_count_cores", psutil_cpu_count_cores, METH_VARARGS,
+ "Return the number of CPU cores on the system."},
{"net_connections", psutil_net_connections, METH_VARARGS,
"Return TCP and UDP syste-wide open connections."},
{"net_if_stats", psutil_net_if_stats, METH_VARARGS,
diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c
index 64f25b3e..5bff854d 100644
--- a/psutil/_psutil_windows.c
+++ b/psutil/_psutil_windows.c
@@ -46,35 +46,6 @@ static PyObject *TimeoutAbandoned;
/*
- * Return the number of logical, active CPUs. Return 0 if undetermined.
- * See discussion at: https://bugs.python.org/issue33166#msg314631
- */
-unsigned int
-psutil_get_num_cpus(int fail_on_err) {
- unsigned int ncpus = 0;
-
- // Minimum requirement: Windows 7
- if (GetActiveProcessorCount != NULL) {
- ncpus = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
- if ((ncpus == 0) && (fail_on_err == 1)) {
- PyErr_SetFromWindowsErr(0);
- }
- }
- else {
- psutil_debug("GetActiveProcessorCount() not available; "
- "using GetSystemInfo()");
- ncpus = (unsigned int)PSUTIL_SYSTEM_INFO.dwNumberOfProcessors;
- if ((ncpus <= 0) && (fail_on_err == 1)) {
- PyErr_SetString(
- PyExc_RuntimeError,
- "GetSystemInfo() failed to retrieve CPU count");
- }
- }
- return ncpus;
-}
-
-
-/*
* Return a Python float representing the system uptime expressed in seconds
* since the epoch.
*/
@@ -161,22 +132,15 @@ psutil_proc_kill(PyObject *self, PyObject *args) {
return AccessDenied("automatically set for PID 0");
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+ hProcess = psutil_check_phandle(hProcess, pid, 0);
if (hProcess == NULL) {
- if (GetLastError() == ERROR_INVALID_PARAMETER) {
- // see https://github.com/giampaolo/psutil/issues/24
- psutil_debug("OpenProcess -> ERROR_INVALID_PARAMETER turned "
- "into NoSuchProcess");
- NoSuchProcess("OpenProcess");
- }
- else {
- PyErr_SetFromWindowsErr(0);
- }
return NULL;
}
if (! TerminateProcess(hProcess, SIGTERM)) {
// ERROR_ACCESS_DENIED may happen if the process already died. See:
// https://github.com/giampaolo/psutil/issues/1099
+ // http://bugs.python.org/issue14252
if (GetLastError() != ERROR_ACCESS_DENIED) {
PyErr_SetFromOSErrnoWithSyscall("TerminateProcess");
return NULL;
@@ -213,7 +177,7 @@ psutil_proc_wait(PyObject *self, PyObject *args) {
Py_RETURN_NONE;
}
else {
- PyErr_SetFromWindowsErr(0);
+ PyErr_SetFromOSErrnoWithSyscall("OpenProcess");
return NULL;
}
}
@@ -282,7 +246,7 @@ psutil_proc_times(PyObject *self, PyObject *args) {
if (GetLastError() == ERROR_ACCESS_DENIED) {
// usually means the process has died so we throw a NoSuchProcess
// here
- NoSuchProcess("GetProcessTimes");
+ NoSuchProcess("GetProcessTimes -> ERROR_ACCESS_DENIED");
}
else {
PyErr_SetFromWindowsErr(0);
@@ -334,7 +298,7 @@ psutil_proc_cmdline(PyObject *self, PyObject *args, PyObject *kwdict) {
pid_return = psutil_pid_is_running(pid);
if (pid_return == 0)
- return NoSuchProcess("psutil_pid_is_running");
+ return NoSuchProcess("psutil_pid_is_running -> 0");
if (pid_return == -1)
return NULL;
@@ -358,7 +322,7 @@ psutil_proc_environ(PyObject *self, PyObject *args) {
pid_return = psutil_pid_is_running(pid);
if (pid_return == 0)
- return NoSuchProcess("psutil_pid_is_running");
+ return NoSuchProcess("psutil_pid_is_running -> 0");
if (pid_return == -1)
return NULL;
@@ -385,7 +349,7 @@ psutil_proc_exe(PyObject *self, PyObject *args) {
return NULL;
if (pid == 0)
- return AccessDenied("forced for PID 0");
+ return AccessDenied("automatically set for PID 0");
buffer = MALLOC_ZERO(bufferSize);
if (! buffer)
@@ -419,7 +383,7 @@ psutil_proc_exe(PyObject *self, PyObject *args) {
if (! NT_SUCCESS(status)) {
FREE(buffer);
if (psutil_pid_is_running(pid) == 0)
- NoSuchProcess("NtQuerySystemInformation");
+ NoSuchProcess("psutil_pid_is_running -> 0");
else
psutil_SetFromNTStatusErr(status, "NtQuerySystemInformation");
return NULL;
@@ -538,10 +502,10 @@ psutil_GetProcWsetInformation(
if (!NT_SUCCESS(status)) {
if (status == STATUS_ACCESS_DENIED) {
- AccessDenied("NtQueryVirtualMemory");
+ AccessDenied("NtQueryVirtualMemory -> STATUS_ACCESS_DENIED");
}
else if (psutil_pid_is_running(pid) == 0) {
- NoSuchProcess("psutil_pid_is_running");
+ NoSuchProcess("psutil_pid_is_running -> 0");
}
else {
PyErr_Clear();
@@ -646,7 +610,7 @@ psutil_proc_cwd(PyObject *self, PyObject *args) {
pid_return = psutil_pid_is_running(pid);
if (pid_return == 0)
- return NoSuchProcess("psutil_pid_is_running");
+ return NoSuchProcess("psutil_pid_is_running -> 0");
if (pid_return == -1)
return NULL;
@@ -705,13 +669,13 @@ psutil_proc_threads(PyObject *self, PyObject *args) {
if (pid == 0) {
// raise AD instead of returning 0 as procexp is able to
// retrieve useful information somehow
- AccessDenied("automatically set for PID 0");
+ AccessDenied("forced for PID 0");
goto error;
}
pid_return = psutil_pid_is_running(pid);
if (pid_return == 0) {
- NoSuchProcess("psutil_pid_is_running");
+ NoSuchProcess("psutil_pid_is_running -> 0");
goto error;
}
if (pid_return == -1)
@@ -904,6 +868,19 @@ psutil_proc_username(PyObject *self, PyObject *args) {
free(domainName);
continue;
}
+ else if (GetLastError() == ERROR_NONE_MAPPED) {
+ // From MS doc:
+ // https://docs.microsoft.com/en-us/windows/win32/api/winbase/
+ // nf-winbase-lookupaccountsida
+ // If the function cannot find an account name for the SID,
+ // GetLastError returns ERROR_NONE_MAPPED. This can occur if
+ // a network time-out prevents the function from finding the
+ // name. It also occurs for SIDs that have no corresponding
+ // account name, such as a logon SID that identifies a logon
+ // session.
+ AccessDenied("LookupAccountSidW -> ERROR_NONE_MAPPED");
+ goto error;
+ }
else {
PyErr_SetFromOSErrnoWithSyscall("LookupAccountSidW");
goto error;
@@ -1192,17 +1169,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;
@@ -1211,8 +1188,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;
}
@@ -1224,9 +1214,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;
@@ -1240,21 +1233,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;
@@ -1264,26 +1258,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;
@@ -1297,6 +1288,7 @@ psutil_users(PyObject *self, PyObject *args) {
WTSFreeMemory(sessions);
WTSFreeMemory(buffer_user);
WTSFreeMemory(buffer_addr);
+ WTSFreeMemory(buffer_info);
return py_retlist;
error:
@@ -1311,6 +1303,8 @@ error:
WTSFreeMemory(buffer_user);
if (buffer_addr != NULL)
WTSFreeMemory(buffer_addr);
+ if (buffer_info != NULL)
+ WTSFreeMemory(buffer_info);
return NULL;
}
@@ -1619,8 +1613,8 @@ PsutilMethods[] = {
"Determine if the process exists in the current process list."},
{"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS,
"Returns the number of logical CPUs on the system"},
- {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS,
- "Returns the number of physical CPUs on the system"},
+ {"cpu_count_cores", psutil_cpu_count_cores, METH_VARARGS,
+ "Returns the number of CPU cores on the system"},
{"boot_time", psutil_boot_time, METH_VARARGS,
"Return the system boot time expressed in seconds since the epoch."},
{"virtual_mem", psutil_virtual_mem, METH_VARARGS,
@@ -1674,7 +1668,7 @@ PsutilMethods[] = {
"Stop a service"},
// --- windows API bindings
- {"win32_QueryDosDevice", psutil_win32_QueryDosDevice, METH_VARARGS,
+ {"QueryDosDevice", psutil_QueryDosDevice, METH_VARARGS,
"QueryDosDevice binding"},
// --- others
diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py
index 7a7663e5..cf87e18e 100644
--- a/psutil/_pswindows.py
+++ b/psutil/_pswindows.py
@@ -199,7 +199,7 @@ def convert_dos_path(s):
"C:\Windows\systemew\file.txt"
"""
rawdrive = '\\'.join(s.split('\\')[:3])
- driveletter = cext.win32_QueryDosDevice(rawdrive)
+ driveletter = cext.QueryDosDevice(rawdrive)
remainder = s[len(rawdrive):]
return os.path.join(driveletter, remainder)
@@ -243,8 +243,16 @@ def virtual_memory():
def swap_memory():
"""Swap system memory as a (total, used, free, sin, sout) tuple."""
mem = cext.virtual_mem()
- total = mem[2]
- free = mem[3]
+
+ total_phys = mem[0]
+ free_phys = mem[1]
+ total_system = mem[2]
+ free_system = mem[3]
+
+ # Despite the name PageFile refers to total system memory here
+ # thus physical memory values need to be substracted to get swap values
+ total = total_system - total_phys
+ free = min(total, free_system - free_phys)
used = total - free
percent = usage_percent(used, total, round_=1)
return _common.sswap(total, used, free, percent, 0, 0)
@@ -311,9 +319,9 @@ def cpu_count_logical():
return cext.cpu_count_logical()
-def cpu_count_physical():
- """Return the number of physical CPU cores in the system."""
- return cext.cpu_count_phys()
+def cpu_count_cores():
+ """Return the number of CPU cores in the system."""
+ return cext.cpu_count_cores()
def cpu_stats():
@@ -1073,11 +1081,11 @@ class Process(object):
@wrap_exceptions
def cpu_affinity_set(self, value):
- def to_bitmask(l):
- if not l:
- raise ValueError("invalid argument %r" % l)
+ def to_bitmask(ls):
+ if not ls:
+ raise ValueError("invalid argument %r" % ls)
out = 0
- for b in l:
+ for b in ls:
out |= 2 ** b
return out
diff --git a/psutil/arch/freebsd/cpu.c b/psutil/arch/freebsd/cpu.c
new file mode 100644
index 00000000..f31e9bb0
--- /dev/null
+++ b/psutil/arch/freebsd/cpu.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009, Jay Loden, 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.
+ */
+
+/*
+System-wide CPU related functions.
+Original code was refactored and moved from psutil/arch/freebsd/specific.c
+in 2020 (and was moved in there previously already) from cset.
+a4c0a0eb0d2a872ab7a45e47fcf37ef1fde5b012
+For reference, here's the git history with original(ish) implementations:
+- CPU stats: fb0154ef164d0e5942ac85102ab660b8d2938fbb
+- CPU freq: 459556dd1e2979cdee22177339ced0761caf4c83
+- CPU cores: e0d6d7865df84dc9a1d123ae452fd311f79b1dde
+*/
+
+
+#include <Python.h>
+#include <sys/sysctl.h>
+
+#include "../../_psutil_common.h"
+#include "../../_psutil_posix.h"
+
+
+PyObject *
+psutil_cpu_topology(PyObject *self, PyObject *args) {
+ void *topology = NULL;
+ size_t size = 0;
+ PyObject *py_str;
+
+ if (sysctlbyname("kern.sched.topology_spec", NULL, &size, NULL, 0))
+ goto error;
+
+ topology = malloc(size);
+ if (!topology) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (sysctlbyname("kern.sched.topology_spec", topology, &size, NULL, 0))
+ goto error;
+
+ py_str = Py_BuildValue("s", topology);
+ free(topology);
+ return py_str;
+
+error:
+ if (topology != NULL)
+ free(topology);
+ Py_RETURN_NONE;
+}
+
+
+PyObject *
+psutil_cpu_stats(PyObject *self, PyObject *args) {
+ unsigned int v_soft;
+ unsigned int v_intr;
+ unsigned int v_syscall;
+ unsigned int v_trap;
+ unsigned int v_swtch;
+ size_t size = sizeof(v_soft);
+
+ if (sysctlbyname("vm.stats.sys.v_soft", &v_soft, &size, NULL, 0)) {
+ return PyErr_SetFromOSErrnoWithSyscall(
+ "sysctlbyname('vm.stats.sys.v_soft')");
+ }
+ if (sysctlbyname("vm.stats.sys.v_intr", &v_intr, &size, NULL, 0)) {
+ return PyErr_SetFromOSErrnoWithSyscall(
+ "sysctlbyname('vm.stats.sys.v_intr')");
+ }
+ if (sysctlbyname("vm.stats.sys.v_syscall", &v_syscall, &size, NULL, 0)) {
+ return PyErr_SetFromOSErrnoWithSyscall(
+ "sysctlbyname('vm.stats.sys.v_syscall')");
+ }
+ if (sysctlbyname("vm.stats.sys.v_trap", &v_trap, &size, NULL, 0)) {
+ return PyErr_SetFromOSErrnoWithSyscall(
+ "sysctlbyname('vm.stats.sys.v_trap')");
+ }
+ if (sysctlbyname("vm.stats.sys.v_swtch", &v_swtch, &size, NULL, 0)) {
+ return PyErr_SetFromOSErrnoWithSyscall(
+ "sysctlbyname('vm.stats.sys.v_swtch')");
+ }
+
+ return Py_BuildValue(
+ "IIIII",
+ v_swtch, // ctx switches
+ v_intr, // interrupts
+ v_soft, // software interrupts
+ v_syscall, // syscalls
+ v_trap // traps
+ );
+}
+
+
+/*
+ * Return frequency information of a given CPU.
+ * As of Dec 2018 only CPU 0 appears to be supported and all other
+ * cores match the frequency of CPU 0.
+ */
+PyObject *
+psutil_cpu_freq(PyObject *self, PyObject *args) {
+ int current;
+ int core;
+ char sensor[26];
+ char available_freq_levels[1000];
+ size_t size = sizeof(current);
+
+ if (! PyArg_ParseTuple(args, "i", &core))
+ return NULL;
+ // https://www.unix.com/man-page/FreeBSD/4/cpufreq/
+ sprintf(sensor, "dev.cpu.%d.freq", core);
+ if (sysctlbyname(sensor, &current, &size, NULL, 0))
+ goto error;
+
+ size = sizeof(available_freq_levels);
+ // https://www.unix.com/man-page/FreeBSD/4/cpufreq/
+ // In case of failure, an empty string is returned.
+ sprintf(sensor, "dev.cpu.%d.freq_levels", core);
+ sysctlbyname(sensor, &available_freq_levels, &size, NULL, 0);
+
+ return Py_BuildValue("is", current, available_freq_levels);
+
+error:
+ if (errno == ENOENT)
+ PyErr_SetString(PyExc_NotImplementedError, "unable to read frequency");
+ else
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+}
diff --git a/psutil/arch/freebsd/cpu.h b/psutil/arch/freebsd/cpu.h
new file mode 100644
index 00000000..8decd773
--- /dev/null
+++ b/psutil/arch/freebsd/cpu.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <Python.h>
+
+PyObject* psutil_cpu_freq(PyObject* self, PyObject* args);
+PyObject* psutil_cpu_stats(PyObject* self, PyObject* args);
+PyObject* psutil_cpu_topology(PyObject* self, PyObject* args);
diff --git a/psutil/arch/freebsd/specific.c b/psutil/arch/freebsd/specific.c
index 3f37a08e..423f0c7b 100644
--- a/psutil/arch/freebsd/specific.c
+++ b/psutil/arch/freebsd/specific.c
@@ -262,7 +262,7 @@ psutil_proc_exe(PyObject *self, PyObject *args) {
if (ret == -1)
return NULL;
else if (ret == 0)
- return NoSuchProcess("psutil_pid_exists");
+ return NoSuchProcess("psutil_pid_exists -> 0");
else
strcpy(pathname, "");
}
@@ -363,37 +363,6 @@ error:
}
-PyObject *
-psutil_cpu_count_phys(PyObject *self, PyObject *args) {
- // Return an XML string from which we'll determine the number of
- // physical CPU cores in the system.
- void *topology = NULL;
- size_t size = 0;
- PyObject *py_str;
-
- if (sysctlbyname("kern.sched.topology_spec", NULL, &size, NULL, 0))
- goto error;
-
- topology = malloc(size);
- if (!topology) {
- PyErr_NoMemory();
- return NULL;
- }
-
- if (sysctlbyname("kern.sched.topology_spec", topology, &size, NULL, 0))
- goto error;
-
- py_str = Py_BuildValue("s", topology);
- free(topology);
- return py_str;
-
-error:
- if (topology != NULL)
- free(topology);
- Py_RETURN_NONE;
-}
-
-
/*
* Return virtual memory usage statistics.
*/
@@ -404,7 +373,7 @@ psutil_virtual_mem(PyObject *self, PyObject *args) {
size_t size = sizeof(total);
struct vmtotal vm;
int mib[] = {CTL_VM, VM_METER};
- long pagesize = getpagesize();
+ long pagesize = psutil_getpagesize();
#if __FreeBSD_version > 702101
long buffers;
#else
@@ -465,7 +434,7 @@ psutil_swap_mem(PyObject *self, PyObject *args) {
struct kvm_swap kvmsw[1];
unsigned int swapin, swapout, nodein, nodeout;
size_t size = sizeof(unsigned int);
- int pagesize;
+ long pagesize = psutil_getpagesize();
kd = kvm_open(NULL, _PATH_DEVNULL, NULL, O_RDONLY, "kvm_open failed");
if (kd == NULL) {
@@ -499,12 +468,6 @@ psutil_swap_mem(PyObject *self, PyObject *args) {
"sysctlbyname('vm.stats.vm.v_vnodeout)'");
}
- pagesize = getpagesize();
- if (pagesize <= 0) {
- PyErr_SetString(PyExc_ValueError, "invalid getpagesize()");
- return NULL;
- }
-
return Py_BuildValue(
"(KKKII)",
(unsigned long long)kvmsw[0].ksw_total * pagesize, // total
@@ -517,7 +480,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 +758,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;
@@ -936,47 +901,6 @@ error:
}
-PyObject *
-psutil_cpu_stats(PyObject *self, PyObject *args) {
- unsigned int v_soft;
- unsigned int v_intr;
- unsigned int v_syscall;
- unsigned int v_trap;
- unsigned int v_swtch;
- size_t size = sizeof(v_soft);
-
- if (sysctlbyname("vm.stats.sys.v_soft", &v_soft, &size, NULL, 0)) {
- return PyErr_SetFromOSErrnoWithSyscall(
- "sysctlbyname('vm.stats.sys.v_soft')");
- }
- if (sysctlbyname("vm.stats.sys.v_intr", &v_intr, &size, NULL, 0)) {
- return PyErr_SetFromOSErrnoWithSyscall(
- "sysctlbyname('vm.stats.sys.v_intr')");
- }
- if (sysctlbyname("vm.stats.sys.v_syscall", &v_syscall, &size, NULL, 0)) {
- return PyErr_SetFromOSErrnoWithSyscall(
- "sysctlbyname('vm.stats.sys.v_syscall')");
- }
- if (sysctlbyname("vm.stats.sys.v_trap", &v_trap, &size, NULL, 0)) {
- return PyErr_SetFromOSErrnoWithSyscall(
- "sysctlbyname('vm.stats.sys.v_trap')");
- }
- if (sysctlbyname("vm.stats.sys.v_swtch", &v_swtch, &size, NULL, 0)) {
- return PyErr_SetFromOSErrnoWithSyscall(
- "sysctlbyname('vm.stats.sys.v_swtch')");
- }
-
- return Py_BuildValue(
- "IIIII",
- v_swtch, // ctx switches
- v_intr, // interrupts
- v_soft, // software interrupts
- v_syscall, // syscalls
- v_trap // traps
- );
-}
-
-
/*
* Return battery information.
*/
@@ -1041,37 +965,85 @@ error:
/*
- * Return frequency information of a given CPU.
- * As of Dec 2018 only CPU 0 appears to be supported and all other
- * cores match the frequency of CPU 0.
+ * An emulation of Linux prlimit(). Returns a (soft, hard) tuple.
*/
PyObject *
-psutil_cpu_freq(PyObject *self, PyObject *args) {
- int current;
- int core;
- char sensor[26];
- char available_freq_levels[1000];
- size_t size = sizeof(current);
+psutil_proc_getrlimit(PyObject *self, PyObject *args) {
+ pid_t pid;
+ int ret;
+ int resource;
+ size_t len;
+ int name[5];
+ struct rlimit rlp;
- if (! PyArg_ParseTuple(args, "i", &core))
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID "i", &pid, &resource))
return NULL;
- // https://www.unix.com/man-page/FreeBSD/4/cpufreq/
- sprintf(sensor, "dev.cpu.%d.freq", core);
- if (sysctlbyname(sensor, &current, &size, NULL, 0))
- goto error;
- size = sizeof(available_freq_levels);
- // https://www.unix.com/man-page/FreeBSD/4/cpufreq/
- // In case of failure, an empty string is returned.
- sprintf(sensor, "dev.cpu.%d.freq_levels", core);
- sysctlbyname(sensor, &available_freq_levels, &size, NULL, 0);
+ name[0] = CTL_KERN;
+ name[1] = KERN_PROC;
+ name[2] = KERN_PROC_RLIMIT;
+ name[3] = pid;
+ name[4] = resource;
+ len = sizeof(rlp);
- return Py_BuildValue("is", current, available_freq_levels);
+ ret = sysctl(name, 5, &rlp, &len, NULL, 0);
+ if (ret == -1)
+ return PyErr_SetFromErrno(PyExc_OSError);
-error:
- if (errno == ENOENT)
- PyErr_SetString(PyExc_NotImplementedError, "unable to read frequency");
- else
- PyErr_SetFromErrno(PyExc_OSError);
- return NULL;
+#if defined(HAVE_LONG_LONG)
+ return Py_BuildValue("LL",
+ (PY_LONG_LONG) rlp.rlim_cur,
+ (PY_LONG_LONG) rlp.rlim_max);
+#else
+ return Py_BuildValue("ll",
+ (long) rlp.rlim_cur,
+ (long) rlp.rlim_max);
+#endif
+}
+
+
+/*
+ * An emulation of Linux prlimit() (set).
+ */
+PyObject *
+psutil_proc_setrlimit(PyObject *self, PyObject *args) {
+ pid_t pid;
+ int ret;
+ int resource;
+ int name[5];
+ struct rlimit new;
+ struct rlimit *newp = NULL;
+ PyObject *py_soft = NULL;
+ PyObject *py_hard = NULL;
+
+ if (! PyArg_ParseTuple(
+ args, _Py_PARSE_PID "iOO", &pid, &resource, &py_soft, &py_hard))
+ return NULL;
+
+ name[0] = CTL_KERN;
+ name[1] = KERN_PROC;
+ name[2] = KERN_PROC_RLIMIT;
+ name[3] = pid;
+ name[4] = resource;
+
+#if defined(HAVE_LONG_LONG)
+ new.rlim_cur = PyLong_AsLongLong(py_soft);
+ if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred())
+ return NULL;
+ new.rlim_max = PyLong_AsLongLong(py_hard);
+ if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred())
+ return NULL;
+#else
+ new.rlim_cur = PyLong_AsLong(py_soft);
+ if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred())
+ return NULL;
+ new.rlim_max = PyLong_AsLong(py_hard);
+ if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred())
+ return NULL;
+#endif
+ newp = &new;
+ ret = sysctl(name, 5, NULL, 0, newp, sizeof(*newp));
+ if (ret == -1)
+ return PyErr_SetFromErrno(PyExc_OSError);
+ Py_RETURN_NONE;
}
diff --git a/psutil/arch/freebsd/specific.h b/psutil/arch/freebsd/specific.h
index 875c8166..57f0a2a4 100644
--- a/psutil/arch/freebsd/specific.h
+++ b/psutil/arch/freebsd/specific.h
@@ -11,8 +11,6 @@ typedef struct kinfo_proc kinfo_proc;
int psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount);
int psutil_kinfo_proc(const pid_t pid, struct kinfo_proc *proc);
-//
-PyObject* psutil_cpu_count_phys(PyObject* self, PyObject* args);
PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args);
PyObject* psutil_get_cmdline(long pid);
PyObject* psutil_per_cpu_times(PyObject* self, PyObject* args);
@@ -20,15 +18,13 @@ PyObject* psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args);
PyObject* psutil_proc_cpu_affinity_set(PyObject* self, PyObject* args);
PyObject* psutil_proc_cwd(PyObject* self, PyObject* args);
PyObject* psutil_proc_exe(PyObject* self, PyObject* args);
+PyObject* psutil_proc_getrlimit(PyObject* self, PyObject* args);
PyObject* psutil_proc_memory_maps(PyObject* self, PyObject* args);
PyObject* psutil_proc_num_fds(PyObject* self, PyObject* args);
PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args);
+PyObject* psutil_proc_setrlimit(PyObject* self, PyObject* args);
PyObject* psutil_proc_threads(PyObject* self, PyObject* args);
-PyObject* psutil_swap_mem(PyObject* self, PyObject* args);
-PyObject* psutil_virtual_mem(PyObject* self, PyObject* args);
-PyObject* psutil_cpu_stats(PyObject* self, PyObject* args);
-#if defined(PSUTIL_FREEBSD)
PyObject* psutil_sensors_battery(PyObject* self, PyObject* args);
PyObject* psutil_sensors_cpu_temperature(PyObject* self, PyObject* args);
-PyObject* psutil_cpu_freq(PyObject* self, PyObject* args);
-#endif
+PyObject* psutil_swap_mem(PyObject* self, PyObject* args);
+PyObject* psutil_virtual_mem(PyObject* self, PyObject* args);
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/netbsd/specific.c b/psutil/arch/netbsd/specific.c
index 9dab3618..4e286e5e 100644
--- a/psutil/arch/netbsd/specific.c
+++ b/psutil/arch/netbsd/specific.c
@@ -126,7 +126,7 @@ psutil_proc_cwd(PyObject *self, PyObject *args) {
int name[] = { CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_CWD};
if (sysctl(name, 4, path, &pathlen, NULL, 0) != 0) {
if (errno == ENOENT)
- NoSuchProcess("");
+ NoSuchProcess("sysctl -> ENOENT");
else
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
@@ -142,7 +142,7 @@ psutil_proc_cwd(PyObject *self, PyObject *args) {
free(buf);
if (len == -1) {
if (errno == ENOENT)
- NoSuchProcess("readlink (ENOENT)");
+ NoSuchProcess("readlink -> ENOENT");
else
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
@@ -198,7 +198,7 @@ psutil_proc_exe(PyObject *self, PyObject *args) {
if (ret == -1)
return NULL;
else if (ret == 0)
- return NoSuchProcess("psutil_pid_exists");
+ return NoSuchProcess("psutil_pid_exists -> 0");
else
strcpy(pathname, "");
}
@@ -445,7 +445,7 @@ psutil_virtual_mem(PyObject *self, PyObject *args) {
size_t size;
struct uvmexp_sysctl uv;
int mib[] = {CTL_VM, VM_UVMEXP2};
- long pagesize = getpagesize();
+ long pagesize = psutil_getpagesize();
size = sizeof(uv);
if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) {
@@ -472,6 +472,7 @@ psutil_swap_mem(PyObject *self, PyObject *args) {
uint64_t swap_total, swap_free;
struct swapent *swdev;
int nswap, i;
+ long pagesize = psutil_getpagesize();
nswap = swapctl(SWAP_NSWAP, 0, 0);
if (nswap == 0) {
@@ -505,7 +506,6 @@ psutil_swap_mem(PyObject *self, PyObject *args) {
size_t size = sizeof(total);
struct uvmexp_sysctl uv;
int mib[] = {CTL_VM, VM_UVMEXP2};
- long pagesize = getpagesize();
size = sizeof(uv);
if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) {
PyErr_SetFromErrno(PyExc_OSError);
diff --git a/psutil/arch/openbsd/specific.c b/psutil/arch/openbsd/specific.c
index d97a8f9b..cdd4f623 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.
@@ -311,7 +297,7 @@ psutil_virtual_mem(PyObject *self, PyObject *args) {
struct uvmexp uvmexp;
struct bcachestats bcstats;
struct vmtotal vmdata;
- long pagesize = getpagesize();
+ long pagesize = psutil_getpagesize();
size = sizeof(total_physmem);
if (sysctl(physmem_mib, 2, &total_physmem, &size, NULL, 0) < 0) {
diff --git a/psutil/arch/osx/cpu.c b/psutil/arch/osx/cpu.c
new file mode 100644
index 00000000..37141a2d
--- /dev/null
+++ b/psutil/arch/osx/cpu.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2009, Jay Loden, 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.
+ */
+
+/*
+System-wide CPU related functions.
+
+Original code was refactored and moved from psutil/_psutil_osx.c in 2020
+right before a4c0a0eb0d2a872ab7a45e47fcf37ef1fde5b012.
+For reference, here's the git history with original implementations:
+
+- CPU count logical: 3d291d425b856077e65163e43244050fb188def1
+- CPU count physical: 4263e354bb4984334bc44adf5dd2f32013d69fba
+- CPU times: 32488bdf54aed0f8cef90d639c1667ffaa3c31c7
+- CPU stat: fa00dfb961ef63426c7818899340866ced8d2418
+- CPU frequency: 6ba1ac4ebfcd8c95fca324b15606ab0ec1412d39
+*/
+
+#include <Python.h>
+#include <sys/sysctl.h>
+#include <sys/vmmeter.h>
+
+#include <mach/mach_error.h>
+#include <mach/mach_host.h>
+#include <mach/mach_port.h>
+
+#include "../../_psutil_common.h"
+#include "../../_psutil_posix.h"
+
+
+
+PyObject *
+psutil_cpu_count_logical(PyObject *self, PyObject *args) {
+ int num;
+ size_t size = sizeof(int);
+
+ if (sysctlbyname("hw.logicalcpu", &num, &size, NULL, 2))
+ Py_RETURN_NONE; // mimic os.cpu_count()
+ else
+ return Py_BuildValue("i", num);
+}
+
+
+PyObject *
+psutil_cpu_count_cores(PyObject *self, PyObject *args) {
+ int num;
+ size_t size = sizeof(int);
+
+ if (sysctlbyname("hw.physicalcpu", &num, &size, NULL, 0))
+ Py_RETURN_NONE; // mimic os.cpu_count()
+ else
+ return Py_BuildValue("i", num);
+}
+
+
+PyObject *
+psutil_cpu_times(PyObject *self, PyObject *args) {
+ mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT;
+ kern_return_t error;
+ host_cpu_load_info_data_t r_load;
+
+ mach_port_t host_port = mach_host_self();
+ error = host_statistics(host_port, HOST_CPU_LOAD_INFO,
+ (host_info_t)&r_load, &count);
+ if (error != KERN_SUCCESS) {
+ return PyErr_Format(
+ PyExc_RuntimeError,
+ "host_statistics(HOST_CPU_LOAD_INFO) syscall failed: %s",
+ mach_error_string(error));
+ }
+ mach_port_deallocate(mach_task_self(), host_port);
+
+ return Py_BuildValue(
+ "(dddd)",
+ (double)r_load.cpu_ticks[CPU_STATE_USER] / CLK_TCK,
+ (double)r_load.cpu_ticks[CPU_STATE_NICE] / CLK_TCK,
+ (double)r_load.cpu_ticks[CPU_STATE_SYSTEM] / CLK_TCK,
+ (double)r_load.cpu_ticks[CPU_STATE_IDLE] / CLK_TCK
+ );
+}
+
+
+PyObject *
+psutil_cpu_stats(PyObject *self, PyObject *args) {
+ struct vmmeter vmstat;
+ kern_return_t ret;
+ mach_msg_type_number_t count = sizeof(vmstat) / sizeof(integer_t);
+ mach_port_t mport = mach_host_self();
+
+ ret = host_statistics(mport, HOST_VM_INFO, (host_info_t)&vmstat, &count);
+ if (ret != KERN_SUCCESS) {
+ PyErr_Format(
+ PyExc_RuntimeError,
+ "host_statistics(HOST_VM_INFO) failed: %s",
+ mach_error_string(ret));
+ return NULL;
+ }
+ mach_port_deallocate(mach_task_self(), mport);
+
+ return Py_BuildValue(
+ "IIIII",
+ vmstat.v_swtch, // ctx switches
+ vmstat.v_intr, // interrupts
+ vmstat.v_soft, // software interrupts
+ vmstat.v_syscall, // syscalls
+ vmstat.v_trap // traps
+ );
+}
+
+
+PyObject *
+psutil_cpu_freq(PyObject *self, PyObject *args) {
+ unsigned int curr;
+ int64_t min = 0;
+ int64_t max = 0;
+ int mib[2];
+ size_t len = sizeof(curr);
+ size_t size = sizeof(min);
+
+ // also availble as "hw.cpufrequency" but it's deprecated
+ mib[0] = CTL_HW;
+ mib[1] = HW_CPU_FREQ;
+
+ if (sysctl(mib, 2, &curr, &len, NULL, 0) < 0)
+ return PyErr_SetFromOSErrnoWithSyscall("sysctl(HW_CPU_FREQ)");
+
+ if (sysctlbyname("hw.cpufrequency_min", &min, &size, NULL, 0))
+ psutil_debug("sysct('hw.cpufrequency_min') failed (set to 0)");
+
+ if (sysctlbyname("hw.cpufrequency_max", &max, &size, NULL, 0))
+ psutil_debug("sysctl('hw.cpufrequency_min') failed (set to 0)");
+
+ return Py_BuildValue(
+ "IKK",
+ curr / 1000 / 1000,
+ min / 1000 / 1000,
+ max / 1000 / 1000);
+}
diff --git a/psutil/arch/osx/cpu.h b/psutil/arch/osx/cpu.h
new file mode 100644
index 00000000..aac0f809
--- /dev/null
+++ b/psutil/arch/osx/cpu.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <Python.h>
+
+PyObject *psutil_cpu_count_logical(PyObject *self, PyObject *args);
+PyObject *psutil_cpu_count_cores(PyObject *self, PyObject *args);
+PyObject *psutil_cpu_times(PyObject *self, PyObject *args);
+PyObject *psutil_cpu_freq(PyObject *self, PyObject *args);
+PyObject *psutil_cpu_stats(PyObject *self, PyObject *args);
diff --git a/psutil/arch/osx/process_info.c b/psutil/arch/osx/process_info.c
index 4b84a723..15ad3b89 100644
--- a/psutil/arch/osx/process_info.c
+++ b/psutil/arch/osx/process_info.c
@@ -9,13 +9,7 @@
#include <Python.h>
-#include <assert.h>
#include <errno.h>
-#include <limits.h> // for INT_MAX
-#include <stdbool.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <signal.h>
#include <sys/sysctl.h>
#include <libproc.h>
@@ -23,6 +17,7 @@
#include "../../_psutil_posix.h"
#include "process_info.h"
+
/*
* Returns a list of all BSD processes on the system. This routine
* allocates the list and puts it in *procList and a count of the
@@ -33,16 +28,15 @@
*/
int
psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) {
- int mib3[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
+ int mib[3];
size_t size, size2;
void *ptr;
int err;
int lim = 8; // some limit
- assert( procList != NULL);
- assert(*procList == NULL);
- assert(procCount != NULL);
-
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_ALL;
*procCount = 0;
/*
@@ -59,7 +53,7 @@ psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) {
*/
while (lim-- > 0) {
size = 0;
- if (sysctl((int *)mib3, 3, NULL, &size, NULL, 0) == -1) {
+ if (sysctl((int *)mib, 3, NULL, &size, NULL, 0) == -1) {
PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)");
return 1;
}
@@ -79,7 +73,7 @@ psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) {
return 1;
}
- if (sysctl((int *)mib3, 3, ptr, &size, NULL, 0) == -1) {
+ if (sysctl((int *)mib, 3, ptr, &size, NULL, 0) == -1) {
err = errno;
free(ptr);
if (err != ENOMEM) {
@@ -104,12 +98,15 @@ psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) {
// Read the maximum argument size for processes
-int
-psutil_get_argmax() {
+static int
+psutil_sysctl_argmax() {
int argmax;
- int mib[] = { CTL_KERN, KERN_ARGMAX };
+ int mib[2];
size_t size = sizeof(argmax);
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_ARGMAX;
+
if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0)
return argmax;
PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_ARGMAX)");
@@ -117,6 +114,40 @@ psutil_get_argmax() {
}
+// Read process argument space.
+static int
+psutil_sysctl_procargs(pid_t pid, char *procargs, size_t argmax) {
+ int mib[3];
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROCARGS2;
+ mib[2] = pid;
+
+ if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
+ if (psutil_pid_exists(pid) == 0) {
+ NoSuchProcess("psutil_pid_exists -> 0");
+ return 1;
+ }
+ // In case of zombie process we'll get EINVAL. We translate it
+ // to NSP and _psosx.py will translate it to ZP.
+ if (errno == EINVAL) {
+ psutil_debug("sysctl(KERN_PROCARGS2) -> EINVAL translated to NSP");
+ NoSuchProcess("sysctl(KERN_PROCARGS2) -> EINVAL");
+ return 1;
+ }
+ // There's nothing we can do other than raising AD.
+ if (errno == EIO) {
+ psutil_debug("sysctl(KERN_PROCARGS2) -> EIO translated to AD");
+ AccessDenied("sysctl(KERN_PROCARGS2) -> EIO");
+ return 1;
+ }
+ PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROCARGS2)");
+ return 1;
+ }
+ return 0;
+}
+
+
// Return 1 if pid refers to a zombie process else 0.
int
psutil_is_zombie(pid_t pid) {
@@ -128,11 +159,9 @@ psutil_is_zombie(pid_t pid) {
}
-
// return process args as a python list
PyObject *
psutil_get_cmdline(pid_t pid) {
- int mib[3];
int nargs;
size_t len;
char *procargs = NULL;
@@ -149,7 +178,7 @@ psutil_get_cmdline(pid_t pid) {
return Py_BuildValue("[]");
// read argmax and allocate memory for argument space.
- argmax = psutil_get_argmax();
+ argmax = psutil_sysctl_argmax();
if (! argmax)
goto error;
@@ -159,19 +188,8 @@ psutil_get_cmdline(pid_t pid) {
goto error;
}
- // read argument space
- mib[0] = CTL_KERN;
- mib[1] = KERN_PROCARGS2;
- mib[2] = pid;
- if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
- // In case of zombie process we'll get EINVAL. We translate it
- // to NSP and _psosx.py will translate it to ZP.
- if ((errno == EINVAL) && (psutil_pid_exists(pid)))
- NoSuchProcess("sysctl");
- else
- PyErr_SetFromErrno(PyExc_OSError);
+ if (psutil_sysctl_procargs(pid, procargs, argmax) != 0)
goto error;
- }
arg_end = &procargs[argmax];
// copy the number of arguments to nargs
@@ -226,7 +244,6 @@ error:
// return process environment as a python string
PyObject *
psutil_get_environ(pid_t pid) {
- int mib[3];
int nargs;
char *procargs = NULL;
char *procenv = NULL;
@@ -241,7 +258,7 @@ psutil_get_environ(pid_t pid) {
goto empty;
// read argmax and allocate memory for argument space.
- argmax = psutil_get_argmax();
+ argmax = psutil_sysctl_argmax();
if (! argmax)
goto error;
@@ -251,19 +268,8 @@ psutil_get_environ(pid_t pid) {
goto error;
}
- // read argument space
- mib[0] = CTL_KERN;
- mib[1] = KERN_PROCARGS2;
- mib[2] = pid;
- if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
- // In case of zombie process we'll get EINVAL. We translate it
- // to NSP and _psosx.py will translate it to ZP.
- if ((errno == EINVAL) && (psutil_pid_exists(pid)))
- NoSuchProcess("sysctl");
- else
- PyErr_SetFromErrno(PyExc_OSError);
+ if (psutil_sysctl_procargs(pid, procargs, argmax) != 0)
goto error;
- }
arg_end = &procargs[argmax];
// copy the number of arguments to nargs
@@ -299,12 +305,9 @@ psutil_get_environ(pid_t pid) {
while (*arg_ptr != '\0' && arg_ptr < arg_end) {
char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr);
-
if (s == NULL)
break;
-
memcpy(procenv + (arg_ptr - env_start), arg_ptr, s - arg_ptr);
-
arg_ptr = s + 1;
}
@@ -320,7 +323,6 @@ psutil_get_environ(pid_t pid) {
free(procargs);
free(procenv);
-
return py_ret;
empty:
@@ -353,7 +355,7 @@ psutil_get_kinfo_proc(pid_t pid, struct kinfo_proc *kp) {
// now read the data from sysctl
if (sysctl(mib, 4, kp, &len, NULL, 0) == -1) {
// raise an exception and throw errno as the error
- PyErr_SetFromErrno(PyExc_OSError);
+ PyErr_SetFromOSErrnoWithSyscall("sysctl");
return -1;
}
@@ -368,14 +370,21 @@ psutil_get_kinfo_proc(pid_t pid, struct kinfo_proc *kp) {
/*
* A wrapper around proc_pidinfo().
+ * https://opensource.apple.com/source/xnu/xnu-2050.7.9/bsd/kern/proc_info.c.
* Returns 0 on failure (and Python exception gets already set).
*/
int
psutil_proc_pidinfo(pid_t pid, int flavor, uint64_t arg, void *pti, int size) {
errno = 0;
- int ret = proc_pidinfo(pid, flavor, arg, pti, size);
- if ((ret <= 0) || ((unsigned long)ret < sizeof(pti))) {
- psutil_raise_for_pid(pid, "proc_pidinfo()");
+ int ret;
+
+ ret = proc_pidinfo(pid, flavor, arg, pti, size);
+ if (ret <= 0) {
+ psutil_raise_for_pid(pid, "proc_pidinfo() failed");
+ return 0;
+ }
+ else if ((unsigned long )ret < sizeof(pti)) {
+ psutil_raise_for_pid(pid, "proc_pidinfo() len mismatch");
return 0;
}
return ret;
diff --git a/psutil/arch/osx/process_info.h b/psutil/arch/osx/process_info.h
index 35755247..ffa6230f 100644
--- a/psutil/arch/osx/process_info.h
+++ b/psutil/arch/osx/process_info.h
@@ -8,7 +8,6 @@
typedef struct kinfo_proc kinfo_proc;
-int psutil_get_argmax(void);
int psutil_is_zombie(pid_t pid);
int psutil_get_kinfo_proc(pid_t pid, struct kinfo_proc *kp);
int psutil_get_proc_list(kinfo_proc **procList, size_t *procCount);
diff --git a/psutil/arch/windows/cpu.c b/psutil/arch/windows/cpu.c
index 18f32e59..355de6df 100644
--- a/psutil/arch/windows/cpu.c
+++ b/psutil/arch/windows/cpu.c
@@ -177,11 +177,10 @@ psutil_cpu_count_logical(PyObject *self, PyObject *args) {
/*
- * Return the number of physical CPU cores (hyper-thread CPUs count
- * is excluded).
+ * Return the number of CPU cores (non hyper-threading).
*/
PyObject *
-psutil_cpu_count_phys(PyObject *self, PyObject *args) {
+psutil_cpu_count_cores(PyObject *self, PyObject *args) {
DWORD rc;
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX buffer = NULL;
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX ptr = NULL;
@@ -196,7 +195,7 @@ psutil_cpu_count_phys(PyObject *self, PyObject *args) {
// than 64 CPUs. See:
// https://bugs.python.org/issue33166
if (GetLogicalProcessorInformationEx == NULL) {
- psutil_debug("Win < 7; cpu_count_phys() forced to None");
+ psutil_debug("Win < 7; cpu_count_cores() forced to None");
Py_RETURN_NONE;
}
diff --git a/psutil/arch/windows/cpu.h b/psutil/arch/windows/cpu.h
index d88c2212..1ef3ff1f 100644
--- a/psutil/arch/windows/cpu.h
+++ b/psutil/arch/windows/cpu.h
@@ -7,7 +7,7 @@
#include <Python.h>
PyObject *psutil_cpu_count_logical(PyObject *self, PyObject *args);
-PyObject *psutil_cpu_count_phys(PyObject *self, PyObject *args);
+PyObject *psutil_cpu_count_cores(PyObject *self, PyObject *args);
PyObject *psutil_cpu_freq(PyObject *self, PyObject *args);
PyObject *psutil_cpu_stats(PyObject *self, PyObject *args);
PyObject *psutil_cpu_times(PyObject *self, PyObject *args);
diff --git a/psutil/arch/windows/disk.c b/psutil/arch/windows/disk.c
index 45e0ee1e..29bd0a20 100644
--- a/psutil/arch/windows/disk.c
+++ b/psutil/arch/windows/disk.c
@@ -206,6 +206,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
BOOL mp_flag= TRUE;
LPTSTR fs_type[MAX_PATH + 1] = { 0 };
DWORD pflags = 0;
+ DWORD lpMaximumComponentLength = 0; // max file name
PyObject *py_all;
PyObject *py_retlist = PyList_New(0);
PyObject *py_tuple = NULL;
@@ -257,8 +258,14 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
}
ret = GetVolumeInformation(
- (LPCTSTR)drive_letter, NULL, _ARRAYSIZE(drive_letter),
- NULL, NULL, &pflags, (LPTSTR)fs_type, _ARRAYSIZE(fs_type));
+ (LPCTSTR)drive_letter,
+ NULL,
+ _ARRAYSIZE(drive_letter),
+ NULL,
+ &lpMaximumComponentLength,
+ &pflags,
+ (LPTSTR)fs_type,
+ _ARRAYSIZE(fs_type));
if (ret == 0) {
// We might get here in case of a floppy hard drive, in
// which case the error is (21, "device not ready").
@@ -274,6 +281,8 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
strcat_s(opts, _countof(opts), "rw");
if (pflags & FILE_VOLUME_IS_COMPRESSED)
strcat_s(opts, _countof(opts), ",compressed");
+ if (pflags & FILE_READ_ONLY_VOLUME)
+ strcat_s(opts, _countof(opts), ",readonly");
// Check for mount points on this volume and add/get info
// (checks first to know if we can even have mount points)
@@ -282,17 +291,19 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
drive_letter, mp_buf, MAX_PATH);
if (mp_h != INVALID_HANDLE_VALUE) {
while (mp_flag) {
-
// Append full mount path with drive letter
strcpy_s(mp_path, _countof(mp_path), drive_letter);
strcat_s(mp_path, _countof(mp_path), mp_buf);
py_tuple = Py_BuildValue(
- "(ssss)",
+ "(ssssIi)",
drive_letter,
mp_path,
- fs_type, // Typically NTFS
- opts);
+ fs_type, // typically "NTFS"
+ opts,
+ lpMaximumComponentLength, // max file length
+ MAX_PATH // max path length
+ );
if (!py_tuple ||
PyList_Append(py_retlist, py_tuple) == -1) {
@@ -317,11 +328,14 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
strcat_s(opts, _countof(opts), psutil_get_drive_type(type));
py_tuple = Py_BuildValue(
- "(ssss)",
+ "(ssssIi)",
drive_letter,
drive_letter,
fs_type, // either FAT, FAT32, NTFS, HPFS, CDFS, UDF or NWFS
- opts);
+ opts,
+ lpMaximumComponentLength, // max file length
+ MAX_PATH // max path length
+ );
if (!py_tuple)
goto error;
if (PyList_Append(py_retlist, py_tuple))
@@ -350,7 +364,7 @@ error:
If no match is found return an empty string.
*/
PyObject *
-psutil_win32_QueryDosDevice(PyObject *self, PyObject *args) {
+psutil_QueryDosDevice(PyObject *self, PyObject *args) {
LPCTSTR lpDevicePath;
TCHAR d = TEXT('A');
TCHAR szBuff[5];
diff --git a/psutil/arch/windows/disk.h b/psutil/arch/windows/disk.h
index 298fb6ba..28bed22b 100644
--- a/psutil/arch/windows/disk.h
+++ b/psutil/arch/windows/disk.h
@@ -9,4 +9,4 @@
PyObject *psutil_disk_io_counters(PyObject *self, PyObject *args);
PyObject *psutil_disk_partitions(PyObject *self, PyObject *args);
PyObject *psutil_disk_usage(PyObject *self, PyObject *args);
-PyObject *psutil_win32_QueryDosDevice(PyObject *self, PyObject *args);
+PyObject *psutil_QueryDosDevice(PyObject *self, PyObject *args);
diff --git a/psutil/arch/windows/net.c b/psutil/arch/windows/net.c
index 0891bdba..8d8f7d1c 100644
--- a/psutil/arch/windows/net.c
+++ b/psutil/arch/windows/net.c
@@ -16,42 +16,35 @@
static PIP_ADAPTER_ADDRESSES
-psutil_get_nic_addresses() {
- // Note: GetAdaptersAddresses() increase the handle count on first
- // call (and not anymore later on).
- // allocate a 15 KB buffer to start with
- int outBufLen = 15000;
- DWORD dwRetVal = 0;
- ULONG attempts = 0;
- PIP_ADAPTER_ADDRESSES pAddresses = NULL;
-
- do {
- pAddresses = (IP_ADAPTER_ADDRESSES *) malloc(outBufLen);
- if (pAddresses == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
-
- dwRetVal = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses,
- &outBufLen);
- if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
- free(pAddresses);
- pAddresses = NULL;
- }
- else {
- break;
- }
-
- attempts++;
- } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (attempts < 3));
+psutil_get_nic_addresses(void) {
+ ULONG bufferLength = 0;
+ PIP_ADAPTER_ADDRESSES buffer;
+
+ if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, NULL, &bufferLength)
+ != ERROR_BUFFER_OVERFLOW)
+ {
+ PyErr_SetString(PyExc_RuntimeError,
+ "GetAdaptersAddresses() syscall failed.");
+ return NULL;
+ }
- if (dwRetVal != NO_ERROR) {
- PyErr_SetString(
- PyExc_RuntimeError, "GetAdaptersAddresses() syscall failed.");
+ buffer = malloc(bufferLength);
+ if (buffer == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ memset(buffer, 0, bufferLength);
+
+ if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, buffer, &bufferLength)
+ != ERROR_SUCCESS)
+ {
+ free(buffer);
+ PyErr_SetString(PyExc_RuntimeError,
+ "GetAdaptersAddresses() syscall failed.");
return NULL;
}
- return pAddresses;
+ return buffer;
}
@@ -191,11 +184,11 @@ psutil_net_if_addrs(PyObject *self, PyObject *args) {
for (i = 0; i < (int) pCurrAddresses->PhysicalAddressLength; i++) {
if (i == (pCurrAddresses->PhysicalAddressLength - 1)) {
sprintf_s(ptr, _countof(buff_macaddr), "%.2X\n",
- (int)pCurrAddresses->PhysicalAddress[i]);
+ (int)pCurrAddresses->PhysicalAddress[i]);
}
else {
sprintf_s(ptr, _countof(buff_macaddr), "%.2X-",
- (int)pCurrAddresses->PhysicalAddress[i]);
+ (int)pCurrAddresses->PhysicalAddress[i]);
}
ptr += 3;
}
@@ -325,8 +318,7 @@ error:
/*
* Provides stats about NIC interfaces installed on the system.
- * TODO: get 'duplex' (currently it's hard coded to '2', aka
- 'full duplex')
+ * TODO: get 'duplex' (currently it's hard coded to '2', aka 'full duplex')
*/
PyObject *
psutil_net_if_stats(PyObject *self, PyObject *args) {
diff --git a/psutil/arch/windows/ntextapi.h b/psutil/arch/windows/ntextapi.h
index 8cb00430..e0662fa0 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,19 +362,44 @@ 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;
-
-// cpu_count_phys()
+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_cores()
#if (_WIN32_WINNT < 0x0601) // Windows < 7 (Vista and XP)
typedef struct _SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX {
LOGICAL_PROCESSOR_RELATIONSHIP Relationship;
@@ -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/process_handles.c b/psutil/arch/windows/process_handles.c
index f63d4af3..72e3f4d4 100644
--- a/psutil/arch/windows/process_handles.c
+++ b/psutil/arch/windows/process_handles.c
@@ -247,7 +247,7 @@ psutil_get_open_files(DWORD dwPid, HANDLE hProcess) {
if (psutil_threaded_get_filename(hFile) != 0)
goto error;
- if (globalFileName->Length > 0) {
+ if ((globalFileName != NULL) && (globalFileName->Length > 0)) {
py_path = PyUnicode_FromWideChar(globalFileName->Buffer,
wcslen(globalFileName->Buffer));
if (! py_path)
diff --git a/psutil/arch/windows/process_info.c b/psutil/arch/windows/process_info.c
index 73a69912..d44c4eb7 100644
--- a/psutil/arch/windows/process_info.c
+++ b/psutil/arch/windows/process_info.c
@@ -50,6 +50,48 @@ enum psutil_process_data_kind {
};
+static void
+psutil_convert_winerr(ULONG err, char* syscall) {
+ char fullmsg[8192];
+
+ if (err == ERROR_NOACCESS) {
+ sprintf(fullmsg, "%s -> ERROR_NOACCESS", syscall);
+ psutil_debug(fullmsg);
+ AccessDenied(fullmsg);
+ }
+ else {
+ PyErr_SetFromOSErrnoWithSyscall(syscall);
+ }
+}
+
+
+static void
+psutil_convert_ntstatus_err(NTSTATUS status, char* syscall) {
+ ULONG err;
+
+ if (NT_NTWIN32(status))
+ err = WIN32_FROM_NTSTATUS(status);
+ else
+ err = RtlNtStatusToDosErrorNoTeb(status);
+ psutil_convert_winerr(err, syscall);
+}
+
+
+static void
+psutil_giveup_with_ad(NTSTATUS status, char* syscall) {
+ ULONG err;
+ char fullmsg[8192];
+
+ if (NT_NTWIN32(status))
+ err = WIN32_FROM_NTSTATUS(status);
+ else
+ err = RtlNtStatusToDosErrorNoTeb(status);
+ sprintf(fullmsg, "%s -> %lu (%s)", syscall, err, strerror(err));
+ psutil_debug(fullmsg);
+ AccessDenied(fullmsg);
+}
+
+
/*
* Get data from the process with the given pid. The data is returned
* in the pdata output member as a nul terminated string which must be
@@ -87,16 +129,14 @@ psutil_get_process_data(DWORD pid,
http://www.drdobbs.com/embracing-64-bit-windows/184401966
*/
SIZE_T size = 0;
-#ifndef _WIN64
- static __NtQueryInformationProcess NtWow64QueryInformationProcess64 = NULL;
- static _NtWow64ReadVirtualMemory64 NtWow64ReadVirtualMemory64 = NULL;
-#endif
HANDLE hProcess = NULL;
LPCVOID src;
WCHAR *buffer = NULL;
#ifdef _WIN64
LPVOID ppeb32 = NULL;
#else
+ static __NtQueryInformationProcess NtWow64QueryInformationProcess64 = NULL;
+ static _NtWow64ReadVirtualMemory64 NtWow64ReadVirtualMemory64 = NULL;
PVOID64 src64;
BOOL weAreWow64;
BOOL theyAreWow64;
@@ -133,7 +173,7 @@ psutil_get_process_data(DWORD pid,
if (!ReadProcessMemory(hProcess, ppeb32, &peb32, sizeof(peb32), NULL)) {
// May fail with ERROR_PARTIAL_COPY, see:
// https://github.com/giampaolo/psutil/issues/875
- PyErr_SetFromWindowsErr(0);
+ psutil_convert_winerr(GetLastError(), "ReadProcessMemory");
goto error;
}
@@ -146,7 +186,7 @@ psutil_get_process_data(DWORD pid,
{
// May fail with ERROR_PARTIAL_COPY, see:
// https://github.com/giampaolo/psutil/issues/875
- PyErr_SetFromWindowsErr(0);
+ psutil_convert_winerr(GetLastError(), "ReadProcessMemory");
goto error;
}
@@ -164,8 +204,19 @@ psutil_get_process_data(DWORD pid,
break;
}
} else
-#else
- /* 32 bit case. Check if the target is also 32 bit. */
+#else // #ifdef _WIN64
+ // 32 bit process. In here we may run into a lot of errors, e.g.:
+ // * [Error 0] The operation completed successfully
+ // (originated from NtWow64ReadVirtualMemory64)
+ // * [Error 998] Invalid access to memory location:
+ // (originated from NtWow64ReadVirtualMemory64)
+ // Refs:
+ // * https://github.com/giampaolo/psutil/issues/1839
+ // * https://github.com/giampaolo/psutil/pull/1866
+ // Since the following code is quite hackish and fails unpredictably,
+ // in case of any error from NtWow64* APIs we raise AccessDenied.
+
+ // 32 bit case. Check if the target is also 32 bit.
if (!IsWow64Process(GetCurrentProcess(), &weAreWow64) ||
!IsWow64Process(hProcess, &theyAreWow64)) {
PyErr_SetFromOSErrnoWithSyscall("IsWow64Process");
@@ -206,10 +257,14 @@ psutil_get_process_data(DWORD pid,
sizeof(pbi64),
NULL);
if (!NT_SUCCESS(status)) {
- psutil_SetFromNTStatusErr(
- status,
- "NtWow64QueryInformationProcess64(ProcessBasicInformation)"
- );
+ /*
+ psutil_convert_ntstatus_err(
+ status,
+ "NtWow64QueryInformationProcess64(ProcessBasicInformation)");
+ */
+ psutil_giveup_with_ad(
+ status,
+ "NtWow64QueryInformationProcess64(ProcessBasicInformation)");
goto error;
}
@@ -221,7 +276,13 @@ psutil_get_process_data(DWORD pid,
sizeof(peb64),
NULL);
if (!NT_SUCCESS(status)) {
- psutil_SetFromNTStatusErr(status, "NtWow64ReadVirtualMemory64");
+ /*
+ psutil_convert_ntstatus_err(
+ status, "NtWow64ReadVirtualMemory64(pbi64.PebBaseAddress)");
+ */
+ psutil_giveup_with_ad(
+ status,
+ "NtWow64ReadVirtualMemory64(pbi64.PebBaseAddress)");
goto error;
}
@@ -233,10 +294,13 @@ psutil_get_process_data(DWORD pid,
sizeof(procParameters64),
NULL);
if (!NT_SUCCESS(status)) {
- psutil_SetFromNTStatusErr(
- status,
- "NtWow64ReadVirtualMemory64(ProcessParameters)"
- );
+ /*
+ psutil_convert_ntstatus_err(
+ status, "NtWow64ReadVirtualMemory64(peb64.ProcessParameters)");
+ */
+ psutil_giveup_with_ad(
+ status,
+ "NtWow64ReadVirtualMemory64(peb64.ProcessParameters)");
goto error;
}
@@ -284,7 +348,7 @@ psutil_get_process_data(DWORD pid,
{
// May fail with ERROR_PARTIAL_COPY, see:
// https://github.com/giampaolo/psutil/issues/875
- PyErr_SetFromWindowsErr(0);
+ psutil_convert_winerr(GetLastError(), "ReadProcessMemory");
goto error;
}
@@ -297,7 +361,7 @@ psutil_get_process_data(DWORD pid,
{
// May fail with ERROR_PARTIAL_COPY, see:
// https://github.com/giampaolo/psutil/issues/875
- PyErr_SetFromWindowsErr(0);
+ psutil_convert_winerr(GetLastError(), "ReadProcessMemory");
goto error;
}
@@ -343,7 +407,8 @@ psutil_get_process_data(DWORD pid,
size,
NULL);
if (!NT_SUCCESS(status)) {
- psutil_SetFromNTStatusErr(status, "NtWow64ReadVirtualMemory64");
+ // psutil_convert_ntstatus_err(status, "NtWow64ReadVirtualMemory64");
+ psutil_giveup_with_ad(status, "NtWow64ReadVirtualMemory64");
goto error;
}
} else
@@ -351,7 +416,7 @@ psutil_get_process_data(DWORD pid,
if (!ReadProcessMemory(hProcess, src, buffer, size, NULL)) {
// May fail with ERROR_PARTIAL_COPY, see:
// https://github.com/giampaolo/psutil/issues/875
- PyErr_SetFromWindowsErr(0);
+ psutil_convert_winerr(GetLastError(), "ReadProcessMemory");
goto error;
}
@@ -408,7 +473,7 @@ psutil_cmdline_query_proc(DWORD pid, WCHAR **pdata, SIZE_T *psize) {
// https://github.com/giampaolo/psutil/issues/1501
if (status == STATUS_NOT_FOUND) {
AccessDenied("NtQueryInformationProcess(ProcessBasicInformation) -> "
- "STATUS_NOT_FOUND translated into PermissionError");
+ "STATUS_NOT_FOUND");
goto error;
}
@@ -698,16 +763,16 @@ psutil_proc_info(PyObject *self, PyObject *args) {
py_retlist = Py_BuildValue(
#if defined(_WIN64)
- "kkdddiKKKKKK" "kKKKKKKKKK",
+ "kkdddkKKKKKK" "kKKKKKKKKK",
#else
- "kkdddiKKKKKK" "kIIIIIIIII",
+ "kkdddkKKKKKK" "kIIIIIIIII",
#endif
process->HandleCount, // num handles
ctx_switches, // num ctx switches
user_time, // cpu user time
kernel_time, // cpu kernel time
create_time, // create time
- (int)process->NumberOfThreads, // num threads
+ process->NumberOfThreads, // num threads
// IO counters
process->ReadOperationCount.QuadPart, // io rcount
process->WriteOperationCount.QuadPart, // io wcount
diff --git a/psutil/arch/windows/process_utils.c b/psutil/arch/windows/process_utils.c
index f9d2f2f9..acbda301 100644
--- a/psutil/arch/windows/process_utils.c
+++ b/psutil/arch/windows/process_utils.c
@@ -61,8 +61,10 @@ psutil_pid_in_pids(DWORD pid) {
DWORD i;
proclist = psutil_get_pids(&numberOfReturnedPIDs);
- if (proclist == NULL)
+ if (proclist == NULL) {
+ psutil_debug("psutil_get_pids() failed");
return -1;
+ }
for (i = 0; i < numberOfReturnedPIDs; i++) {
if (proclist[i] == pid) {
free(proclist);
@@ -78,20 +80,36 @@ psutil_pid_in_pids(DWORD pid) {
// does return the handle, else return NULL with Python exception set.
// This is needed because OpenProcess API sucks.
HANDLE
-psutil_check_phandle(HANDLE hProcess, DWORD pid) {
+psutil_check_phandle(HANDLE hProcess, DWORD pid, int check_exit_code) {
DWORD exitCode;
if (hProcess == NULL) {
if (GetLastError() == ERROR_INVALID_PARAMETER) {
// Yeah, this is the actual error code in case of
// "no such process".
- NoSuchProcess("OpenProcess");
+ NoSuchProcess("OpenProcess -> ERROR_INVALID_PARAMETER");
+ return NULL;
+ }
+ if (GetLastError() == ERROR_SUCCESS) {
+ // Yeah, it's this bad.
+ // https://github.com/giampaolo/psutil/issues/1877
+ if (psutil_pid_in_pids(pid) == 1) {
+ psutil_debug("OpenProcess -> ERROR_SUCCESS turned into AD");
+ AccessDenied("OpenProcess -> ERROR_SUCCESS");
+ }
+ else {
+ psutil_debug("OpenProcess -> ERROR_SUCCESS turned into NSP");
+ NoSuchProcess("OpenProcess -> ERROR_SUCCESS");
+ }
return NULL;
}
PyErr_SetFromOSErrnoWithSyscall("OpenProcess");
return NULL;
}
+ if (check_exit_code == 0)
+ return hProcess;
+
if (GetExitCodeProcess(hProcess, &exitCode)) {
// XXX - maybe STILL_ACTIVE is not fully reliable as per:
// http://stackoverflow.com/questions/1591342/#comment47830782_1591379
@@ -137,7 +155,7 @@ psutil_handle_from_pid(DWORD pid, DWORD access) {
return NULL;
}
- hProcess = psutil_check_phandle(hProcess, pid);
+ hProcess = psutil_check_phandle(hProcess, pid, 1);
return hProcess;
}
@@ -159,7 +177,7 @@ psutil_pid_is_running(DWORD pid) {
if ((hProcess == NULL) && (GetLastError() == ERROR_ACCESS_DENIED))
return 1;
- hProcess = psutil_check_phandle(hProcess, pid);
+ hProcess = psutil_check_phandle(hProcess, pid, 1);
if (hProcess != NULL) {
CloseHandle(hProcess);
return 1;
diff --git a/psutil/arch/windows/process_utils.h b/psutil/arch/windows/process_utils.h
index a7171c5c..dca7c991 100644
--- a/psutil/arch/windows/process_utils.h
+++ b/psutil/arch/windows/process_utils.h
@@ -6,6 +6,7 @@
DWORD* psutil_get_pids(DWORD *numberOfReturnedPIDs);
HANDLE psutil_handle_from_pid(DWORD pid, DWORD dwDesiredAccess);
+HANDLE psutil_check_phandle(HANDLE hProcess, DWORD pid, int check_exit_code);
int psutil_pid_is_running(DWORD pid);
int psutil_assert_pid_exists(DWORD pid, char *err);
int psutil_assert_pid_not_exists(DWORD pid, char *err);
diff --git a/psutil/arch/windows/wmi.c b/psutil/arch/windows/wmi.c
index 42a70df7..5fad4053 100644
--- a/psutil/arch/windows/wmi.c
+++ b/psutil/arch/windows/wmi.c
@@ -10,6 +10,8 @@
#include <windows.h>
#include <pdh.h>
+#include "../../_psutil_common.h"
+
// We use an exponentially weighted moving average, just like Unix systems do
// https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation
@@ -30,7 +32,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;
@@ -62,22 +64,28 @@ psutil_init_loadavg_counter(PyObject *self, PyObject *args) {
HANDLE event;
HANDLE waitHandle;
- if ((PdhOpenQueryW(NULL, 0, &hQuery)) != ERROR_SUCCESS)
- goto error;
+ if ((PdhOpenQueryW(NULL, 0, &hQuery)) != ERROR_SUCCESS) {
+ PyErr_Format(PyExc_RuntimeError, "PdhOpenQueryW failed");
+ return NULL;
+ }
s = PdhAddEnglishCounterW(hQuery, szCounterPath, 0, &hCounter);
- if (s != ERROR_SUCCESS)
- goto error;
+ if (s != ERROR_SUCCESS) {
+ PyErr_Format(PyExc_RuntimeError, "PdhAddEnglishCounterW failed");
+ return NULL;
+ }
event = CreateEventW(NULL, FALSE, FALSE, L"LoadUpdateEvent");
if (event == NULL) {
- PyErr_SetFromWindowsErr(GetLastError());
+ PyErr_SetFromOSErrnoWithSyscall("CreateEventW");
return NULL;
}
s = PdhCollectQueryDataEx(hQuery, SAMPLING_INTERVAL, event);
- if (s != ERROR_SUCCESS)
- goto error;
+ if (s != ERROR_SUCCESS) {
+ PyErr_Format(PyExc_RuntimeError, "PdhCollectQueryDataEx failed");
+ return NULL;
+ }
ret = RegisterWaitForSingleObject(
&waitHandle,
@@ -89,15 +97,11 @@ psutil_init_loadavg_counter(PyObject *self, PyObject *args) {
WT_EXECUTEDEFAULT);
if (ret == 0) {
- PyErr_SetFromWindowsErr(GetLastError());
+ PyErr_SetFromOSErrnoWithSyscall("RegisterWaitForSingleObject");
return NULL;
}
Py_RETURN_NONE;
-
-error:
- PyErr_SetFromWindowsErr(0);
- return NULL;
}
diff --git a/psutil/tests/README.rst b/psutil/tests/README.rst
index 61e066b7..9dca1186 100644
--- a/psutil/tests/README.rst
+++ b/psutil/tests/README.rst
@@ -12,12 +12,3 @@ Instructions for running tests
make setup-dev-env # install missing third-party deps
make test # serial run
make test-parallel # parallel run
-
-* To run tests on all supported Python versions install tox
- (``pip install tox``) then run ``tox`` from within psutil root directory.
-
-* Every time a commit is pushed tests are automatically run on Travis
- (Linux, MACOS) and appveyor (Windows):
-
- * Travis builds: https://travis-ci.org/giampaolo/psutil
- * AppVeyor builds: https://ci.appveyor.com/project/giampaolo/psutil
diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py
index 1888a75a..414f1793 100644
--- a/psutil/tests/__init__.py
+++ b/psutil/tests/__init__.py
@@ -37,6 +37,7 @@ from socket import SOCK_STREAM
import psutil
from psutil import AIX
+from psutil import FREEBSD
from psutil import LINUX
from psutil import MACOS
from psutil import POSIX
@@ -54,10 +55,10 @@ from psutil._compat import u
from psutil._compat import unicode
from psutil._compat import which
-if sys.version_info < (2, 7):
- import unittest2 as unittest # requires "pip install unittest2"
-else:
+if PY3:
import unittest
+else:
+ import unittest2 as unittest # requires "pip install unittest2"
try:
from unittest import mock # py3
@@ -74,10 +75,10 @@ else:
__all__ = [
# constants
- 'APPVEYOR', 'DEVNULL', 'GLOBAL_TIMEOUT', 'SYSMEM_TOLERANCE', 'NO_RETRIES',
+ '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',
- 'CI_TESTING', 'VALID_PROC_STATUSES',
+ 'UNICODE_SUFFIX', 'INVALID_UNICODE_SUFFIX',
+ '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",
"HAS_SENSORS_BATTERY", "HAS_BATTERY", "HAS_SENSORS_FANS",
@@ -90,14 +91,14 @@ __all__ = [
# test utils
'unittest', 'skip_on_access_denied', 'skip_on_not_implemented',
'retry_on_failure', 'TestMemoryLeak', 'PsutilTestCase',
- 'process_namespace', 'system_namespace',
+ 'process_namespace', 'system_namespace', 'print_sysinfo',
# install utils
'install_pip', 'install_test_deps',
# fs utils
'chdir', 'safe_rmpath', 'create_exe', 'decode_path', 'encode_path',
'get_testfn',
# os
- 'get_winver', 'get_kernel_version',
+ 'get_winver', 'kernel_version',
# sync primitives
'call_until', 'wait_for_pid', 'wait_for_file',
# network
@@ -117,27 +118,30 @@ __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 = bool(os.environ.get('TRAVIS'))
-APPVEYOR = bool(os.environ.get('APPVEYOR'))
-CIRRUS = bool(os.environ.get('CIRRUS'))
-CI_TESTING = TRAVIS or APPVEYOR or CIRRUS
+APPVEYOR = 'APPVEYOR' in os.environ
+GITHUB_ACTIONS = 'GITHUB_ACTIONS' in os.environ or 'CIBUILDWHEEL' in os.environ
+CI_TESTING = APPVEYOR or GITHUB_ACTIONS
+# are we a 64 bit process?
+IS_64BIT = sys.maxsize > 2 ** 32
+
# --- configurable defaults
# how many times retry_on_failure() decorator will retry
NO_RETRIES = 10
-# bytes tolerance for system-wide memory related tests
-SYSMEM_TOLERANCE = 500 * 1024 # 500KB
+# bytes tolerance for system-wide related tests
+TOLERANCE_SYS_MEM = 5 * 1024 * 1024 # 5MB
+TOLERANCE_DISK_USAGE = 10 * 1024 * 1024 # 10MB
# the timeout used in functions which have to wait
GLOBAL_TIMEOUT = 5
-# be more tolerant if we're on travis / appveyor in order to avoid
-# false positives
-if TRAVIS or APPVEYOR:
+# be more tolerant if we're on CI in order to avoid false positives
+if CI_TESTING:
NO_RETRIES *= 3
GLOBAL_TIMEOUT *= 3
+ TOLERANCE_SYS_MEM *= 3
+ TOLERANCE_DISK_USAGE *= 3
# --- file names
@@ -153,7 +157,6 @@ if PY3:
INVALID_UNICODE_SUFFIX = b"f\xc0\x80".decode('utf8', 'surrogateescape')
else:
INVALID_UNICODE_SUFFIX = "f\xc0\x80"
-
ASCII_FS = sys.getfilesystemencoding().lower() in ('ascii', 'us-ascii')
# --- paths
@@ -180,7 +183,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")
@@ -201,7 +204,14 @@ def _get_py_exe():
else:
return exe
- if MACOS:
+ if GITHUB_ACTIONS:
+ if PYPY:
+ return which("pypy3") if PY3 else which("pypy")
+ elif FREEBSD:
+ return os.path.realpath(sys.executable)
+ else:
+ return which('python')
+ elif MACOS:
exe = \
attempt(sys.executable) or \
attempt(os.path.realpath(sys.executable)) or \
@@ -478,14 +488,21 @@ def terminate(proc_or_pid, sig=signal.SIGTERM, wait_timeout=GLOBAL_TIMEOUT):
from psutil._psposix import wait_pid
def wait(proc, timeout):
- try:
- return psutil.Process(proc.pid).wait(timeout)
- except psutil.NoSuchProcess:
- # Needed to kill zombies.
- if POSIX:
- return wait_pid(proc.pid, timeout)
+ if isinstance(proc, subprocess.Popen) and not PY3:
+ proc.wait()
+ else:
+ proc.wait(timeout)
+ if WINDOWS and isinstance(proc, subprocess.Popen):
+ # Otherwise PID may still hang around.
+ try:
+ return psutil.Process(proc.pid).wait(timeout)
+ except psutil.NoSuchProcess:
+ pass
def sendsig(proc, sig):
+ # XXX: otherwise the build hangs for some reason.
+ if MACOS and GITHUB_ACTIONS:
+ sig = signal.SIGKILL
# If the process received SIGSTOP, SIGCONT is necessary first,
# otherwise SIGTERM won't work.
if POSIX and sig != signal.SIGKILL:
@@ -582,7 +599,7 @@ def reap_children(recursive=False):
# ===================================================================
-def get_kernel_version():
+def kernel_version():
"""Return a tuple such as (2, 6, 36)."""
if not POSIX:
raise NotImplementedError("not POSIX")
@@ -696,7 +713,7 @@ def wait_for_pid(pid):
time.sleep(0.01)
-@retry(exception=(EnvironmentError, AssertionError), logfun=None,
+@retry(exception=(FileNotFoundError, AssertionError), logfun=None,
timeout=GLOBAL_TIMEOUT, interval=0.001)
def wait_for_file(fname, delete=True, empty=False):
"""Wait for a file to be written on disk with some content."""
@@ -732,7 +749,7 @@ def safe_rmpath(path):
# open handles or references preventing the delete operation
# to succeed immediately, so we retry for a while. See:
# https://bugs.python.org/issue33240
- stop_at = time.time() + 1
+ stop_at = time.time() + GLOBAL_TIMEOUT
while time.time() < stop_at:
try:
return fun()
@@ -813,10 +830,8 @@ def get_testfn(suffix="", dir=None):
deletion at interpreter exit. It's technically racy but probably
not really due to the time variant.
"""
- timer = getattr(time, 'perf_counter', time.time)
while True:
- prefix = "%s%.9f-" % (TESTFN_PREFIX, timer())
- name = tempfile.mktemp(prefix=prefix, suffix=suffix, dir=dir)
+ name = tempfile.mktemp(prefix=TESTFN_PREFIX, suffix=suffix, dir=dir)
if not os.path.exists(name): # also include dirs
return os.path.realpath(name) # needed for OSX
@@ -889,7 +904,13 @@ class PsutilTestCase(TestCase):
self.assertRaises(psutil.NoSuchProcess, psutil.Process, proc.pid)
if isinstance(proc, (psutil.Process, psutil.Popen)):
assert not proc.is_running()
- self.assertRaises(psutil.NoSuchProcess, proc.status)
+ try:
+ status = proc.status()
+ except psutil.NoSuchProcess:
+ pass
+ else:
+ raise AssertionError("Process.status() didn't raise exception "
+ "(status=%s)" % status)
proc.wait(timeout=0) # assert not raise TimeoutExpired
assert not psutil.pid_exists(proc.pid), proc.pid
self.assertNotIn(proc.pid, psutil.pids())
@@ -897,33 +918,42 @@ class PsutilTestCase(TestCase):
@unittest.skipIf(PYPY, "unreliable on PYPY")
class TestMemoryLeak(PsutilTestCase):
- """Test framework class for detecting function memory leaks (typically
- functions implemented in C).
- It does so by calling a function many times, and checks whether the
- process memory usage increased before and after having called the
- function repeadetly.
+ """Test framework class for detecting function memory leaks,
+ typically functions implemented in C which forgot to free() memory
+ from the heap. It does so by checking whether the process memory
+ usage increased before and after calling the function many times.
+
+ Note that this is hard (probably impossible) to do reliably, due
+ to how the OS handles memory, the GC and so on (memory can even
+ decrease!). In order to avoid false positives, in case of failure
+ (mem > 0) we retry the test for up to 5 times, increasing call
+ repetitions each time. If the memory keeps increasing then it's a
+ failure.
+
+ If available (Linux, OSX, Windows), USS memory is used for comparison,
+ since it's supposed to be more precise, see:
+ 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.
+
+ PyPy appears to be completely unstable for this framework, probably
+ because of its JIT, so tests on PYPY are skipped.
- In addition also call the function onces and make sure num_fds()
- (POSIX) or num_handles() (Windows) does not increase. This is done
- in order to discover forgotten close(2) and CloseHandle syscalls.
+ Usage:
- Note that sometimes this may produce false positives.
+ class TestLeaks(psutil.tests.TestMemoryLeak):
- PyPy appears to be completely unstable for this framework, probably
- because of how its JIT handles memory, so tests on PYPY are
- automatically skipped.
+ def test_fun(self):
+ self.execute(some_function)
"""
# Configurable class attrs.
- times = 1200
+ times = 200
warmup_times = 10
- tolerance = 4096 # memory
- retry_for = 3.0 # seconds
- check_fds = True # whether to check if num_fds() increased
+ tolerance = 0 # memory
+ retries = 10 if CI_TESTING else 5
verbose = True
-
- def setUp(self):
- self._thisproc = psutil.Process()
- gc.collect()
+ _thisproc = psutil.Process()
def _get_mem(self):
if LINUX:
@@ -935,96 +965,97 @@ class TestMemoryLeak(PsutilTestCase):
else:
return self._thisproc.memory_info().rss
- def _get_fds_or_handles(self):
+ def _get_num_fds(self):
if POSIX:
return self._thisproc.num_fds()
else:
return self._thisproc.num_handles()
- def _call(self, fun):
- return fun()
+ def _log(self, msg):
+ if self.verbose:
+ print_color(msg, color="yellow", file=sys.stderr)
+
+ def _check_fds(self, fun):
+ """Makes sure num_fds() (POSIX) or num_handles() (Windows) does
+ not increase after calling a function. Used to discover forgotten
+ close(2) and CloseHandle syscalls.
+ """
+ before = self._get_num_fds()
+ self.call(fun)
+ after = self._get_num_fds()
+ diff = after - before
+ if diff < 0:
+ raise self.fail("negative diff %r (gc probably collected a "
+ "resource from a previous test)" % diff)
+ if diff > 0:
+ type_ = "fd" if POSIX else "handle"
+ if diff > 1:
+ type_ += "s"
+ msg = "%s unclosed %s after calling %r" % (diff, type_, fun)
+ raise self.fail(msg)
- def _itercall(self, fun, iterator):
+ def _call_ntimes(self, fun, times):
"""Get 2 distinct memory samples, before and after having
called fun repeadetly, and return the memory difference.
"""
- ncalls = 0
- gc.collect()
+ gc.collect(generation=1)
mem1 = self._get_mem()
- for x in iterator:
- ret = self._call(fun)
- ncalls += 1
+ for x in range(times):
+ ret = self.call(fun)
del x, ret
- gc.collect()
+ gc.collect(generation=1)
mem2 = self._get_mem()
self.assertEqual(gc.garbage, [])
- diff = mem2 - mem1
- if diff < 0:
- self._log("negative memory diff -%s" % (bytes2human(abs(diff))))
- return (diff, ncalls)
-
- def _call_ntimes(self, fun, times):
- return self._itercall(fun, range(times))[0]
+ diff = mem2 - mem1 # can also be negative
+ return diff
+
+ def _check_mem(self, fun, times, warmup_times, retries, tolerance):
+ messages = []
+ prev_mem = 0
+ increase = times
+ for idx in range(1, retries + 1):
+ mem = self._call_ntimes(fun, times)
+ msg = "Run #%s: extra-mem=%s, per-call=%s, calls=%s" % (
+ idx, bytes2human(mem), bytes2human(mem / times), times)
+ messages.append(msg)
+ success = mem <= tolerance or mem <= prev_mem
+ if success:
+ if idx > 1:
+ self._log(msg)
+ return
+ else:
+ if idx == 1:
+ print() # NOQA
+ self._log(msg)
+ times += increase
+ prev_mem = mem
+ raise self.fail(". ".join(messages))
- def _call_for(self, fun, secs):
- def iterator(secs):
- stop_at = time.time() + secs
- while time.time() < stop_at:
- yield
- return self._itercall(fun, iterator(secs))
+ # ---
- def _log(self, msg):
- if self.verbose:
- print_color(msg, color="yellow", file=sys.stderr)
+ def call(self, fun):
+ return fun()
- def execute(self, fun, times=times, warmup_times=warmup_times,
- tolerance=tolerance, retry_for=retry_for, check_fds=check_fds):
+ def execute(self, fun, times=None, warmup_times=None, retries=None,
+ tolerance=None):
"""Test a callable."""
- if times <= 0:
- raise ValueError("times must be > 0")
- if warmup_times < 0:
- raise ValueError("warmup_times must be >= 0")
- if tolerance is not None and tolerance < 0:
- raise ValueError("tolerance must be >= 0")
- if retry_for is not None and retry_for < 0:
- raise ValueError("retry_for must be >= 0")
-
- if check_fds:
- before = self._get_fds_or_handles()
- self._call(fun)
- after = self._get_fds_or_handles()
- diff = abs(after - before)
- if diff > 0:
- msg = "%s unclosed fd(s) or handle(s)" % (diff)
- raise self.fail(msg)
-
- # warm up
- self._call_ntimes(fun, warmup_times)
- mem1 = self._call_ntimes(fun, times)
-
- if mem1 > tolerance:
- # This doesn't necessarily mean we have a leak yet.
- # At this point we assume that after having called the
- # function so many times the memory usage is stabilized
- # and if there are no leaks it should not increase
- # anymore. Let's keep calling fun for N more seconds and
- # fail if we notice any difference (ignore tolerance).
- msg1 = "+%s mem increase after %s calls (%s per-call on average)"
- msg1 = msg1 % (bytes2human(mem1), times, bytes2human(mem1 / times))
- if not retry_for:
- raise self.fail(msg1)
- else:
- self._log(msg1 + "; try calling fun for another %s secs"
- % retry_for)
- mem2, ncalls = self._call_for(fun, retry_for)
- if mem2 > mem1:
- # failure
- msg2 = msg1
- msg2 += "; +%s after another %s calls over %s secs "
- msg2 += "(%s per-call on average)"
- msg2 = msg2 % (bytes2human(mem2), ncalls, retry_for,
- bytes2human(mem2 / ncalls))
- raise self.fail(msg2)
+ times = times if times is not None else self.times
+ warmup_times = warmup_times if warmup_times is not None \
+ else self.warmup_times
+ retries = retries if retries is not None else self.retries
+ tolerance = tolerance if tolerance is not None else self.tolerance
+ try:
+ assert times >= 1, "times must be >= 1"
+ assert warmup_times >= 0, "warmup_times must be >= 0"
+ assert retries >= 0, "retries must be >= 0"
+ assert tolerance >= 0, "tolerance must be >= 0"
+ except AssertionError as err:
+ raise ValueError(str(err))
+
+ self._call_ntimes(fun, warmup_times) # warm up
+ self._check_fds(fun)
+ self._check_mem(fun, times=times, warmup_times=warmup_times,
+ retries=retries, tolerance=tolerance)
def execute_w_exc(self, exc, fun, **kwargs):
"""Convenience method to test a callable while making sure it
@@ -1036,12 +1067,103 @@ class TestMemoryLeak(PsutilTestCase):
self.execute(call, **kwargs)
+def print_sysinfo():
+ import collections
+ import datetime
+ import getpass
+ import locale
+ import platform
+ import pprint
+ try:
+ import pip
+ except ImportError:
+ pip = None
+ try:
+ import wheel
+ except ImportError:
+ wheel = None
+
+ info = collections.OrderedDict()
+
+ # OS
+ if psutil.LINUX and which('lsb_release'):
+ info['OS'] = sh('lsb_release -d -s')
+ elif psutil.OSX:
+ info['OS'] = 'Darwin %s' % platform.mac_ver()[0]
+ elif psutil.WINDOWS:
+ info['OS'] = "Windows " + ' '.join(
+ map(str, platform.win32_ver()))
+ if hasattr(platform, 'win32_edition'):
+ info['OS'] += ", " + platform.win32_edition()
+ else:
+ info['OS'] = "%s %s" % (platform.system(), platform.version())
+ info['arch'] = ', '.join(
+ list(platform.architecture()) + [platform.machine()])
+ if psutil.POSIX:
+ info['kernel'] = platform.uname()[2]
+
+ # python
+ info['python'] = ', '.join([
+ platform.python_implementation(),
+ platform.python_version(),
+ platform.python_compiler()])
+ info['pip'] = getattr(pip, '__version__', 'not installed')
+ if wheel is not None:
+ info['pip'] += " (wheel=%s)" % wheel.__version__
+
+ # UNIX
+ if psutil.POSIX:
+ if which('gcc'):
+ out = sh(['gcc', '--version'])
+ info['gcc'] = str(out).split('\n')[0]
+ else:
+ info['gcc'] = 'not installed'
+ s = platform.libc_ver()[1]
+ if s:
+ info['glibc'] = s
+
+ # system
+ info['fs-encoding'] = sys.getfilesystemencoding()
+ lang = locale.getlocale()
+ info['lang'] = '%s, %s' % (lang[0], lang[1])
+ info['boot-time'] = datetime.datetime.fromtimestamp(
+ psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S")
+ info['time'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ info['user'] = getpass.getuser()
+ info['home'] = os.path.expanduser("~")
+ info['cwd'] = os.getcwd()
+ info['pyexe'] = PYTHON_EXE
+ info['hostname'] = platform.node()
+ info['PID'] = os.getpid()
+
+ # metrics
+ info['cpus'] = psutil.cpu_count()
+ info['loadavg'] = "%.1f%%, %.1f%%, %.1f%%" % (
+ tuple([x / psutil.cpu_count() * 100 for x in psutil.getloadavg()]))
+ mem = psutil.virtual_memory()
+ info['memory'] = "%s%%, used=%s, total=%s" % (
+ int(mem.percent), bytes2human(mem.used), bytes2human(mem.total))
+ swap = psutil.swap_memory()
+ info['swap'] = "%s%%, used=%s, total=%s" % (
+ int(swap.percent), bytes2human(swap.used), bytes2human(swap.total))
+ info['pids'] = len(psutil.pids())
+ pinfo = psutil.Process().as_dict()
+ pinfo.pop('memory_maps', None)
+ info['proc'] = pprint.pformat(pinfo)
+
+ print("=" * 70, file=sys.stderr) # NOQA
+ for k, v in info.items():
+ print("%-17s %s" % (k + ':', v), file=sys.stderr) # NOQA
+ print("=" * 70, file=sys.stderr) # NOQA
+ sys.stdout.flush()
+
+
def _get_eligible_cpu():
p = psutil.Process()
if hasattr(p, "cpu_num"):
return p.cpu_num()
elif hasattr(p, "cpu_affinity"):
- return p.cpu_affinity()[0]
+ return random.choice(p.cpu_affinity())
return 0
@@ -1247,17 +1369,6 @@ class system_namespace:
fun = functools.partial(fun, *args, **kwds)
yield (fun, fun_name)
- @classmethod
- def test(cls):
- this = set([x[0] for x in cls.all])
- ignored = set([x[0] for x in cls.ignored])
- # there's a separate test for __all__
- mod = set([x for x in dir(psutil) if x.islower() and x[0] != '_' and
- x in psutil.__all__ and callable(getattr(psutil, x))])
- leftout = (this | ignored) ^ mod
- if leftout:
- raise ValueError("uncovered psutil mod name(s): %r" % leftout)
-
test_class_coverage = process_namespace.test_class_coverage
@@ -1321,8 +1432,9 @@ def skip_on_not_implemented(only_if=None):
# ===================================================================
+# XXX: no longer used
def get_free_port(host='127.0.0.1'):
- """Return an unused TCP port."""
+ """Return an unused TCP port. Subject to race conditions."""
with contextlib.closing(socket.socket()) as sock:
sock.bind((host, 0))
return sock.getsockname()[1]
@@ -1438,7 +1550,7 @@ def check_net_address(addr, family):
IPv6 and MAC addresses.
"""
import ipaddress # python >= 3.3 / requires "pip install ipaddress"
- if enum and PY3:
+ if enum and PY3 and not PYPY:
assert isinstance(family, enum.IntEnum), family
if family == socket.AF_INET:
octs = [int(x) for x in addr.split('.')]
@@ -1459,6 +1571,83 @@ def check_net_address(addr, family):
raise ValueError("unknown family %r", family)
+def check_connection_ntuple(conn):
+ """Check validity of a connection namedtuple."""
+ def check_ntuple(conn):
+ has_pid = len(conn) == 7
+ assert len(conn) in (6, 7), len(conn)
+ assert conn[0] == conn.fd, conn.fd
+ assert conn[1] == conn.family, conn.family
+ assert conn[2] == conn.type, conn.type
+ assert conn[3] == conn.laddr, conn.laddr
+ assert conn[4] == conn.raddr, conn.raddr
+ assert conn[5] == conn.status, conn.status
+ if has_pid:
+ assert conn[6] == conn.pid, conn.pid
+
+ def check_family(conn):
+ assert conn.family in (AF_INET, AF_INET6, AF_UNIX), conn.family
+ if enum is not None:
+ assert isinstance(conn.family, enum.IntEnum), conn
+ else:
+ assert isinstance(conn.family, int), conn
+ if conn.family == AF_INET:
+ # actually try to bind the local socket; ignore IPv6
+ # sockets as their address might be represented as
+ # an IPv4-mapped-address (e.g. "::127.0.0.1")
+ # and that's rejected by bind()
+ s = socket.socket(conn.family, conn.type)
+ with contextlib.closing(s):
+ try:
+ s.bind((conn.laddr[0], 0))
+ except socket.error as err:
+ if err.errno != errno.EADDRNOTAVAIL:
+ raise
+ elif conn.family == AF_UNIX:
+ assert conn.status == psutil.CONN_NONE, conn.status
+
+ def check_type(conn):
+ # SOCK_SEQPACKET may happen in case of AF_UNIX socks
+ SOCK_SEQPACKET = getattr(socket, "SOCK_SEQPACKET", object())
+ assert conn.type in (socket.SOCK_STREAM, socket.SOCK_DGRAM,
+ SOCK_SEQPACKET), conn.type
+ if enum is not None:
+ assert isinstance(conn.type, enum.IntEnum), conn
+ else:
+ assert isinstance(conn.type, int), conn
+ if conn.type == socket.SOCK_DGRAM:
+ assert conn.status == psutil.CONN_NONE, conn.status
+
+ def check_addrs(conn):
+ # check IP address and port sanity
+ for addr in (conn.laddr, conn.raddr):
+ if conn.family in (AF_INET, AF_INET6):
+ assert isinstance(addr, tuple), type(addr)
+ if not addr:
+ continue
+ assert isinstance(addr.port, int), type(addr.port)
+ assert 0 <= addr.port <= 65535, addr.port
+ check_net_address(addr.ip, conn.family)
+ elif conn.family == AF_UNIX:
+ assert isinstance(addr, str), type(addr)
+
+ def check_status(conn):
+ assert isinstance(conn.status, str), conn.status
+ valids = [getattr(psutil, x) for x in dir(psutil)
+ if x.startswith('CONN_')]
+ assert conn.status in valids, conn.status
+ if conn.family in (AF_INET, AF_INET6) and conn.type == SOCK_STREAM:
+ assert conn.status != psutil.CONN_NONE, conn.status
+ else:
+ assert conn.status == psutil.CONN_NONE, conn.status
+
+ check_ntuple(conn)
+ check_family(conn)
+ check_type(conn)
+ check_addrs(conn)
+ check_status(conn)
+
+
# ===================================================================
# --- compatibility
# ===================================================================
@@ -1589,7 +1778,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 97139fbc..d761cd21 100755
--- a/psutil/tests/runner.py
+++ b/psutil/tests/runner.py
@@ -43,17 +43,17 @@ from psutil._common import hilite
from psutil._common import print_color
from psutil._common import term_supports_colors
from psutil._compat import super
-from psutil.tests import APPVEYOR
+from psutil.tests import CI_TESTING
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 = term_supports_colors() and not APPVEYOR
+USE_COLORS = not CI_TESTING and term_supports_colors()
HERE = os.path.abspath(os.path.dirname(__file__))
loadTestsFromTestCase = unittest.defaultTestLoader.loadTestsFromTestCase
@@ -71,7 +71,7 @@ def cprint(msg, color, bold=False, file=None):
class TestLoader:
testdir = HERE
- skip_files = ['test_memory_leaks.py']
+ skip_files = ['test_memleaks.py']
if "WHEELHOUSE_UPLOADER_USERNAME" in os.environ:
skip_files.extend(['test_osx.py', 'test_linux.py', 'test_posix.py'])
@@ -304,9 +304,9 @@ def run_from_name(name):
def setup():
- if 'PSUTIL_TESTING' not in os.environ:
- # This won't work on Windows but set_testing() below will do it.
- os.environ['PSUTIL_TESTING'] = '1'
+ # Note: doc states that altering os.environment may cause memory
+ # leaks on some platforms.
+ # Sets PSUTIL_TESTING and PSUTIL_DEBUG in the C module.
psutil._psplatform.cext.set_testing()
@@ -338,7 +338,8 @@ def main():
else:
suite = loader.all()
- # runner
+ if CI_TESTING:
+ print_sysinfo()
runner = get_runner(opts.parallel)
runner.run(suite)
diff --git a/psutil/tests/test_aix.py b/psutil/tests/test_aix.py
index caf20357..a32c3f6a 100755
--- a/psutil/tests/test_aix.py
+++ b/psutil/tests/test_aix.py
@@ -38,17 +38,17 @@ class AIXSpecificTestCase(PsutilTestCase):
psutil_result = psutil.virtual_memory()
- # SYSMEM_TOLERANCE from psutil.tests is not enough. For some reason
+ # TOLERANCE_SYS_MEM from psutil.tests is not enough. For some reason
# we're seeing differences of ~1.2 MB. 2 MB is still a good tolerance
# when compared to GBs.
- SYSMEM_TOLERANCE = 2 * KB * KB # 2 MB
+ TOLERANCE_SYS_MEM = 2 * KB * KB # 2 MB
self.assertEqual(psutil_result.total, total)
self.assertAlmostEqual(
- psutil_result.used, used, delta=SYSMEM_TOLERANCE)
+ psutil_result.used, used, delta=TOLERANCE_SYS_MEM)
self.assertAlmostEqual(
- psutil_result.available, available, delta=SYSMEM_TOLERANCE)
+ psutil_result.available, available, delta=TOLERANCE_SYS_MEM)
self.assertAlmostEqual(
- psutil_result.free, free, delta=SYSMEM_TOLERANCE)
+ psutil_result.free, free, delta=TOLERANCE_SYS_MEM)
def test_swap_memory(self):
out = sh('/usr/sbin/lsps -a')
diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py
index cfbec71d..b0bff87f 100755
--- a/psutil/tests/test_bsd.py
+++ b/psutil/tests/test_bsd.py
@@ -25,19 +25,20 @@ from psutil.tests import HAS_BATTERY
from psutil.tests import PsutilTestCase
from psutil.tests import retry_on_failure
from psutil.tests import sh
-from psutil.tests import SYSMEM_TOLERANCE
+from psutil.tests import TOLERANCE_SYS_MEM
from psutil.tests import terminate
from psutil.tests import unittest
from psutil.tests import which
if BSD:
- PAGESIZE = os.sysconf("SC_PAGE_SIZE")
- if os.getuid() == 0: # muse requires root privileges
- MUSE_AVAILABLE = which('muse')
- else:
- MUSE_AVAILABLE = False
+ from psutil._psutil_posix import getpagesize
+
+ PAGESIZE = getpagesize()
+ # muse requires root privileges
+ MUSE_AVAILABLE = True if os.getuid() == 0 and which('muse') else False
else:
+ PAGESIZE = None
MUSE_AVAILABLE = False
@@ -280,37 +281,37 @@ class FreeBSDSystemTestCase(PsutilTestCase):
def test_vmem_active(self):
syst = sysctl("vm.stats.vm.v_active_count") * PAGESIZE
self.assertAlmostEqual(psutil.virtual_memory().active, syst,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_vmem_inactive(self):
syst = sysctl("vm.stats.vm.v_inactive_count") * PAGESIZE
self.assertAlmostEqual(psutil.virtual_memory().inactive, syst,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_vmem_wired(self):
syst = sysctl("vm.stats.vm.v_wire_count") * PAGESIZE
self.assertAlmostEqual(psutil.virtual_memory().wired, syst,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_vmem_cached(self):
syst = sysctl("vm.stats.vm.v_cache_count") * PAGESIZE
self.assertAlmostEqual(psutil.virtual_memory().cached, syst,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_vmem_free(self):
syst = sysctl("vm.stats.vm.v_free_count") * PAGESIZE
self.assertAlmostEqual(psutil.virtual_memory().free, syst,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_vmem_buffers(self):
syst = sysctl("vfs.bufspace")
self.assertAlmostEqual(psutil.virtual_memory().buffers, syst,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
# --- virtual_memory(); tests against muse
@@ -324,42 +325,42 @@ class FreeBSDSystemTestCase(PsutilTestCase):
def test_muse_vmem_active(self):
num = muse('Active')
self.assertAlmostEqual(psutil.virtual_memory().active, num,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
@retry_on_failure()
def test_muse_vmem_inactive(self):
num = muse('Inactive')
self.assertAlmostEqual(psutil.virtual_memory().inactive, num,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
@retry_on_failure()
def test_muse_vmem_wired(self):
num = muse('Wired')
self.assertAlmostEqual(psutil.virtual_memory().wired, num,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
@retry_on_failure()
def test_muse_vmem_cached(self):
num = muse('Cache')
self.assertAlmostEqual(psutil.virtual_memory().cached, num,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
@retry_on_failure()
def test_muse_vmem_free(self):
num = muse('Free')
self.assertAlmostEqual(psutil.virtual_memory().free, num,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
@unittest.skipIf(not MUSE_AVAILABLE, "muse not installed")
@retry_on_failure()
def test_muse_vmem_buffers(self):
num = muse('Buffer')
self.assertAlmostEqual(psutil.virtual_memory().buffers, num,
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
def test_cpu_stats_ctx_switches(self):
self.assertAlmostEqual(psutil.cpu_stats().ctx_switches,
@@ -388,17 +389,17 @@ class FreeBSDSystemTestCase(PsutilTestCase):
def test_swapmem_free(self):
total, used, free = self.parse_swapinfo()
self.assertAlmostEqual(
- psutil.swap_memory().free, free, delta=SYSMEM_TOLERANCE)
+ psutil.swap_memory().free, free, delta=TOLERANCE_SYS_MEM)
def test_swapmem_used(self):
total, used, free = self.parse_swapinfo()
self.assertAlmostEqual(
- psutil.swap_memory().used, used, delta=SYSMEM_TOLERANCE)
+ psutil.swap_memory().used, used, delta=TOLERANCE_SYS_MEM)
def test_swapmem_total(self):
total, used, free = self.parse_swapinfo()
self.assertAlmostEqual(
- psutil.swap_memory().total, total, delta=SYSMEM_TOLERANCE)
+ psutil.swap_memory().total, total, delta=TOLERANCE_SYS_MEM)
# --- others
@@ -474,6 +475,7 @@ class FreeBSDSystemTestCase(PsutilTestCase):
psutil.sensors_temperatures()["coretemp"][cpu].high,
sysctl_result)
+
# =====================================================================
# --- OpenBSD
# =====================================================================
@@ -512,27 +514,27 @@ class NetBSDTestCase(PsutilTestCase):
def test_vmem_free(self):
self.assertAlmostEqual(
psutil.virtual_memory().free, self.parse_meminfo("MemFree:"),
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
def test_vmem_buffers(self):
self.assertAlmostEqual(
psutil.virtual_memory().buffers, self.parse_meminfo("Buffers:"),
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
def test_vmem_shared(self):
self.assertAlmostEqual(
psutil.virtual_memory().shared, self.parse_meminfo("MemShared:"),
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
def test_swapmem_total(self):
self.assertAlmostEqual(
psutil.swap_memory().total, self.parse_meminfo("SwapTotal:"),
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
def test_swapmem_free(self):
self.assertAlmostEqual(
psutil.swap_memory().free, self.parse_meminfo("SwapFree:"),
- delta=SYSMEM_TOLERANCE)
+ delta=TOLERANCE_SYS_MEM)
def test_swapmem_used(self):
smem = psutil.swap_memory()
diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py
index 7fcc2f98..6bbf2194 100755
--- a/psutil/tests/test_connections.py
+++ b/psutil/tests/test_connections.py
@@ -6,11 +6,9 @@
"""Tests for net_connections() and Process.connections() APIs."""
-import contextlib
-import errno
import os
import socket
-import string
+import sys
import textwrap
from contextlib import closing
from socket import AF_INET
@@ -32,18 +30,16 @@ from psutil._compat import PY3
from psutil.tests import AF_UNIX
from psutil.tests import bind_socket
from psutil.tests import bind_unix_socket
-from psutil.tests import check_net_address
-from psutil.tests import CIRRUS
+from psutil.tests import check_connection_ntuple
from psutil.tests import create_sockets
-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
from psutil.tests import tcp_socketpair
-from psutil.tests import TRAVIS
from psutil.tests import unittest
from psutil.tests import unix_socketpair
from psutil.tests import wait_for_file
@@ -51,10 +47,11 @@ from psutil.tests import wait_for_file
thisproc = psutil.Process()
SOCK_SEQPACKET = getattr(socket, "SOCK_SEQPACKET", object())
+PYTHON_39 = sys.version_info[:2] == (3, 9)
@serialrun
-class _ConnTestCase(PsutilTestCase):
+class ConnectionTestCase(PsutilTestCase):
def setUp(self):
if not (NETBSD or FREEBSD):
@@ -89,93 +86,19 @@ class _ConnTestCase(PsutilTestCase):
proc_cons.sort()
self.assertEqual(proc_cons, sys_cons)
- def check_connection_ntuple(self, conn):
- """Check validity of a connection namedtuple."""
- def check_ntuple(conn):
- has_pid = len(conn) == 7
- self.assertIn(len(conn), (6, 7))
- self.assertEqual(conn[0], conn.fd)
- self.assertEqual(conn[1], conn.family)
- self.assertEqual(conn[2], conn.type)
- self.assertEqual(conn[3], conn.laddr)
- self.assertEqual(conn[4], conn.raddr)
- self.assertEqual(conn[5], conn.status)
- if has_pid:
- self.assertEqual(conn[6], conn.pid)
-
- def check_family(conn):
- self.assertIn(conn.family, (AF_INET, AF_INET6, AF_UNIX))
- if enum is not None:
- assert isinstance(conn.family, enum.IntEnum), conn
- else:
- assert isinstance(conn.family, int), conn
- if conn.family == AF_INET:
- # actually try to bind the local socket; ignore IPv6
- # sockets as their address might be represented as
- # an IPv4-mapped-address (e.g. "::127.0.0.1")
- # and that's rejected by bind()
- s = socket.socket(conn.family, conn.type)
- with contextlib.closing(s):
- try:
- s.bind((conn.laddr[0], 0))
- except socket.error as err:
- if err.errno != errno.EADDRNOTAVAIL:
- raise
- elif conn.family == AF_UNIX:
- self.assertEqual(conn.status, psutil.CONN_NONE)
-
- def check_type(conn):
- # SOCK_SEQPACKET may happen in case of AF_UNIX socks
- self.assertIn(conn.type, (SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET))
- if enum is not None:
- assert isinstance(conn.type, enum.IntEnum), conn
- else:
- assert isinstance(conn.type, int), conn
- if conn.type == SOCK_DGRAM:
- self.assertEqual(conn.status, psutil.CONN_NONE)
-
- def check_addrs(conn):
- # check IP address and port sanity
- for addr in (conn.laddr, conn.raddr):
- if conn.family in (AF_INET, AF_INET6):
- self.assertIsInstance(addr, tuple)
- if not addr:
- continue
- self.assertIsInstance(addr.port, int)
- assert 0 <= addr.port <= 65535, addr.port
- check_net_address(addr.ip, conn.family)
- elif conn.family == AF_UNIX:
- self.assertIsInstance(addr, str)
-
- def check_status(conn):
- self.assertIsInstance(conn.status, str)
- valids = [getattr(psutil, x) for x in dir(psutil)
- if x.startswith('CONN_')]
- self.assertIn(conn.status, valids)
- if conn.family in (AF_INET, AF_INET6) and conn.type == SOCK_STREAM:
- self.assertNotEqual(conn.status, psutil.CONN_NONE)
- else:
- self.assertEqual(conn.status, psutil.CONN_NONE)
- check_ntuple(conn)
- check_family(conn)
- check_type(conn)
- check_addrs(conn)
- check_status(conn)
-
-
-class TestBasicOperations(_ConnTestCase):
+class TestBasicOperations(ConnectionTestCase):
@unittest.skipIf(SKIP_SYSCONS, "requires root")
def test_system(self):
with create_sockets():
for conn in psutil.net_connections(kind='all'):
- self.check_connection_ntuple(conn)
+ check_connection_ntuple(conn)
def test_process(self):
with create_sockets():
for conn in psutil.Process().connections(kind='all'):
- self.check_connection_ntuple(conn)
+ check_connection_ntuple(conn)
def test_invalid_kind(self):
self.assertRaises(ValueError, thisproc.connections, kind='???')
@@ -183,7 +106,7 @@ class TestBasicOperations(_ConnTestCase):
@serialrun
-class TestUnconnectedSockets(_ConnTestCase):
+class TestUnconnectedSockets(ConnectionTestCase):
"""Tests sockets which are open but not connected to anything."""
def get_conn_from_sock(self, sock):
@@ -205,7 +128,7 @@ class TestUnconnectedSockets(_ConnTestCase):
only (the one supposed to be checked).
"""
conn = self.get_conn_from_sock(sock)
- self.check_connection_ntuple(conn)
+ check_connection_ntuple(conn)
# fd, family, type
if conn.fd != -1:
@@ -235,7 +158,7 @@ class TestUnconnectedSockets(_ConnTestCase):
return conn
def test_tcp_v4(self):
- addr = ("127.0.0.1", get_free_port())
+ addr = ("127.0.0.1", 0)
with closing(bind_socket(AF_INET, SOCK_STREAM, addr=addr)) as sock:
conn = self.check_socket(sock)
assert not conn.raddr
@@ -243,14 +166,14 @@ class TestUnconnectedSockets(_ConnTestCase):
@unittest.skipIf(not supports_ipv6(), "IPv6 not supported")
def test_tcp_v6(self):
- addr = ("::1", get_free_port())
+ addr = ("::1", 0)
with closing(bind_socket(AF_INET6, SOCK_STREAM, addr=addr)) as sock:
conn = self.check_socket(sock)
assert not conn.raddr
self.assertEqual(conn.status, psutil.CONN_LISTEN)
def test_udp_v4(self):
- addr = ("127.0.0.1", get_free_port())
+ addr = ("127.0.0.1", 0)
with closing(bind_socket(AF_INET, SOCK_DGRAM, addr=addr)) as sock:
conn = self.check_socket(sock)
assert not conn.raddr
@@ -258,7 +181,7 @@ class TestUnconnectedSockets(_ConnTestCase):
@unittest.skipIf(not supports_ipv6(), "IPv6 not supported")
def test_udp_v6(self):
- addr = ("::1", get_free_port())
+ addr = ("::1", 0)
with closing(bind_socket(AF_INET6, SOCK_DGRAM, addr=addr)) as sock:
conn = self.check_socket(sock)
assert not conn.raddr
@@ -282,7 +205,7 @@ class TestUnconnectedSockets(_ConnTestCase):
@serialrun
-class TestConnectedSocket(_ConnTestCase):
+class TestConnectedSocket(ConnectionTestCase):
"""Test socket pairs which are are actually connected to
each other.
"""
@@ -291,7 +214,7 @@ class TestConnectedSocket(_ConnTestCase):
# in TIME_WAIT state.
@unittest.skipIf(SUNOS, "unreliable on SUONS")
def test_tcp(self):
- addr = ("127.0.0.1", get_free_port())
+ addr = ("127.0.0.1", 0)
assert not thisproc.connections(kind='tcp4')
server, client = tcp_socketpair(AF_INET, addr=addr)
try:
@@ -321,9 +244,6 @@ class TestConnectedSocket(_ConnTestCase):
# On NetBSD creating a UNIX socket will cause
# a UNIX connection to /var/run/log.
cons = [c for c in cons if c.raddr != '/var/run/log']
- if CIRRUS:
- cons = [c for c in cons if c.fd in
- (server.fileno(), client.fileno())]
self.assertEqual(len(cons), 2, msg=cons)
if LINUX or FREEBSD or SUNOS:
# remote path is never set
@@ -346,7 +266,7 @@ class TestConnectedSocket(_ConnTestCase):
client.close()
-class TestFilters(_ConnTestCase):
+class TestFilters(ConnectionTestCase):
def test_filters(self):
def check(kind, families, types):
@@ -393,10 +313,12 @@ 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")
- self.check_connection_ntuple(conn)
+ check_connection_ntuple(conn)
self.assertEqual(conn.family, family)
self.assertEqual(conn.type, type)
self.assertEqual(conn.laddr, laddr)
@@ -416,45 +338,45 @@ class TestFilters(_ConnTestCase):
tcp_template = textwrap.dedent("""
import socket, time
- s = socket.socket($family, socket.SOCK_STREAM)
- s.bind(('$addr', 0))
+ s = socket.socket({family}, socket.SOCK_STREAM)
+ s.bind(('{addr}', 0))
s.listen(5)
- with open('$testfn', 'w') as f:
+ with open('{testfn}', 'w') as f:
f.write(str(s.getsockname()[:2]))
time.sleep(60)
- """)
+ """)
udp_template = textwrap.dedent("""
import socket, time
- s = socket.socket($family, socket.SOCK_DGRAM)
- s.bind(('$addr', 0))
- with open('$testfn', 'w') as f:
+ s = socket.socket({family}, socket.SOCK_DGRAM)
+ s.bind(('{addr}', 0))
+ with open('{testfn}', 'w') as f:
f.write(str(s.getsockname()[:2]))
time.sleep(60)
- """)
+ """)
# must be relative on Windows
testfile = os.path.basename(self.get_testfn(dir=os.getcwd()))
- tcp4_template = string.Template(tcp_template).substitute(
+ tcp4_template = tcp_template.format(
family=int(AF_INET), addr="127.0.0.1", testfn=testfile)
- udp4_template = string.Template(udp_template).substitute(
+ udp4_template = udp_template.format(
family=int(AF_INET), addr="127.0.0.1", testfn=testfile)
- tcp6_template = string.Template(tcp_template).substitute(
+ tcp6_template = tcp_template.format(
family=int(AF_INET6), addr="::1", testfn=testfile)
- udp6_template = string.Template(udp_template).substitute(
+ udp6_template = udp_template.format(
family=int(AF_INET6), addr="::1", testfn=testfile)
# launch various subprocess instantiating a socket of various
# families and types to enrich psutil results
tcp4_proc = self.pyrun(tcp4_template)
- tcp4_addr = eval(wait_for_file(testfile))
+ tcp4_addr = eval(wait_for_file(testfile, delete=True))
udp4_proc = self.pyrun(udp4_template)
- udp4_addr = eval(wait_for_file(testfile))
+ udp4_addr = eval(wait_for_file(testfile, delete=True))
if supports_ipv6():
tcp6_proc = self.pyrun(tcp6_template)
- tcp6_addr = eval(wait_for_file(testfile))
+ tcp6_addr = eval(wait_for_file(testfile, delete=True))
udp6_proc = self.pyrun(udp6_template)
- udp6_addr = eval(wait_for_file(testfile))
+ udp6_addr = eval(wait_for_file(testfile, delete=True))
else:
tcp6_proc = None
udp6_proc = None
@@ -546,7 +468,7 @@ class TestFilters(_ConnTestCase):
@unittest.skipIf(SKIP_SYSCONS, "requires root")
-class TestSystemWideConnections(_ConnTestCase):
+class TestSystemWideConnections(ConnectionTestCase):
"""Tests for net_connections()."""
def test_it(self):
@@ -555,7 +477,7 @@ class TestSystemWideConnections(_ConnTestCase):
self.assertIn(conn.family, families, msg=conn)
if conn.family != AF_UNIX:
self.assertIn(conn.type, types_, msg=conn)
- self.check_connection_ntuple(conn)
+ check_connection_ntuple(conn)
with create_sockets():
from psutil._common import conn_tmap
@@ -568,8 +490,7 @@ class TestSystemWideConnections(_ConnTestCase):
self.assertEqual(len(cons), len(set(cons)))
check(cons, families, types_)
- # 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 edeb1d9a..32c75fd7 100755
--- a/psutil/tests/test_contracts.py
+++ b/psutil/tests/test_contracts.py
@@ -14,6 +14,7 @@ import multiprocessing
import os
import signal
import stat
+import sys
import time
import traceback
@@ -28,20 +29,24 @@ from psutil import OSX
from psutil import POSIX
from psutil import SUNOS
from psutil import WINDOWS
-from psutil._common import isfile_strict
from psutil._compat import FileNotFoundError
from psutil._compat import long
from psutil._compat import range
+from psutil.tests import APPVEYOR
+from psutil.tests import check_connection_ntuple
+from psutil.tests import CI_TESTING
from psutil.tests import create_sockets
from psutil.tests import enum
-from psutil.tests import get_kernel_version
+from psutil.tests import GITHUB_ACTIONS
from psutil.tests import HAS_CPU_FREQ
from psutil.tests import HAS_NET_IO_COUNTERS
from psutil.tests import HAS_SENSORS_FANS
from psutil.tests import HAS_SENSORS_TEMPERATURES
from psutil.tests import is_namedtuple
+from psutil.tests import kernel_version
from psutil.tests import process_namespace
from psutil.tests import PsutilTestCase
+from psutil.tests import PYPY
from psutil.tests import serialrun
from psutil.tests import SKIP_SYSCONS
from psutil.tests import unittest
@@ -85,29 +90,38 @@ class TestAvailConstantsAPIs(PsutilTestCase):
ae(hasattr(psutil, "IOPRIO_LOW"), WINDOWS)
ae(hasattr(psutil, "IOPRIO_VERYLOW"), WINDOWS)
- def test_linux_rlimit(self):
+ @unittest.skipIf(GITHUB_ACTIONS and LINUX,
+ "unsupported on GITHUB_ACTIONS + LINUX")
+ def test_rlimit(self):
ae = self.assertEqual
- hasit = LINUX and get_kernel_version() >= (2, 6, 36)
- ae(hasattr(psutil.Process, "rlimit"), hasit)
- ae(hasattr(psutil, "RLIM_INFINITY"), hasit)
- ae(hasattr(psutil, "RLIMIT_AS"), hasit)
- ae(hasattr(psutil, "RLIMIT_CORE"), hasit)
- ae(hasattr(psutil, "RLIMIT_CPU"), hasit)
- ae(hasattr(psutil, "RLIMIT_DATA"), hasit)
- ae(hasattr(psutil, "RLIMIT_FSIZE"), hasit)
- ae(hasattr(psutil, "RLIMIT_LOCKS"), hasit)
- ae(hasattr(psutil, "RLIMIT_MEMLOCK"), hasit)
- ae(hasattr(psutil, "RLIMIT_NOFILE"), hasit)
- ae(hasattr(psutil, "RLIMIT_NPROC"), hasit)
- ae(hasattr(psutil, "RLIMIT_RSS"), hasit)
- ae(hasattr(psutil, "RLIMIT_STACK"), hasit)
-
- hasit = LINUX and get_kernel_version() >= (3, 0)
- ae(hasattr(psutil, "RLIMIT_MSGQUEUE"), hasit)
- ae(hasattr(psutil, "RLIMIT_NICE"), hasit)
- ae(hasattr(psutil, "RLIMIT_RTPRIO"), hasit)
- ae(hasattr(psutil, "RLIMIT_RTTIME"), hasit)
- ae(hasattr(psutil, "RLIMIT_SIGPENDING"), hasit)
+ ae(hasattr(psutil, "RLIM_INFINITY"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_AS"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_CORE"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_CPU"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_DATA"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_FSIZE"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_MEMLOCK"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_NOFILE"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_NPROC"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_RSS"), LINUX or FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_STACK"), LINUX or FREEBSD)
+
+ ae(hasattr(psutil, "RLIMIT_LOCKS"), LINUX)
+ if POSIX:
+ if kernel_version() >= (2, 6, 8):
+ ae(hasattr(psutil, "RLIMIT_MSGQUEUE"), LINUX)
+ if kernel_version() >= (2, 6, 12):
+ ae(hasattr(psutil, "RLIMIT_NICE"), LINUX)
+ if kernel_version() >= (2, 6, 12):
+ ae(hasattr(psutil, "RLIMIT_RTPRIO"), LINUX)
+ if kernel_version() >= (2, 6, 25):
+ ae(hasattr(psutil, "RLIMIT_RTTIME"), LINUX)
+ if kernel_version() >= (2, 6, 8):
+ ae(hasattr(psutil, "RLIMIT_SIGPENDING"), LINUX)
+
+ ae(hasattr(psutil, "RLIMIT_SWAP"), FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_SBSIZE"), FREEBSD)
+ ae(hasattr(psutil, "RLIMIT_NPTS"), FREEBSD)
class TestAvailSystemAPIs(PsutilTestCase):
@@ -119,11 +133,8 @@ class TestAvailSystemAPIs(PsutilTestCase):
self.assertEqual(hasattr(psutil, "win_service_get"), WINDOWS)
def test_cpu_freq(self):
- linux = (LINUX and
- (os.path.exists("/sys/devices/system/cpu/cpufreq") or
- os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq")))
self.assertEqual(hasattr(psutil, "cpu_freq"),
- linux or MACOS or WINDOWS or FREEBSD)
+ LINUX or MACOS or WINDOWS or FREEBSD)
def test_sensors_temperatures(self):
self.assertEqual(
@@ -141,7 +152,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)
@@ -155,8 +167,10 @@ class TestAvailProcessAPIs(PsutilTestCase):
def test_ionice(self):
self.assertEqual(hasattr(psutil.Process, "ionice"), LINUX or WINDOWS)
+ @unittest.skipIf(GITHUB_ACTIONS and LINUX,
+ "unsupported on GITHUB_ACTIONS + LINUX")
def test_rlimit(self):
- self.assertEqual(hasattr(psutil.Process, "rlimit"), LINUX)
+ self.assertEqual(hasattr(psutil.Process, "rlimit"), LINUX or FREEBSD)
def test_io_counters(self):
hasit = hasattr(psutil.Process, "io_counters")
@@ -239,6 +253,8 @@ class TestSystemAPITypes(PsutilTestCase):
self.assertIsInstance(disk.mountpoint, str)
self.assertIsInstance(disk.fstype, str)
self.assertIsInstance(disk.opts, str)
+ self.assertIsInstance(disk.maxfile, int)
+ self.assertIsInstance(disk.maxpath, int)
@unittest.skipIf(SKIP_SYSCONS, "requires root")
def test_net_connections(self):
@@ -253,7 +269,7 @@ class TestSystemAPITypes(PsutilTestCase):
for ifname, addrs in psutil.net_if_addrs().items():
self.assertIsInstance(ifname, str)
for addr in addrs:
- if enum is not None:
+ if enum is not None and not PYPY:
self.assertIsInstance(addr.family, enum.IntEnum)
else:
self.assertIsInstance(addr.family, int)
@@ -338,9 +354,9 @@ def proc_info(pid):
tcase.assertEqual(exc.pid, pid)
tcase.assertEqual(exc.name, name)
if isinstance(exc, psutil.ZombieProcess):
- # XXX investigate zombie/ppid relation on POSIX
- # tcase.assertEqual(exc.ppid, ppid)
- pass
+ if exc.ppid is not None:
+ tcase.assertGreaterEqual(exc.ppid, 0)
+ tcase.assertEqual(exc.ppid, ppid)
elif isinstance(exc, psutil.NoSuchProcess):
tcase.assertProcessGone(proc)
str(exc)
@@ -362,18 +378,15 @@ def proc_info(pid):
name, ppid = d['name'], d['ppid']
info = {'pid': proc.pid}
ns = process_namespace(proc)
- with proc.oneshot():
- for fun, fun_name in ns.iter(ns.getters, clear_cache=False):
- try:
- info[fun_name] = fun()
- except psutil.NoSuchProcess as exc:
- check_exception(exc, proc, name, ppid)
- do_wait()
- return info
- except psutil.AccessDenied as exc:
- check_exception(exc, proc, name, ppid)
- continue
- do_wait()
+ # We don't use oneshot() because in order not to fool
+ # check_exception() in case of NSP.
+ for fun, fun_name in ns.iter(ns.getters, clear_cache=False):
+ try:
+ info[fun_name] = fun()
+ except psutil.Error as exc:
+ check_exception(exc, proc, name, ppid)
+ continue
+ do_wait()
return info
@@ -391,13 +404,15 @@ class TestFetchAllProcesses(PsutilTestCase):
self.pool.terminate()
self.pool.join()
- def test_all(self):
+ def iter_proc_info(self):
# Fixes "can't pickle <function proc_info>: it's not the
# same object as test_contracts.proc_info".
from psutil.tests.test_contracts import proc_info
+ return self.pool.imap_unordered(proc_info, psutil.pids())
+ def test_all(self):
failures = []
- for info in self.pool.imap_unordered(proc_info, psutil.pids()):
+ for info in self.iter_proc_info():
for name, value in info.items():
meth = getattr(self, name)
try:
@@ -435,8 +450,12 @@ class TestFetchAllProcesses(PsutilTestCase):
# http://stackoverflow.com/questions/3112546/os-path-exists-lies
if POSIX and os.path.isfile(ret):
if hasattr(os, 'access') and hasattr(os, "X_OK"):
- # XXX may fail on MACOS
- assert os.access(ret, os.X_OK)
+ # XXX: may fail on MACOS
+ try:
+ assert os.access(ret, os.X_OK)
+ except AssertionError:
+ if os.path.exists(ret) and not CI_TESTING:
+ raise
def pid(self, ret, info):
self.assertIsInstance(ret, int)
@@ -448,6 +467,8 @@ class TestFetchAllProcesses(PsutilTestCase):
def name(self, ret, info):
self.assertIsInstance(ret, str)
+ if APPVEYOR and not ret and info['status'] == 'stopped':
+ return
# on AIX, "<exiting>" processes don't have names
if not AIX:
assert ret
@@ -518,6 +539,8 @@ class TestFetchAllProcesses(PsutilTestCase):
def num_threads(self, ret, info):
self.assertIsInstance(ret, int)
+ if APPVEYOR and not ret and info['status'] == 'stopped':
+ return
self.assertGreaterEqual(ret, 1)
def threads(self, ret, info):
@@ -596,9 +619,11 @@ class TestFetchAllProcesses(PsutilTestCase):
continue
assert os.path.isabs(f.path), f
try:
- assert isfile_strict(f.path), f
+ st = os.stat(f.path)
except FileNotFoundError:
pass
+ else:
+ assert stat.S_ISREG(st.st_mode), f
def num_fds(self, ret, info):
self.assertIsInstance(ret, int)
@@ -609,6 +634,7 @@ class TestFetchAllProcesses(PsutilTestCase):
self.assertEqual(len(ret), len(set(ret)))
for conn in ret:
assert is_namedtuple(conn)
+ check_connection_ntuple(conn)
def cwd(self, ret, info):
if ret: # 'ret' can be None or empty
@@ -681,6 +707,10 @@ class TestFetchAllProcesses(PsutilTestCase):
priorities = [getattr(psutil, x) for x in dir(psutil)
if x.endswith('_PRIORITY_CLASS')]
self.assertIn(ret, priorities)
+ if sys.version_info > (3, 4):
+ self.assertIsInstance(ret, enum.IntEnum)
+ else:
+ self.assertIsInstance(ret, int)
def num_ctx_switches(self, ret, info):
assert is_namedtuple(ret)
diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py
index 709c77ba..f5243c2c 100755
--- a/psutil/tests/test_linux.py
+++ b/psutil/tests/test_linux.py
@@ -28,6 +28,7 @@ from psutil._compat import FileNotFoundError
from psutil._compat import PY3
from psutil._compat import u
from psutil.tests import call_until
+from psutil.tests import GLOBAL_TIMEOUT
from psutil.tests import HAS_BATTERY
from psutil.tests import HAS_CPU_FREQ
from psutil.tests import HAS_GETLOADAVG
@@ -40,9 +41,9 @@ from psutil.tests import retry_on_failure
from psutil.tests import safe_rmpath
from psutil.tests import sh
from psutil.tests import skip_on_not_implemented
-from psutil.tests import SYSMEM_TOLERANCE
from psutil.tests import ThreadTask
-from psutil.tests import TRAVIS
+from psutil.tests import TOLERANCE_DISK_USAGE
+from psutil.tests import TOLERANCE_SYS_MEM
from psutil.tests import unittest
from psutil.tests import which
@@ -51,6 +52,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*')
@@ -73,6 +76,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]
@@ -138,6 +184,8 @@ def vmstat(stat):
def get_free_version_info():
out = sh("free -V").strip()
+ if 'UNKNOWN' in out:
+ raise unittest.SkipTest("can't determine free version")
return tuple(map(int, out.split()[-1].split('.')))
@@ -195,55 +243,51 @@ class TestSystemVirtualMemory(PsutilTestCase):
# self.assertEqual(free_value, psutil_value)
vmstat_value = vmstat('total memory') * 1024
psutil_value = psutil.virtual_memory().total
- self.assertAlmostEqual(vmstat_value, psutil_value)
-
- # Older versions of procps used slab memory to calculate used memory.
- # This got changed in:
- # https://gitlab.com/procps-ng/procps/commit/
- # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
- @unittest.skipIf(LINUX and get_free_version_info() < (3, 3, 12),
- "old free version")
+ self.assertAlmostEqual(
+ vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM)
+
@retry_on_failure()
def test_used(self):
+ # Older versions of procps used slab memory to calculate used memory.
+ # This got changed in:
+ # https://gitlab.com/procps-ng/procps/commit/
+ # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
+ if get_free_version_info() < (3, 3, 12):
+ raise self.skipTest("old free version")
free = free_physmem()
free_value = free.used
psutil_value = psutil.virtual_memory().used
self.assertAlmostEqual(
- free_value, psutil_value, delta=SYSMEM_TOLERANCE,
+ free_value, psutil_value, delta=TOLERANCE_SYS_MEM,
msg='%s %s \n%s' % (free_value, psutil_value, free.output))
- @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
@retry_on_failure()
def test_free(self):
vmstat_value = vmstat('free memory') * 1024
psutil_value = psutil.virtual_memory().free
self.assertAlmostEqual(
- vmstat_value, psutil_value, delta=SYSMEM_TOLERANCE)
+ vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_buffers(self):
vmstat_value = vmstat('buffer memory') * 1024
psutil_value = psutil.virtual_memory().buffers
self.assertAlmostEqual(
- vmstat_value, psutil_value, delta=SYSMEM_TOLERANCE)
+ vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM)
- # https://travis-ci.org/giampaolo/psutil/jobs/226719664
- @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
@retry_on_failure()
def test_active(self):
vmstat_value = vmstat('active memory') * 1024
psutil_value = psutil.virtual_memory().active
self.assertAlmostEqual(
- vmstat_value, psutil_value, delta=SYSMEM_TOLERANCE)
+ vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM)
- # https://travis-ci.org/giampaolo/psutil/jobs/227242952
- @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
@retry_on_failure()
def test_inactive(self):
vmstat_value = vmstat('inactive memory') * 1024
psutil_value = psutil.virtual_memory().inactive
self.assertAlmostEqual(
- vmstat_value, psutil_value, delta=SYSMEM_TOLERANCE)
+ vmstat_value, psutil_value, delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_shared(self):
@@ -253,7 +297,7 @@ class TestSystemVirtualMemory(PsutilTestCase):
raise unittest.SkipTest("free does not support 'shared' column")
psutil_value = psutil.virtual_memory().shared
self.assertAlmostEqual(
- free_value, psutil_value, delta=SYSMEM_TOLERANCE,
+ free_value, psutil_value, delta=TOLERANCE_SYS_MEM,
msg='%s %s \n%s' % (free_value, psutil_value, free.output))
@retry_on_failure()
@@ -268,7 +312,7 @@ class TestSystemVirtualMemory(PsutilTestCase):
free_value = int(lines[1].split()[-1])
psutil_value = psutil.virtual_memory().available
self.assertAlmostEqual(
- free_value, psutil_value, delta=SYSMEM_TOLERANCE,
+ free_value, psutil_value, delta=TOLERANCE_SYS_MEM,
msg='%s %s \n%s' % (free_value, psutil_value, out))
def test_warnings_on_misses(self):
@@ -506,21 +550,21 @@ class TestSystemSwapMemory(PsutilTestCase):
free_value = free_swap().total
psutil_value = psutil.swap_memory().total
return self.assertAlmostEqual(
- free_value, psutil_value, delta=SYSMEM_TOLERANCE)
+ free_value, psutil_value, delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_used(self):
free_value = free_swap().used
psutil_value = psutil.swap_memory().used
return self.assertAlmostEqual(
- free_value, psutil_value, delta=SYSMEM_TOLERANCE)
+ free_value, psutil_value, delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_free(self):
free_value = free_swap().free
psutil_value = psutil.swap_memory().free
return self.assertAlmostEqual(
- free_value, psutil_value, delta=SYSMEM_TOLERANCE)
+ free_value, psutil_value, delta=TOLERANCE_SYS_MEM)
def test_missing_sin_sout(self):
with mock.patch('psutil._common.open', create=True) as m:
@@ -570,7 +614,7 @@ class TestSystemSwapMemory(PsutilTestCase):
total *= unit_multiplier
free *= unit_multiplier
self.assertEqual(swap.total, total)
- self.assertAlmostEqual(swap.free, free, delta=SYSMEM_TOLERANCE)
+ self.assertAlmostEqual(swap.free, free, delta=TOLERANCE_SYS_MEM)
def test_emulate_meminfo_has_no_metrics(self):
# Emulate a case where /proc/meminfo provides no swap metrics
@@ -589,7 +633,6 @@ class TestSystemSwapMemory(PsutilTestCase):
@unittest.skipIf(not LINUX, "LINUX only")
class TestSystemCPUTimes(PsutilTestCase):
- @unittest.skipIf(TRAVIS, "unknown failure on travis")
def test_fields(self):
fields = psutil.cpu_times()._fields
kernel_ver = re.findall(r'\d+\.\d+\.\d+', os.uname()[2])[0]
@@ -673,7 +716,7 @@ class TestSystemCPUCountLogical(PsutilTestCase):
@unittest.skipIf(not LINUX, "LINUX only")
-class TestSystemCPUCountPhysical(PsutilTestCase):
+class TestSystemCPUCountCores(PsutilTestCase):
@unittest.skipIf(not which("lscpu"), "lscpu utility not available")
def test_against_lscpu(self):
@@ -685,10 +728,18 @@ class TestSystemCPUCountPhysical(PsutilTestCase):
core_ids.add(fields[1])
self.assertEqual(psutil.cpu_count(logical=False), len(core_ids))
+ def test_method_2(self):
+ meth_1 = psutil._pslinux.cpu_count_cores()
+ with mock.patch('glob.glob', return_value=[]) as m:
+ meth_2 = psutil._pslinux.cpu_count_cores()
+ assert m.called
+ if meth_1 is not None:
+ self.assertEqual(meth_1, meth_2)
+
def test_emulate_none(self):
with mock.patch('glob.glob', return_value=[]) as m1:
with mock.patch('psutil._common.open', create=True) as m2:
- self.assertIsNone(psutil._pslinux.cpu_count_physical())
+ self.assertIsNone(psutil._pslinux.cpu_count_cores())
assert m1.called
assert m2.called
@@ -696,7 +747,6 @@ class TestSystemCPUCountPhysical(PsutilTestCase):
@unittest.skipIf(not LINUX, "LINUX only")
class TestSystemCPUFrequency(PsutilTestCase):
- @unittest.skipIf(TRAVIS, "fails on Travis")
@unittest.skipIf(not HAS_CPU_FREQ, "not supported")
def test_emulate_use_second_file(self):
# https://github.com/giampaolo/psutil/issues/981
@@ -719,18 +769,14 @@ class TestSystemCPUFrequency(PsutilTestCase):
if path.startswith('/sys/devices/system/cpu/'):
return False
else:
- if path == "/proc/cpuinfo":
- flags.append(None)
return os_path_exists(path)
- flags = []
os_path_exists = os.path.exists
try:
with mock.patch("os.path.exists", side_effect=path_exists_mock):
reload_module(psutil._pslinux)
ret = psutil.cpu_freq()
assert ret
- assert flags
self.assertEqual(ret.max, 0.0)
self.assertEqual(ret.min, 0.0)
for freq in psutil.cpu_freq(percpu=True):
@@ -817,7 +863,6 @@ class TestSystemCPUFrequency(PsutilTestCase):
if freq[1].max != 0.0:
self.assertEqual(freq[1].max, 600.0)
- @unittest.skipIf(TRAVIS, "fails on Travis")
@unittest.skipIf(not HAS_CPU_FREQ, "not supported")
def test_emulate_no_scaling_cur_freq_file(self):
# See: https://github.com/giampaolo/psutil/issues/1071
@@ -844,13 +889,11 @@ class TestSystemCPUFrequency(PsutilTestCase):
@unittest.skipIf(not LINUX, "LINUX only")
class TestSystemCPUStats(PsutilTestCase):
- @unittest.skipIf(TRAVIS, "fails on Travis")
def test_ctx_switches(self):
vmstat_value = vmstat("context switches")
psutil_value = psutil.cpu_stats().ctx_switches
self.assertAlmostEqual(vmstat_value, psutil_value, delta=500)
- @unittest.skipIf(TRAVIS, "fails on Travis")
def test_interrupts(self):
vmstat_value = vmstat("interrupts")
psutil_value = psutil.cpu_stats().interrupts
@@ -886,11 +929,24 @@ 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")
- # @unittest.skipIf(TRAVIS, "skipped on Travis")
# def test_net_if_names(self):
# out = sh("ip addr").strip()
# nics = [x for x in psutil.net_if_addrs().keys() if ':' not in x]
@@ -915,11 +971,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):
@@ -1023,11 +1083,10 @@ class TestSystemDiskPartitions(PsutilTestCase):
usage = psutil.disk_usage(part.mountpoint)
dev, total, used, free = df(part.mountpoint)
self.assertEqual(usage.total, total)
- # 10 MB tollerance
- if abs(usage.free - free) > 10 * 1024 * 1024:
- self.fail("psutil=%s, df=%s" % (usage.free, free))
- if abs(usage.used - used) > 10 * 1024 * 1024:
- self.fail("psutil=%s, df=%s" % (usage.used, used))
+ self.assertAlmostEqual(usage.free, free,
+ delta=TOLERANCE_DISK_USAGE)
+ self.assertAlmostEqual(usage.used, used,
+ delta=TOLERANCE_DISK_USAGE)
def test_zfs_fs(self):
# Test that ZFS partitions are returned.
@@ -1373,8 +1432,10 @@ class TestMisc(PsutilTestCase):
t.start()
try:
p = psutil.Process()
- tid = p.threads()[1].id
- assert not psutil.pid_exists(tid), tid
+ threads = p.threads()
+ self.assertEqual(len(threads), 2)
+ tid = sorted(threads, key=lambda x: x.id)[1].id
+ self.assertNotEqual(p.pid, tid)
pt = psutil.Process(tid)
pt.as_dict()
self.assertNotIn(tid, psutil.pids())
@@ -1406,17 +1467,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):
@@ -1499,17 +1549,6 @@ class TestSensorsBattery(PsutilTestCase):
self.assertIsNone(psutil.sensors_battery().power_plugged)
assert m.called
- def test_emulate_no_base_files(self):
- # Emulate a case where base metrics files are not present,
- # in which case we're supposed to get None.
- with mock_open_exception(
- "/sys/class/power_supply/BAT0/energy_now",
- IOError(errno.ENOENT, "")):
- with mock_open_exception(
- "/sys/class/power_supply/BAT0/charge_now",
- IOError(errno.ENOENT, "")):
- self.assertIsNone(psutil.sensors_battery())
-
def test_emulate_energy_full_0(self):
# Emulate a case where energy_full files returns 0.
with mock_open_content(
@@ -1545,6 +1584,29 @@ class TestSensorsBattery(PsutilTestCase):
@unittest.skipIf(not LINUX, "LINUX only")
+class TestSensorsBatteryEmulated(PsutilTestCase):
+
+ def test_it(self):
+ def open_mock(name, *args, **kwargs):
+ if name.endswith("/energy_now"):
+ return io.StringIO(u("60000000"))
+ elif name.endswith("/power_now"):
+ return io.StringIO(u("0"))
+ elif name.endswith("/energy_full"):
+ return io.StringIO(u("60000001"))
+ else:
+ return orig_open(name, *args, **kwargs)
+
+ orig_open = open
+ patch_point = 'builtins.open' if PY3 else '__builtin__.open'
+ with mock.patch('os.listdir', return_value=["BAT0"]) as mlistdir:
+ with mock.patch(patch_point, side_effect=open_mock) as mopen:
+ self.assertIsNotNone(psutil.sensors_battery())
+ assert mlistdir.called
+ assert mopen.called
+
+
+@unittest.skipIf(not LINUX, "LINUX only")
class TestSensorsTemperatures(PsutilTestCase):
def test_emulate_class_hwmon(self):
@@ -1703,7 +1765,7 @@ class TestProcess(PsutilTestCase):
def test_open_files_mode(self):
def get_test_file(fname):
p = psutil.Process()
- giveup_at = time.time() + 2
+ giveup_at = time.time() + GLOBAL_TIMEOUT
while True:
for file in p.open_files():
if file.path == os.path.abspath(fname):
@@ -1898,7 +1960,7 @@ class TestProcess(PsutilTestCase):
# Emulate a case where rlimit() raises ENOSYS, which may
# happen in case of zombie process:
# https://travis-ci.org/giampaolo/psutil/jobs/51368273
- with mock.patch("psutil._pslinux.cext.linux_prlimit",
+ with mock.patch("psutil._pslinux.prlimit",
side_effect=OSError(errno.ENOSYS, "")) as m:
p = psutil.Process()
p.name()
diff --git a/psutil/tests/test_memory_leaks.py b/psutil/tests/test_memleaks.py
index caac60c3..d53acd87 100755
--- a/psutil/tests/test_memory_leaks.py
+++ b/psutil/tests/test_memleaks.py
@@ -21,7 +21,6 @@ import os
import psutil
import psutil._common
-from psutil import FREEBSD
from psutil import LINUX
from psutil import MACOS
from psutil import OPENBSD
@@ -30,7 +29,6 @@ from psutil import SUNOS
from psutil import WINDOWS
from psutil._compat import ProcessLookupError
from psutil._compat import super
-from psutil.tests import CIRRUS
from psutil.tests import create_sockets
from psutil.tests import get_testfn
from psutil.tests import HAS_CPU_AFFINITY
@@ -52,17 +50,32 @@ from psutil.tests import spawn_testproc
from psutil.tests import system_namespace
from psutil.tests import terminate
from psutil.tests import TestMemoryLeak
-from psutil.tests import TRAVIS
from psutil.tests import unittest
-SKIP_PYTHON_IMPL = True
+
cext = psutil._psplatform.cext
thisproc = psutil.Process()
+FEW_TIMES = 5
-def skip_if_linux():
- return unittest.skipIf(LINUX and SKIP_PYTHON_IMPL,
- "worthless on LINUX (pure python)")
+def fewtimes_if_linux():
+ """Decorator for those Linux functions which are implemented in pure
+ Python, and which we want to run faster.
+ """
+ def decorator(fun):
+ @functools.wraps(fun)
+ def wrapper(self, *args, **kwargs):
+ if LINUX:
+ before = self.__class__.times
+ try:
+ self.__class__.times = FEW_TIMES
+ return fun(self, *args, **kwargs)
+ finally:
+ self.__class__.times = before
+ else:
+ return fun(self, *args, **kwargs)
+ return wrapper
+ return decorator
# ===================================================================
@@ -79,33 +92,33 @@ class TestProcessObjectLeaks(TestMemoryLeak):
ns = process_namespace(None)
ns.test_class_coverage(self, ns.getters + ns.setters)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_name(self):
self.execute(self.proc.name)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_cmdline(self):
self.execute(self.proc.cmdline)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_exe(self):
self.execute(self.proc.exe)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_ppid(self):
self.execute(self.proc.ppid)
@unittest.skipIf(not POSIX, "POSIX only")
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_uids(self):
self.execute(self.proc.uids)
@unittest.skipIf(not POSIX, "POSIX only")
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_gids(self):
self.execute(self.proc.gids)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_status(self):
self.execute(self.proc.status)
@@ -131,7 +144,7 @@ class TestProcessObjectLeaks(TestMemoryLeak):
self.execute_w_exc(OSError, fun)
@unittest.skipIf(not HAS_PROC_IO_COUNTERS, "not supported")
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_io_counters(self):
self.execute(self.proc.io_counters)
@@ -141,11 +154,11 @@ class TestProcessObjectLeaks(TestMemoryLeak):
psutil.Process().username()
self.execute(self.proc.username)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_create_time(self):
self.execute(self.proc.create_time)
- @skip_if_linux()
+ @fewtimes_if_linux()
@skip_on_access_denied(only_if=OPENBSD)
def test_num_threads(self):
self.execute(self.proc.num_threads)
@@ -155,47 +168,46 @@ class TestProcessObjectLeaks(TestMemoryLeak):
self.execute(self.proc.num_handles)
@unittest.skipIf(not POSIX, "POSIX only")
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_num_fds(self):
self.execute(self.proc.num_fds)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_num_ctx_switches(self):
self.execute(self.proc.num_ctx_switches)
- @skip_if_linux()
+ @fewtimes_if_linux()
@skip_on_access_denied(only_if=OPENBSD)
def test_threads(self):
self.execute(self.proc.threads)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_cpu_times(self):
self.execute(self.proc.cpu_times)
- @skip_if_linux()
+ @fewtimes_if_linux()
@unittest.skipIf(not HAS_PROC_CPU_NUM, "not supported")
def test_cpu_num(self):
self.execute(self.proc.cpu_num)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_memory_info(self):
self.execute(self.proc.memory_info)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_memory_full_info(self):
self.execute(self.proc.memory_full_info)
@unittest.skipIf(not POSIX, "POSIX only")
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_terminal(self):
self.execute(self.proc.terminal)
- @unittest.skipIf(POSIX and SKIP_PYTHON_IMPL,
- "worthless on POSIX (pure python)")
def test_resume(self):
- self.execute(self.proc.resume)
+ times = FEW_TIMES if POSIX else self.times
+ self.execute(self.proc.resume, times=times)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_cwd(self):
self.execute(self.proc.cwd)
@@ -207,17 +219,16 @@ class TestProcessObjectLeaks(TestMemoryLeak):
def test_cpu_affinity_set(self):
affinity = thisproc.cpu_affinity()
self.execute(lambda: self.proc.cpu_affinity(affinity))
- if not TRAVIS:
- self.execute_w_exc(
- ValueError, lambda: self.proc.cpu_affinity([-1]))
+ self.execute_w_exc(
+ ValueError, lambda: self.proc.cpu_affinity([-1]))
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_open_files(self):
with open(get_testfn(), 'w'):
self.execute(self.proc.open_files, times=100)
@unittest.skipIf(not HAS_MEMORY_MAPS, "not supported")
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_memory_maps(self):
self.execute(self.proc.memory_maps)
@@ -231,9 +242,9 @@ class TestProcessObjectLeaks(TestMemoryLeak):
def test_rlimit_set(self):
limit = thisproc.rlimit(psutil.RLIMIT_NOFILE)
self.execute(lambda: self.proc.rlimit(psutil.RLIMIT_NOFILE, limit))
- self.execute_w_exc(OSError, lambda: self.proc.rlimit(-1))
+ self.execute_w_exc((OSError, ValueError), lambda: self.proc.rlimit(-1))
- @skip_if_linux()
+ @fewtimes_if_linux()
# Windows implementation is based on a single system-wide
# function (tested later).
@unittest.skipIf(WINDOWS, "worthless on WINDOWS")
@@ -243,7 +254,7 @@ class TestProcessObjectLeaks(TestMemoryLeak):
# be executed.
with create_sockets():
kind = 'inet' if SUNOS else 'all'
- self.execute(lambda: self.proc.connections(kind), times=100)
+ self.execute(lambda: self.proc.connections(kind))
@unittest.skipIf(not HAS_ENVIRON, "not supported")
def test_environ(self):
@@ -274,7 +285,7 @@ class TestTerminatedProcessLeaks(TestProcessObjectLeaks):
super().tearDownClass()
terminate(cls.subp)
- def _call(self, fun):
+ def call(self, fun):
try:
fun()
except psutil.NoSuchProcess:
@@ -332,27 +343,27 @@ class TestModuleFunctionsLeaks(TestMemoryLeak):
# --- cpu
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_cpu_count(self): # logical
self.execute(lambda: psutil.cpu_count(logical=True))
- @skip_if_linux()
- def test_cpu_count_physical(self):
+ @fewtimes_if_linux()
+ def test_cpu_count_cores(self):
self.execute(lambda: psutil.cpu_count(logical=False))
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_cpu_times(self):
self.execute(psutil.cpu_times)
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_per_cpu_times(self):
self.execute(lambda: psutil.cpu_times(percpu=True))
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_cpu_stats(self):
self.execute(psutil.cpu_stats)
- @skip_if_linux()
+ @fewtimes_if_linux()
@unittest.skipIf(not HAS_CPU_FREQ, "not supported")
def test_cpu_freq(self):
self.execute(psutil.cpu_freq)
@@ -372,96 +383,77 @@ class TestModuleFunctionsLeaks(TestMemoryLeak):
def test_swap_memory(self):
self.execute(psutil.swap_memory)
- @unittest.skipIf(POSIX and SKIP_PYTHON_IMPL,
- "worthless on POSIX (pure python)")
def test_pid_exists(self):
- self.execute(lambda: psutil.pid_exists(os.getpid()))
+ times = FEW_TIMES if POSIX else self.times
+ self.execute(lambda: psutil.pid_exists(os.getpid()), times=times)
# --- disk
- @unittest.skipIf(POSIX and SKIP_PYTHON_IMPL,
- "worthless on POSIX (pure python)")
def test_disk_usage(self):
- self.execute(lambda: psutil.disk_usage('.'))
+ times = FEW_TIMES if POSIX else self.times
+ self.execute(lambda: psutil.disk_usage('.'), times=times)
def test_disk_partitions(self):
self.execute(psutil.disk_partitions)
@unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'),
'/proc/diskstats not available on this Linux version')
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_disk_io_counters(self):
self.execute(lambda: psutil.disk_io_counters(nowrap=False))
# --- proc
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_pids(self):
self.execute(psutil.pids)
# --- net
- # XXX
- @unittest.skipIf(TRAVIS and MACOS, "false positive on TRAVIS + MACOS")
- @unittest.skipIf(CIRRUS and FREEBSD, "false positive on CIRRUS + FREEBSD")
- @skip_if_linux()
+ @fewtimes_if_linux()
@unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported')
def test_net_io_counters(self):
- if WINDOWS:
- # GetAdaptersAddresses() increases the handle count on first
- # call (only).
- psutil.net_io_counters()
self.execute(lambda: psutil.net_io_counters(nowrap=False))
- @skip_if_linux()
+ @fewtimes_if_linux()
@unittest.skipIf(MACOS and os.getuid() != 0, "need root access")
def test_net_connections(self):
# always opens and handle on Windows() (once)
psutil.net_connections(kind='all')
with create_sockets():
- self.execute(lambda: psutil.net_connections(kind='all'), times=100)
+ self.execute(lambda: psutil.net_connections(kind='all'))
def test_net_if_addrs(self):
- if WINDOWS:
- # GetAdaptersAddresses() increases the handle count on first
- # call (only).
- psutil.net_if_addrs()
# Note: verified that on Windows this was a false positive.
- self.execute(psutil.net_if_addrs,
- tolerance=80 * 1024 if WINDOWS else 4096)
+ tolerance = 80 * 1024 if WINDOWS else self.tolerance
+ self.execute(psutil.net_if_addrs, tolerance=tolerance)
- @unittest.skipIf(TRAVIS, "EPERM on travis")
def test_net_if_stats(self):
- if WINDOWS:
- # GetAdaptersAddresses() increases the handle count on first
- # call (only).
- psutil.net_if_stats()
self.execute(psutil.net_if_stats)
# --- sensors
- @skip_if_linux()
+ @fewtimes_if_linux()
@unittest.skipIf(not HAS_SENSORS_BATTERY, "not supported")
def test_sensors_battery(self):
self.execute(psutil.sensors_battery)
- @skip_if_linux()
+ @fewtimes_if_linux()
@unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported")
def test_sensors_temperatures(self):
self.execute(psutil.sensors_temperatures)
- @skip_if_linux()
+ @fewtimes_if_linux()
@unittest.skipIf(not HAS_SENSORS_FANS, "not supported")
def test_sensors_fans(self):
self.execute(psutil.sensors_fans)
# --- others
- @skip_if_linux()
+ @fewtimes_if_linux()
def test_boot_time(self):
self.execute(psutil.boot_time)
- @unittest.skipIf(WINDOWS, "XXX produces a false positive on Windows")
def test_users(self):
self.execute(psutil.users)
diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py
index 300360cd..81fa8f39 100755
--- a/psutil/tests/test_misc.py
+++ b/psutil/tests/test_misc.py
@@ -17,6 +17,7 @@ import os
import pickle
import socket
import stat
+import sys
from psutil import LINUX
from psutil import POSIX
@@ -28,7 +29,6 @@ from psutil._common import wrap_numbers
from psutil._compat import PY3
from psutil.tests import APPVEYOR
from psutil.tests import CI_TESTING
-from psutil.tests import DEVNULL
from psutil.tests import HAS_BATTERY
from psutil.tests import HAS_MEMORY_MAPS
from psutil.tests import HAS_NET_IO_COUNTERS
@@ -43,12 +43,14 @@ from psutil.tests import reload_module
from psutil.tests import ROOT_DIR
from psutil.tests import SCRIPTS_DIR
from psutil.tests import sh
-from psutil.tests import TRAVIS
from psutil.tests import unittest
import psutil
import psutil.tests
+PYTHON_39 = sys.version_info[:2] == (3, 9)
+
+
# ===================================================================
# --- Misc / generic tests.
# ===================================================================
@@ -61,7 +63,8 @@ class TestMisc(PsutilTestCase):
r = func(p)
self.assertIn("psutil.Process", r)
self.assertIn("pid=%s" % p.pid, r)
- self.assertIn("name='%s'" % p.name(), r)
+ self.assertIn("name='%s'" % str(p.name()),
+ r.replace("name=u'", "name='"))
self.assertIn("status=", r)
self.assertNotIn("exitcode=", r)
p.terminate()
@@ -323,7 +326,7 @@ class TestMisc(PsutilTestCase):
side_effect=OSError(errno.EACCES, "foo")):
self.assertRaises(OSError, isfile_strict, this_file)
with mock.patch('psutil._common.os.stat',
- side_effect=OSError(errno.EINVAL, "foo")):
+ side_effect=OSError(errno.ENOENT, "foo")):
assert not isfile_strict(this_file)
with mock.patch('psutil._common.stat.S_ISREG', return_value=False):
assert not isfile_strict(this_file)
@@ -353,7 +356,7 @@ class TestMisc(PsutilTestCase):
def test_setup_script(self):
setup_py = os.path.join(ROOT_DIR, 'setup.py')
- if TRAVIS and not os.path.exists(setup_py):
+ if CI_TESTING and not os.path.exists(setup_py):
return self.skipTest("can't find setup.py")
module = import_module_by_path(setup_py)
self.assertRaises(SystemExit, module.setup)
@@ -707,8 +710,6 @@ class TestScripts(PsutilTestCase):
def test_netstat(self):
self.assert_stdout('netstat.py')
- # permission denied on travis
- @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
def test_ifconfig(self):
self.assert_stdout('ifconfig.py')
@@ -719,7 +720,7 @@ class TestScripts(PsutilTestCase):
def test_procsmem(self):
if 'uss' not in psutil.Process().memory_full_info()._fields:
raise self.skipTest("not supported")
- self.assert_stdout('procsmem.py', stderr=DEVNULL)
+ self.assert_stdout('procsmem.py')
def test_killall(self):
self.assert_syntax('killall.py')
@@ -745,14 +746,12 @@ class TestScripts(PsutilTestCase):
self.assert_syntax('cpu_distribution.py')
@unittest.skipIf(not HAS_SENSORS_TEMPERATURES, "not supported")
- @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
def test_temperatures(self):
if not psutil.sensors_temperatures():
self.skipTest("no temperatures")
self.assert_stdout('temperatures.py')
@unittest.skipIf(not HAS_SENSORS_FANS, "not supported")
- @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
def test_fans(self):
if not psutil.sensors_fans():
self.skipTest("no fans")
@@ -763,6 +762,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_osx.py b/psutil/tests/test_osx.py
index 1d6e1dc9..b7a0b088 100755
--- a/psutil/tests/test_osx.py
+++ b/psutil/tests/test_osx.py
@@ -4,28 +4,24 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-"""MACOS specific tests."""
+"""macOS specific tests."""
-import os
import re
import time
import psutil
from psutil import MACOS
-from psutil.tests import spawn_zombie
-from psutil.tests import spawn_testproc
from psutil.tests import HAS_BATTERY
from psutil.tests import PsutilTestCase
from psutil.tests import retry_on_failure
from psutil.tests import sh
-from psutil.tests import SYSMEM_TOLERANCE
+from psutil.tests import spawn_testproc
from psutil.tests import terminate
+from psutil.tests import TOLERANCE_DISK_USAGE
+from psutil.tests import TOLERANCE_SYS_MEM
from psutil.tests import unittest
-PAGESIZE = os.sysconf("SC_PAGE_SIZE") if MACOS else None
-
-
def sysctl(cmdline):
"""Expects a sysctl command with an argument and parse the result
returning only the value of interest.
@@ -40,13 +36,15 @@ def sysctl(cmdline):
def vm_stat(field):
"""Wrapper around 'vm_stat' cmdline utility."""
+ from psutil._psutil_posix import getpagesize
+
out = sh('vm_stat')
for line in out.split('\n'):
if field in line:
break
else:
raise ValueError("line not found")
- return int(re.search(r'\d+', line).group(0)) * PAGESIZE
+ return int(re.search(r'\d+', line).group(0)) * getpagesize()
# http://code.activestate.com/recipes/578019/
@@ -101,70 +99,12 @@ class TestProcess(PsutilTestCase):
time.strftime("%Y", time.localtime(start_psutil)))
-# TODO: probably needs removal (duplicate)
-@unittest.skipIf(not MACOS, "MACOS only")
-class TestZombieProcessAPIs(PsutilTestCase):
-
- @classmethod
- def setUpClass(cls):
- cls.parent, cls.zombie = spawn_zombie()
-
- @classmethod
- def tearDownClass(cls):
- terminate(cls.parent)
- terminate(cls.zombie)
-
- def test_pidtask_info(self):
- self.assertEqual(self.zombie.status(), psutil.STATUS_ZOMBIE)
- self.zombie.ppid()
- self.zombie.uids()
- self.zombie.gids()
- self.zombie.terminal()
- self.zombie.create_time()
-
- def test_exe(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.exe)
-
- def test_cmdline(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.cmdline)
-
- def test_environ(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.environ)
-
- def test_cwd(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.cwd)
-
- def test_memory_full_info(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.memory_full_info)
-
- def test_cpu_times(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.cpu_times)
-
- def test_num_ctx_switches(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.num_ctx_switches)
-
- def test_num_threads(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.num_threads)
-
- def test_open_files(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.open_files)
-
- def test_connections(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.connections)
-
- def test_num_fds(self):
- self.assertRaises(psutil.ZombieProcess, self.zombie.num_fds)
-
- def test_threads(self):
- self.assertRaises((psutil.ZombieProcess, psutil.AccessDenied),
- self.zombie.threads)
-
-
@unittest.skipIf(not MACOS, "MACOS only")
class TestSystemAPIs(PsutilTestCase):
# --- disk
+ @retry_on_failure()
def test_disks(self):
# test psutil.disk_usage() and psutil.disk_partitions()
# against "df -a"
@@ -186,11 +126,10 @@ class TestSystemAPIs(PsutilTestCase):
dev, total, used, free = df(part.mountpoint)
self.assertEqual(part.device, dev)
self.assertEqual(usage.total, total)
- # 10 MB tollerance
- if abs(usage.free - free) > 10 * 1024 * 1024:
- self.fail("psutil=%s, df=%s" % usage.free, free)
- if abs(usage.used - used) > 10 * 1024 * 1024:
- self.fail("psutil=%s, df=%s" % usage.used, used)
+ self.assertAlmostEqual(usage.free, free,
+ delta=TOLERANCE_DISK_USAGE)
+ self.assertAlmostEqual(usage.used, used,
+ delta=TOLERANCE_DISK_USAGE)
# --- cpu
@@ -198,7 +137,7 @@ class TestSystemAPIs(PsutilTestCase):
num = sysctl("sysctl hw.logicalcpu")
self.assertEqual(num, psutil.cpu_count(logical=True))
- def test_cpu_count_physical(self):
+ def test_cpu_count_cores(self):
num = sysctl("sysctl hw.physicalcpu")
self.assertEqual(num, psutil.cpu_count(logical=False))
@@ -221,25 +160,25 @@ class TestSystemAPIs(PsutilTestCase):
def test_vmem_free(self):
vmstat_val = vm_stat("free")
psutil_val = psutil.virtual_memory().free
- self.assertAlmostEqual(psutil_val, vmstat_val, delta=SYSMEM_TOLERANCE)
+ self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_vmem_active(self):
vmstat_val = vm_stat("active")
psutil_val = psutil.virtual_memory().active
- self.assertAlmostEqual(psutil_val, vmstat_val, delta=SYSMEM_TOLERANCE)
+ self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_vmem_inactive(self):
vmstat_val = vm_stat("inactive")
psutil_val = psutil.virtual_memory().inactive
- self.assertAlmostEqual(psutil_val, vmstat_val, delta=SYSMEM_TOLERANCE)
+ self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM)
@retry_on_failure()
def test_vmem_wired(self):
vmstat_val = vm_stat("wired")
psutil_val = psutil.virtual_memory().wired
- self.assertAlmostEqual(psutil_val, vmstat_val, delta=SYSMEM_TOLERANCE)
+ self.assertAlmostEqual(psutil_val, vmstat_val, delta=TOLERANCE_SYS_MEM)
# --- swap mem
diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py
index e2d18ccb..acb6aa20 100755
--- a/psutil/tests/test_posix.py
+++ b/psutil/tests/test_posix.py
@@ -32,10 +32,15 @@ from psutil.tests import retry_on_failure
from psutil.tests import sh
from psutil.tests import skip_on_access_denied
from psutil.tests import terminate
-from psutil.tests import TRAVIS
from psutil.tests import unittest
from psutil.tests import which
+if POSIX:
+ import mmap
+ import resource
+
+ from psutil._psutil_posix import getpagesize
+
def ps(fmt, pid=None):
"""
@@ -307,7 +312,6 @@ class TestSystemAPIs(PsutilTestCase):
# for some reason ifconfig -a does not report all interfaces
# returned by psutil
@unittest.skipIf(SUNOS, "unreliable on SUNOS")
- @unittest.skipIf(TRAVIS, "unreliable on TRAVIS")
@unittest.skipIf(not which('ifconfig'), "no ifconfig cmd")
@unittest.skipIf(not HAS_NET_IO_COUNTERS, "not supported")
def test_nic_names(self):
@@ -371,6 +375,7 @@ class TestSystemAPIs(PsutilTestCase):
# AIX can return '-' in df output instead of numbers, e.g. for /proc
@unittest.skipIf(AIX, "unreliable on AIX")
+ @retry_on_failure()
def test_disk_usage(self):
def df(device):
out = sh("df -k %s" % device).strip()
@@ -405,6 +410,16 @@ class TestSystemAPIs(PsutilTestCase):
self.assertAlmostEqual(usage.percent, percent, delta=1)
+@unittest.skipIf(not POSIX, "POSIX only")
+class TestMisc(PsutilTestCase):
+
+ def test_getpagesize(self):
+ pagesize = getpagesize()
+ self.assertGreater(pagesize, 0)
+ self.assertEqual(pagesize, resource.getpagesize())
+ self.assertEqual(pagesize, mmap.PAGESIZE)
+
+
if __name__ == '__main__':
from psutil.tests.runner import run_from_name
run_from_name(__file__)
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index 07a00e81..dfe88547 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -13,6 +13,7 @@ import itertools
import os
import signal
import socket
+import stat
import subprocess
import sys
import textwrap
@@ -32,15 +33,17 @@ from psutil import POSIX
from psutil import SUNOS
from psutil import WINDOWS
from psutil._common import open_text
+from psutil._compat import FileNotFoundError
from psutil._compat import long
from psutil._compat import PY3
from psutil._compat import super
from psutil.tests import APPVEYOR
from psutil.tests import call_until
-from psutil.tests import CIRRUS
+from psutil.tests import CI_TESTING
from psutil.tests import copyload_shared_lib
from psutil.tests import create_exe
-from psutil.tests import enum
+from psutil.tests import GITHUB_ACTIONS
+from psutil.tests import GLOBAL_TIMEOUT
from psutil.tests import HAS_CPU_AFFINITY
from psutil.tests import HAS_ENVIRON
from psutil.tests import HAS_IONICE
@@ -60,7 +63,6 @@ from psutil.tests import sh
from psutil.tests import skip_on_access_denied
from psutil.tests import skip_on_not_implemented
from psutil.tests import ThreadTask
-from psutil.tests import TRAVIS
from psutil.tests import unittest
from psutil.tests import wait_for_pid
@@ -209,7 +211,7 @@ class TestProcess(PsutilTestCase):
p = self.spawn_psproc()
self.assertRaises(psutil.TimeoutExpired, p.wait, 0)
p.kill()
- stop_at = time.time() + 2
+ stop_at = time.time() + GLOBAL_TIMEOUT
while time.time() < stop_at:
try:
code = p.wait(0)
@@ -291,14 +293,11 @@ class TestProcess(PsutilTestCase):
time.strftime("%Y %m %d %H:%M:%S", time.localtime(p.create_time()))
@unittest.skipIf(not POSIX, 'POSIX only')
- @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)
@@ -347,11 +346,13 @@ class TestProcess(PsutilTestCase):
@unittest.skipIf(not LINUX, "linux only")
def test_ionice_linux(self):
p = psutil.Process()
- self.assertEqual(p.ionice()[0], psutil.IOPRIO_CLASS_NONE)
+ if not CI_TESTING:
+ self.assertEqual(p.ionice()[0], psutil.IOPRIO_CLASS_NONE)
self.assertEqual(psutil.IOPRIO_CLASS_NONE, 0)
self.assertEqual(psutil.IOPRIO_CLASS_RT, 1) # high
self.assertEqual(psutil.IOPRIO_CLASS_BE, 2) # normal
self.assertEqual(psutil.IOPRIO_CLASS_IDLE, 3) # low
+ init = p.ionice()
try:
# low
p.ionice(psutil.IOPRIO_CLASS_IDLE)
@@ -365,16 +366,10 @@ class TestProcess(PsutilTestCase):
self.assertEqual(tuple(p.ionice()), (psutil.IOPRIO_CLASS_BE, 7))
with self.assertRaises(ValueError):
p.ionice(psutil.IOPRIO_CLASS_BE, value=8)
- # high
- if os.getuid() == 0: # root
- p.ionice(psutil.IOPRIO_CLASS_RT)
- self.assertEqual(tuple(p.ionice()),
- (psutil.IOPRIO_CLASS_RT, 0))
+ try:
p.ionice(psutil.IOPRIO_CLASS_RT, value=7)
- self.assertEqual(tuple(p.ionice()),
- (psutil.IOPRIO_CLASS_RT, 7))
- with self.assertRaises(ValueError):
- p.ionice(psutil.IOPRIO_CLASS_IDLE, value=8)
+ except psutil.AccessDenied:
+ pass
# errs
self.assertRaisesRegex(
ValueError, "ioclass accepts no value",
@@ -386,13 +381,18 @@ class TestProcess(PsutilTestCase):
ValueError, "'ioclass' argument must be specified",
p.ionice, value=1)
finally:
- p.ionice(psutil.IOPRIO_CLASS_BE)
+ ioclass, value = init
+ if ioclass == psutil.IOPRIO_CLASS_NONE:
+ value = 0
+ p.ionice(ioclass, value)
@unittest.skipIf(not HAS_IONICE, "not supported")
@unittest.skipIf(not WINDOWS, 'not supported on this win version')
def test_ionice_win(self):
p = psutil.Process()
- self.assertEqual(p.ionice(), psutil.IOPRIO_NORMAL)
+ if not CI_TESTING:
+ self.assertEqual(p.ionice(), psutil.IOPRIO_NORMAL)
+ init = p.ionice()
try:
# base
p.ionice(psutil.IOPRIO_VERYLOW)
@@ -413,8 +413,7 @@ class TestProcess(PsutilTestCase):
ValueError, "is not a valid priority",
p.ionice, psutil.IOPRIO_HIGH + 1)
finally:
- p.ionice(psutil.IOPRIO_NORMAL)
- self.assertEqual(p.ionice(), psutil.IOPRIO_NORMAL)
+ p.ionice(init)
@unittest.skipIf(not HAS_RLIMIT, "not supported")
def test_rlimit_get(self):
@@ -446,8 +445,9 @@ class TestProcess(PsutilTestCase):
self.assertEqual(p.rlimit(psutil.RLIMIT_NOFILE), (5, 5))
# If pid is 0 prlimit() applies to the calling process and
# we don't want that.
- with self.assertRaises(ValueError):
- psutil._psplatform.Process(0).rlimit(0)
+ if LINUX:
+ with self.assertRaisesRegex(ValueError, "can't use prlimit"):
+ psutil._psplatform.Process(0).rlimit(0)
with self.assertRaises(ValueError):
p.rlimit(psutil.RLIMIT_NOFILE, (5, 5, 5))
@@ -559,6 +559,7 @@ class TestProcess(PsutilTestCase):
p.cpu_times().system,
sum([x.system_time for x in p.threads()]), delta=0.1)
+ @retry_on_failure()
def test_memory_info(self):
p = psutil.Process()
@@ -634,7 +635,12 @@ class TestProcess(PsutilTestCase):
# 64 bit dlls: they are visible via explorer but cannot
# be accessed via os.stat() (wtf?).
if '64' not in os.path.basename(nt.path):
- assert os.path.exists(nt.path), nt.path
+ try:
+ st = os.stat(nt.path)
+ except FileNotFoundError:
+ pass
+ else:
+ assert stat.S_ISREG(st.st_mode), nt.path
for nt in ext_maps:
for fname in nt._fields:
value = getattr(nt, fname)
@@ -704,18 +710,21 @@ class TestProcess(PsutilTestCase):
def test_cmdline(self):
cmdline = [PYTHON_EXE, "-c", "import time; time.sleep(60)"]
p = self.spawn_psproc(cmdline)
- try:
+ # XXX - most of the times the underlying sysctl() call on Net
+ # and Open BSD returns a truncated string.
+ # Also /proc/pid/cmdline behaves the same so it looks
+ # like this is a kernel bug.
+ # XXX - AIX truncates long arguments in /proc/pid/cmdline
+ if NETBSD or OPENBSD or AIX:
+ self.assertEqual(p.cmdline()[0], PYTHON_EXE)
+ else:
+ if MACOS and CI_TESTING:
+ pyexe = p.cmdline()[0]
+ if pyexe != PYTHON_EXE:
+ self.assertEqual(' '.join(p.cmdline()[1:]),
+ ' '.join(cmdline[1:]))
+ return
self.assertEqual(' '.join(p.cmdline()), ' '.join(cmdline))
- except AssertionError:
- # XXX - most of the times the underlying sysctl() call on Net
- # and Open BSD returns a truncated string.
- # Also /proc/pid/cmdline behaves the same so it looks
- # like this is a kernel bug.
- # XXX - AIX truncates long arguments in /proc/pid/cmdline
- if NETBSD or OPENBSD or AIX:
- self.assertEqual(p.cmdline()[0], PYTHON_EXE)
- else:
- raise
@unittest.skipIf(PYPY, "broken on PYPY")
def test_long_cmdline(self):
@@ -752,9 +761,6 @@ class TestProcess(PsutilTestCase):
"import time; [time.sleep(0.01) for x in range(3000)];"
"arg1", "arg2", "", "arg3", ""]
p = self.spawn_psproc(cmdline)
- # ...in order to try to prevent occasional failures on travis
- if TRAVIS:
- wait_for_pid(p.pid)
self.assertEqual(p.cmdline(), cmdline)
self.assertEqual(p.name(), os.path.basename(funky_path))
self.assertEqual(os.path.normcase(p.exe()),
@@ -791,43 +797,46 @@ class TestProcess(PsutilTestCase):
def test_nice(self):
p = psutil.Process()
self.assertRaises(TypeError, p.nice, "str")
- if WINDOWS:
- try:
- init = p.nice()
- if sys.version_info > (3, 4):
- self.assertIsInstance(init, enum.IntEnum)
- else:
- self.assertIsInstance(init, int)
- self.assertEqual(init, psutil.NORMAL_PRIORITY_CLASS)
- p.nice(psutil.HIGH_PRIORITY_CLASS)
- self.assertEqual(p.nice(), psutil.HIGH_PRIORITY_CLASS)
- p.nice(psutil.NORMAL_PRIORITY_CLASS)
- self.assertEqual(p.nice(), psutil.NORMAL_PRIORITY_CLASS)
- finally:
- p.nice(psutil.NORMAL_PRIORITY_CLASS)
- else:
- first_nice = p.nice()
- try:
- if hasattr(os, "getpriority"):
- self.assertEqual(
- os.getpriority(os.PRIO_PROCESS, os.getpid()), p.nice())
- p.nice(1)
- self.assertEqual(p.nice(), 1)
- if hasattr(os, "getpriority"):
- self.assertEqual(
- os.getpriority(os.PRIO_PROCESS, os.getpid()), p.nice())
- # XXX - going back to previous nice value raises
- # AccessDenied on MACOS
- if not MACOS:
- p.nice(0)
- self.assertEqual(p.nice(), 0)
- except psutil.AccessDenied:
- pass
- finally:
+ init = p.nice()
+ try:
+ if WINDOWS:
+ for prio in [psutil.NORMAL_PRIORITY_CLASS,
+ psutil.IDLE_PRIORITY_CLASS,
+ psutil.BELOW_NORMAL_PRIORITY_CLASS,
+ psutil.REALTIME_PRIORITY_CLASS,
+ psutil.HIGH_PRIORITY_CLASS,
+ psutil.ABOVE_NORMAL_PRIORITY_CLASS]:
+ with self.subTest(prio=prio):
+ try:
+ p.nice(prio)
+ except psutil.AccessDenied:
+ pass
+ else:
+ self.assertEqual(p.nice(), prio)
+ else:
try:
- p.nice(first_nice)
+ if hasattr(os, "getpriority"):
+ self.assertEqual(
+ os.getpriority(os.PRIO_PROCESS, os.getpid()),
+ p.nice())
+ p.nice(1)
+ self.assertEqual(p.nice(), 1)
+ if hasattr(os, "getpriority"):
+ self.assertEqual(
+ os.getpriority(os.PRIO_PROCESS, os.getpid()),
+ p.nice())
+ # XXX - going back to previous nice value raises
+ # AccessDenied on MACOS
+ if not MACOS:
+ p.nice(0)
+ self.assertEqual(p.nice(), 0)
except psutil.AccessDenied:
pass
+ finally:
+ try:
+ p.nice(init)
+ except psutil.AccessDenied:
+ pass
def test_status(self):
p = psutil.Process()
@@ -866,9 +875,7 @@ class TestProcess(PsutilTestCase):
self.assertEqual(len(initial), len(set(initial)))
all_cpus = list(range(len(psutil.cpu_percent(percpu=True))))
- # Work around travis failure:
- # https://travis-ci.org/giampaolo/psutil/builds/284173194
- for n in all_cpus if not TRAVIS else initial:
+ for n in all_cpus:
p.cpu_affinity([n])
self.assertEqual(p.cpu_affinity(), [n])
if hasattr(os, "sched_getaffinity"):
@@ -893,9 +900,8 @@ class TestProcess(PsutilTestCase):
self.assertRaises(TypeError, p.cpu_affinity, 1)
p.cpu_affinity(initial)
# it should work with all iterables, not only lists
- if not TRAVIS:
- p.cpu_affinity(set(all_cpus))
- p.cpu_affinity(tuple(all_cpus))
+ p.cpu_affinity(set(all_cpus))
+ p.cpu_affinity(tuple(all_cpus))
@unittest.skipIf(not HAS_CPU_AFFINITY, 'not supported')
def test_cpu_affinity_errs(self):
@@ -917,14 +923,14 @@ class TestProcess(PsutilTestCase):
if len(initial) > 12:
initial = initial[:12] # ...otherwise it will take forever
combos = []
- for l in range(0, len(initial) + 1):
- for subset in itertools.combinations(initial, l):
+ for i in range(0, len(initial) + 1):
+ for subset in itertools.combinations(initial, i):
if subset:
combos.append(list(subset))
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")
@@ -1096,6 +1102,8 @@ class TestProcess(PsutilTestCase):
pass
# this is the one, now let's make sure there are no duplicates
pid = sorted(table.items(), key=lambda x: x[1])[-1][0]
+ if LINUX and pid == 0:
+ raise self.skipTest("PID 0")
p = psutil.Process(pid)
try:
c = p.children(recursive=True)
@@ -1263,7 +1271,7 @@ class TestProcess(PsutilTestCase):
assert_raises_nsp(fun, name)
# NtQuerySystemInformation succeeds even if process is gone.
- if WINDOWS:
+ if WINDOWS and not GITHUB_ACTIONS:
normcase = os.path.normcase
self.assertEqual(normcase(p.exe()), normcase(PYTHON_EXE))
@@ -1299,14 +1307,10 @@ class TestProcess(PsutilTestCase):
succeed_or_zombie_p_exc(fun)
assert psutil.pid_exists(zproc.pid)
- if not TRAVIS and MACOS:
- # For some reason this started failing all of the sudden.
- # Maybe they upgraded MACOS version?
- # https://travis-ci.org/giampaolo/psutil/jobs/310896404
- self.assertIn(zproc.pid, psutil.pids())
- self.assertIn(zproc.pid, [x.pid for x in psutil.process_iter()])
- psutil._pmap = {}
- self.assertIn(zproc.pid, [x.pid for x in psutil.process_iter()])
+ self.assertIn(zproc.pid, psutil.pids())
+ self.assertIn(zproc.pid, [x.pid for x in psutil.process_iter()])
+ psutil._pmap = {}
+ self.assertIn(zproc.pid, [x.pid for x in psutil.process_iter()])
@unittest.skipIf(not POSIX, 'POSIX only')
def test_zombie_process_is_running_w_exc(self):
@@ -1387,7 +1391,8 @@ class TestProcess(PsutilTestCase):
p = psutil.Process()
d1 = clean_dict(p.environ())
d2 = clean_dict(os.environ.copy())
- self.assertEqual(d1, d2)
+ if not OSX and GITHUB_ACTIONS:
+ self.assertEqual(d1, d2)
@unittest.skipIf(not HAS_ENVIRON, "not supported")
@unittest.skipIf(not POSIX, "POSIX only")
@@ -1415,7 +1420,16 @@ class TestProcess(PsutilTestCase):
assert p.is_running()
# Wait for process to exec or exit.
self.assertEqual(sproc.stderr.read(), b"")
- self.assertEqual(p.environ(), {"A": "1", "C": "3"})
+ if MACOS and CI_TESTING:
+ try:
+ env = p.environ()
+ except psutil.AccessDenied:
+ # XXX: fails sometimes with:
+ # PermissionError from 'sysctl(KERN_PROCARGS2) -> EIO'
+ return
+ else:
+ env = p.environ()
+ self.assertEqual(env, {"A": "1", "C": "3"})
sproc.communicate()
self.assertEqual(sproc.returncode, 0)
@@ -1500,9 +1514,9 @@ class TestPopen(PsutilTestCase):
self.assertRaises(AttributeError, getattr, proc, 'foo')
proc.terminate()
if POSIX:
- self.assertEqual(proc.wait(), -signal.SIGTERM)
+ self.assertEqual(proc.wait(5), -signal.SIGTERM)
else:
- self.assertEqual(proc.wait(), signal.SIGTERM)
+ self.assertEqual(proc.wait(5), signal.SIGTERM)
def test_ctx_manager(self):
with psutil.Popen([PYTHON_EXE, "-V"],
diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py
index 09f5324f..4e3ac3e4 100644..100755
--- a/psutil/tests/test_system.py
+++ b/psutil/tests/test_system.py
@@ -35,6 +35,7 @@ from psutil.tests import check_net_address
from psutil.tests import CI_TESTING
from psutil.tests import DEVNULL
from psutil.tests import enum
+from psutil.tests import GLOBAL_TIMEOUT
from psutil.tests import HAS_BATTERY
from psutil.tests import HAS_CPU_FREQ
from psutil.tests import HAS_GETLOADAVG
@@ -42,11 +43,12 @@ from psutil.tests import HAS_NET_IO_COUNTERS
from psutil.tests import HAS_SENSORS_BATTERY
from psutil.tests import HAS_SENSORS_FANS
from psutil.tests import HAS_SENSORS_TEMPERATURES
+from psutil.tests import IS_64BIT
from psutil.tests import mock
from psutil.tests import PsutilTestCase
from psutil.tests import PYPY
from psutil.tests import retry_on_failure
-from psutil.tests import TRAVIS
+from psutil.tests import GITHUB_ACTIONS
from psutil.tests import UNICODE_SUFFIX
from psutil.tests import unittest
@@ -216,14 +218,6 @@ class TestMiscAPIs(PsutilTestCase):
else:
psutil.Process(user.pid)
- @unittest.skipIf(not POSIX, 'POSIX only')
- def test_PAGESIZE(self):
- # pagesize is used internally to perform different calculations
- # and it's determined by using SC_PAGE_SIZE; make sure
- # getpagesize() returns the same value.
- import resource
- self.assertEqual(os.sysconf("SC_PAGE_SIZE"), resource.getpagesize())
-
def test_test(self):
# test for psutil.test() function
stdout = sys.stdout
@@ -322,16 +316,16 @@ class TestCpuAPIs(PsutilTestCase):
if "physical id" not in cpuinfo_data:
raise unittest.SkipTest("cpuinfo doesn't include physical id")
- def test_cpu_count_physical(self):
+ def test_cpu_count_cores(self):
logical = psutil.cpu_count()
- physical = psutil.cpu_count(logical=False)
- if physical is None:
- raise self.skipTest("physical cpu_count() is None")
+ cores = psutil.cpu_count(logical=False)
+ if cores is None:
+ raise self.skipTest("cpu_count_cores() is None")
if WINDOWS and sys.getwindowsversion()[:2] <= (6, 1): # <= Vista
- self.assertIsNone(physical)
+ self.assertIsNone(cores)
else:
- self.assertGreaterEqual(physical, 1)
- self.assertGreaterEqual(logical, physical)
+ self.assertGreaterEqual(cores, 1)
+ self.assertGreaterEqual(logical, cores)
def test_cpu_count_none(self):
# https://github.com/giampaolo/psutil/issues/1085
@@ -340,7 +334,7 @@ class TestCpuAPIs(PsutilTestCase):
return_value=val) as m:
self.assertIsNone(psutil.cpu_count())
assert m.called
- with mock.patch('psutil._psplatform.cpu_count_physical',
+ with mock.patch('psutil._psplatform.cpu_count_cores',
return_value=val) as m:
self.assertIsNone(psutil.cpu_count(logical=False))
assert m.called
@@ -377,7 +371,7 @@ class TestCpuAPIs(PsutilTestCase):
def test_cpu_times_time_increases(self):
# Make sure time increases between calls.
t1 = sum(psutil.cpu_times())
- stop_at = time.time() + 1
+ stop_at = time.time() + GLOBAL_TIMEOUT
while time.time() < stop_at:
t2 = sum(psutil.cpu_times())
if t2 > t1:
@@ -421,7 +415,7 @@ class TestCpuAPIs(PsutilTestCase):
# Simulate some work load then make sure time have increased
# between calls.
tot1 = psutil.cpu_times(percpu=True)
- giveup_at = time.time() + 1
+ giveup_at = time.time() + GLOBAL_TIMEOUT
while True:
if time.time() >= giveup_at:
return self.fail("timeout")
@@ -480,6 +474,8 @@ class TestCpuAPIs(PsutilTestCase):
self._test_cpu_percent(percent, last, new)
self._test_cpu_percent(sum(new), last, new)
last = new
+ with self.assertRaises(ValueError):
+ psutil.cpu_times_percent(interval=-1)
def test_per_cpu_times_percent(self):
last = psutil.cpu_times_percent(interval=0.001, percpu=True)
@@ -528,8 +524,6 @@ class TestCpuAPIs(PsutilTestCase):
self.assertGreaterEqual(value, 0)
ls = psutil.cpu_freq(percpu=True)
- if TRAVIS and not ls:
- raise self.skipTest("skipped on Travis")
if FREEBSD and not ls:
raise self.skipTest("returns empty list on FreeBSD")
@@ -542,8 +536,7 @@ class TestCpuAPIs(PsutilTestCase):
@unittest.skipIf(not HAS_GETLOADAVG, "not supported")
def test_getloadavg(self):
loadavg = psutil.getloadavg()
- assert len(loadavg) == 3
-
+ self.assertEqual(len(loadavg), 3)
for load in loadavg:
self.assertIsInstance(load, float)
self.assertGreaterEqual(load, 0.0)
@@ -551,6 +544,7 @@ class TestCpuAPIs(PsutilTestCase):
class TestDiskAPIs(PsutilTestCase):
+ @unittest.skipIf(PYPY and not IS_64BIT, "unreliable on PYPY32 + 32BIT")
def test_disk_usage(self):
usage = psutil.disk_usage(os.getcwd())
self.assertEqual(usage._fields, ('total', 'used', 'free', 'percent'))
@@ -587,17 +581,23 @@ class TestDiskAPIs(PsutilTestCase):
psutil.disk_usage(b'.')
def test_disk_partitions(self):
+ def check_ntuple(nt):
+ self.assertIsInstance(nt.device, str)
+ self.assertIsInstance(nt.mountpoint, str)
+ self.assertIsInstance(nt.fstype, str)
+ self.assertIsInstance(nt.opts, str)
+ self.assertIsInstance(nt.maxfile, (int, type(None)))
+ self.assertIsInstance(nt.maxpath, (int, type(None)))
+ if nt.maxfile is not None and not GITHUB_ACTIONS:
+ self.assertGreater(nt.maxfile, 0)
+ if nt.maxpath is not None:
+ self.assertGreater(nt.maxpath, 0)
+
# all = False
ls = psutil.disk_partitions(all=False)
- # on travis we get:
- # self.assertEqual(p.cpu_affinity(), [n])
- # AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7,... != [0]
self.assertTrue(ls, msg=ls)
for disk in ls:
- self.assertIsInstance(disk.device, str)
- self.assertIsInstance(disk.mountpoint, str)
- self.assertIsInstance(disk.fstype, str)
- self.assertIsInstance(disk.opts, str)
+ check_ntuple(disk)
if WINDOWS and 'cdrom' in disk.opts:
continue
if not POSIX:
@@ -614,11 +614,12 @@ class TestDiskAPIs(PsutilTestCase):
ls = psutil.disk_partitions(all=True)
self.assertTrue(ls, msg=ls)
for disk in psutil.disk_partitions(all=True):
+ check_ntuple(disk)
if not WINDOWS and disk.mountpoint:
try:
os.stat(disk.mountpoint)
except OSError as err:
- if TRAVIS and MACOS and err.errno == errno.EIO:
+ if GITHUB_ACTIONS and MACOS and err.errno == errno.EIO:
continue
# http://mail.python.org/pipermail/python-dev/
# 2012-June/120787.html
@@ -626,8 +627,8 @@ class TestDiskAPIs(PsutilTestCase):
raise
else:
assert os.path.exists(disk.mountpoint), disk
- self.assertIsInstance(disk.fstype, str)
- self.assertIsInstance(disk.opts, str)
+
+ # ---
def find_mount_point(path):
path = os.path.abspath(path)
@@ -639,7 +640,6 @@ class TestDiskAPIs(PsutilTestCase):
mounts = [x.mountpoint.lower() for x in
psutil.disk_partitions(all=True) if x.mountpoint]
self.assertIn(mount, mounts)
- psutil.disk_usage(mount)
@unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'),
'/proc/diskstats not available on this linux version')
@@ -745,7 +745,7 @@ class TestNetAPIs(PsutilTestCase):
self.assertIsInstance(addr.netmask, (str, type(None)))
self.assertIsInstance(addr.broadcast, (str, type(None)))
self.assertIn(addr.family, families)
- if sys.version_info >= (3, 4):
+ if sys.version_info >= (3, 4) and not PYPY:
self.assertIsInstance(addr.family, enum.IntEnum)
if nic_stats[nic].isup:
# Do not test binding to addresses of interfaces
@@ -801,7 +801,6 @@ class TestNetAPIs(PsutilTestCase):
else:
self.assertEqual(addr.address, '06-3d-29-00-00-00')
- @unittest.skipIf(TRAVIS, "unreliable on TRAVIS") # raises EPERM
def test_net_if_stats(self):
nics = psutil.net_if_stats()
assert nics, nics
diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py
index 73782079..09adbdb1 100644..100755
--- a/psutil/tests/test_testutils.py
+++ b/psutil/tests/test_testutils.py
@@ -12,7 +12,6 @@ Tests for testing utils (psutil.tests namespace).
import collections
import contextlib
import errno
-import io
import os
import socket
import stat
@@ -24,12 +23,11 @@ from psutil import POSIX
from psutil._common import open_binary
from psutil._common import open_text
from psutil._common import supports_ipv6
-from psutil._compat import PY3
-from psutil._compat import redirect_stderr
from psutil.tests import bind_socket
from psutil.tests import bind_unix_socket
from psutil.tests import call_until
from psutil.tests import chdir
+from psutil.tests import CI_TESTING
from psutil.tests import create_sockets
from psutil.tests import get_free_port
from psutil.tests import HAS_CONNECTIONS_UNIX
@@ -357,65 +355,55 @@ class TestMemLeakClass(TestMemoryLeak):
def fun():
cnt['cnt'] += 1
cnt = {'cnt': 0}
- self.execute(fun, times=1, warmup_times=10)
- self.assertEqual(cnt['cnt'], 12)
- self.execute(fun, times=10, warmup_times=10)
- self.assertEqual(cnt['cnt'], 33)
-
- @retry_on_failure()
- def test_warmup_times(self):
- def fun():
- cnt['cnt'] += 1
- cnt = {'cnt': 0}
- self.execute(fun, times=1, warmup_times=10)
- self.assertEqual(cnt['cnt'], 12)
+ self.execute(fun, times=10, warmup_times=15)
+ self.assertEqual(cnt['cnt'], 26)
def test_param_err(self):
self.assertRaises(ValueError, self.execute, lambda: 0, times=0)
self.assertRaises(ValueError, self.execute, lambda: 0, times=-1)
self.assertRaises(ValueError, self.execute, lambda: 0, warmup_times=-1)
self.assertRaises(ValueError, self.execute, lambda: 0, tolerance=-1)
- self.assertRaises(ValueError, self.execute, lambda: 0, retry_for=-1)
+ self.assertRaises(ValueError, self.execute, lambda: 0, retries=-1)
@retry_on_failure()
- def test_leak(self):
- def fun():
- ls.append("x" * 24 * 1024)
+ @unittest.skipIf(CI_TESTING, "skipped on CI")
+ def test_leak_mem(self):
ls = []
- times = 100
- self.assertRaises(AssertionError, self.execute, fun, times=times,
- warmup_times=10, retry_for=None)
- self.assertEqual(len(ls), times + 11)
- @retry_on_failure(retries=20) # 2 secs
- def test_leak_with_retry(self, ls=[]):
- def fun():
+ def fun(ls=ls):
ls.append("x" * 24 * 1024)
- times = 100
- f = io.StringIO() if PY3 else io.BytesIO()
- with redirect_stderr(f):
- self.assertRaises(AssertionError, self.execute, fun, times=times,
- retry_for=0.1)
- self.assertIn("try calling fun for another", f.getvalue())
- self.assertGreater(len(ls), times)
- @retry_on_failure()
+ try:
+ # will consume around 3M in total
+ self.assertRaisesRegex(AssertionError, "extra-mem",
+ self.execute, fun, times=50)
+ finally:
+ del ls
+
+ def test_unclosed_files(self):
+ def fun():
+ f = open(__file__)
+ self.addCleanup(f.close)
+ box.append(f)
+
+ box = []
+ kind = "fd" if POSIX else "handle"
+ self.assertRaisesRegex(AssertionError, "unclosed " + kind,
+ self.execute, fun)
+
def test_tolerance(self):
def fun():
ls.append("x" * 24 * 1024)
ls = []
times = 100
self.execute(fun, times=times, warmup_times=0,
- tolerance=200 * 1024 * 1024, check_fds=False)
- self.assertEqual(len(ls), times)
+ tolerance=200 * 1024 * 1024)
+ self.assertEqual(len(ls), times + 1)
- @retry_on_failure()
def test_execute_w_exc(self):
def fun():
1 / 0
- # XXX: use high tolerance, occasional false positive
- self.execute_w_exc(ZeroDivisionError, fun, times=2000,
- warmup_times=20, tolerance=200 * 1024, retry_for=3)
+ self.execute_w_exc(ZeroDivisionError, fun)
with self.assertRaises(ZeroDivisionError):
self.execute_w_exc(OSError, fun)
@@ -424,17 +412,6 @@ class TestMemLeakClass(TestMemoryLeak):
with self.assertRaises(AssertionError):
self.execute_w_exc(ZeroDivisionError, fun)
- def test_unclosed_fds(self):
- def fun():
- f = open(__file__)
- self.addCleanup(f.close)
- box.append(f)
-
- box = []
- self.assertRaisesRegex(
- AssertionError, r"unclosed fd\(s\) or handle\(s\)",
- self.execute, fun, times=5, warmup_times=5)
-
class TestTestingUtils(PsutilTestCase):
@@ -447,7 +424,6 @@ class TestTestingUtils(PsutilTestCase):
def test_system_namespace(self):
ns = system_namespace()
- ns.test()
fun = [x for x in ns.iter(ns.getters) if x[1] == 'net_if_addrs'][0][0]
self.assertEqual(fun(), psutil.net_if_addrs())
diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py
index e43d5326..9edb8c89 100644..100755
--- a/psutil/tests/test_unicode.py
+++ b/psutil/tests/test_unicode.py
@@ -74,12 +74,12 @@ etc.) and make sure that:
"""
import os
+import shutil
import traceback
import warnings
from contextlib import closing
from psutil import BSD
-from psutil import MACOS
from psutil import OPENBSD
from psutil import POSIX
from psutil import WINDOWS
@@ -89,10 +89,9 @@ from psutil.tests import APPVEYOR
from psutil.tests import ASCII_FS
from psutil.tests import bind_unix_socket
from psutil.tests import chdir
-from psutil.tests import CIRRUS
+from psutil.tests import CI_TESTING
from psutil.tests import copyload_shared_lib
from psutil.tests import create_exe
-from psutil.tests import spawn_testproc
from psutil.tests import get_testfn
from psutil.tests import HAS_CONNECTIONS_UNIX
from psutil.tests import HAS_ENVIRON
@@ -104,9 +103,9 @@ from psutil.tests import safe_mkdir
from psutil.tests import safe_rmpath
from psutil.tests import serialrun
from psutil.tests import skip_on_access_denied
+from psutil.tests import spawn_testproc
from psutil.tests import terminate
from psutil.tests import TESTFN_PREFIX
-from psutil.tests import TRAVIS
from psutil.tests import UNICODE_SUFFIX
from psutil.tests import unittest
import psutil
@@ -124,26 +123,26 @@ if APPVEYOR:
# https://github.com/giampaolo/psutil/blob/
# 68c7a70728a31d8b8b58f4be6c4c0baa2f449eda/psutil/arch/
# windows/process_info.c#L146
- from psutil.tests import safe_rmpath as _rm
+ from psutil.tests import safe_rmpath as rm
try:
- return _rm(path)
+ return rm(path)
except WindowsError:
traceback.print_exc()
-def subprocess_supports_unicode(suffix):
+def try_unicode(suffix):
"""Return True if both the fs and the subprocess module can
deal with a unicode file name.
"""
- if PY3:
- return True
sproc = None
testfn = get_testfn(suffix=suffix)
try:
safe_rmpath(testfn)
create_exe(testfn)
sproc = spawn_testproc(cmd=[testfn])
- except UnicodeEncodeError:
+ shutil.copyfile(testfn, testfn + '-2')
+ safe_rmpath(testfn + '-2')
+ except (UnicodeEncodeError, IOError):
return False
else:
return True
@@ -158,10 +157,23 @@ def subprocess_supports_unicode(suffix):
# ===================================================================
-@serialrun
-class _BaseFSAPIsTests(object):
+class BaseUnicodeTest(PsutilTestCase):
funky_suffix = None
+ def setUp(self):
+ if self.funky_suffix is not None:
+ if not try_unicode(self.funky_suffix):
+ raise self.skipTest("can't handle unicode str")
+
+
+@serialrun
+@unittest.skipIf(ASCII_FS, "ASCII fs")
+@unittest.skipIf(PYPY and not PY3, "too much trouble on PYPY2")
+class TestFSAPIs(BaseUnicodeTest):
+ """Test FS APIs with a funky, valid, UTF8 path name."""
+
+ funky_suffix = UNICODE_SUFFIX
+
@classmethod
def setUpClass(cls):
cls.funky_name = get_testfn(suffix=cls.funky_suffix)
@@ -172,7 +184,12 @@ class _BaseFSAPIsTests(object):
safe_rmpath(cls.funky_name)
def expect_exact_path_match(self):
- raise NotImplementedError("must be implemented in subclass")
+ # Do not expect psutil to correctly handle unicode paths on
+ # Python 2 if os.listdir() is not able either.
+ here = '.' if isinstance(self.funky_name, str) else u('.')
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ return self.funky_name in os.listdir(here)
# ---
@@ -229,8 +246,7 @@ class _BaseFSAPIsTests(object):
@unittest.skipIf(not POSIX, "POSIX only")
def test_proc_connections(self):
- suffix = os.path.basename(self.funky_name)
- name = self.get_testfn(suffix=suffix)
+ name = self.get_testfn(suffix=self.funky_suffix)
try:
sock = bind_unix_socket(name)
except UnicodeEncodeError:
@@ -242,7 +258,7 @@ class _BaseFSAPIsTests(object):
conn = psutil.Process().connections('unix')[0]
self.assertIsInstance(conn.laddr, str)
# AF_UNIX addr not set on OpenBSD
- if not OPENBSD and not CIRRUS: # XXX
+ if not OPENBSD: # XXX
self.assertEqual(conn.laddr, name)
@unittest.skipIf(not POSIX, "POSIX only")
@@ -255,8 +271,7 @@ class _BaseFSAPIsTests(object):
return conn
raise ValueError("connection not found")
- suffix = os.path.basename(self.funky_name)
- name = self.get_testfn(suffix=suffix)
+ name = self.get_testfn(suffix=self.funky_suffix)
try:
sock = bind_unix_socket(name)
except UnicodeEncodeError:
@@ -280,8 +295,7 @@ class _BaseFSAPIsTests(object):
@unittest.skipIf(not HAS_MEMORY_MAPS, "not supported")
@unittest.skipIf(not PY3, "ctypes does not support unicode on PY2")
- @unittest.skipIf(PYPY and WINDOWS,
- "copyload_shared_lib() unsupported on PYPY + WINDOWS")
+ @unittest.skipIf(PYPY, "unstable on PYPY")
def test_memory_maps(self):
# XXX: on Python 2, using ctypes.CDLL with a unicode path
# opens a message box which blocks the test run.
@@ -297,31 +311,8 @@ class _BaseFSAPIsTests(object):
self.assertIsInstance(path, str)
-# https://travis-ci.org/giampaolo/psutil/jobs/440073249
-@unittest.skipIf(PYPY and TRAVIS, "unreliable on PYPY + TRAVIS")
-@unittest.skipIf(MACOS and TRAVIS, "unreliable on TRAVIS") # TODO
-@unittest.skipIf(ASCII_FS, "ASCII fs")
-@unittest.skipIf(not subprocess_supports_unicode(UNICODE_SUFFIX),
- "subprocess can't deal with unicode")
-class TestFSAPIs(_BaseFSAPIsTests, PsutilTestCase):
- """Test FS APIs with a funky, valid, UTF8 path name."""
- funky_suffix = UNICODE_SUFFIX
-
- def expect_exact_path_match(self):
- # Do not expect psutil to correctly handle unicode paths on
- # Python 2 if os.listdir() is not able either.
- here = '.' if isinstance(self.funky_name, str) else u('.')
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- return self.funky_name in os.listdir(here)
-
-
-@unittest.skipIf(PYPY and TRAVIS, "unreliable on PYPY + TRAVIS")
-@unittest.skipIf(MACOS and TRAVIS, "unreliable on TRAVIS") # TODO
-@unittest.skipIf(PYPY, "unreliable on PYPY")
-@unittest.skipIf(not subprocess_supports_unicode(INVALID_UNICODE_SUFFIX),
- "subprocess can't deal with invalid unicode")
-class TestFSAPIsWithInvalidPath(_BaseFSAPIsTests, PsutilTestCase):
+@unittest.skipIf(CI_TESTING, "unreliable on CI")
+class TestFSAPIsWithInvalidPath(TestFSAPIs):
"""Test FS APIs with a funky, invalid path name."""
funky_suffix = INVALID_UNICODE_SUFFIX
@@ -336,8 +327,9 @@ class TestFSAPIsWithInvalidPath(_BaseFSAPIsTests, PsutilTestCase):
# ===================================================================
-class TestNonFSAPIS(PsutilTestCase):
+class TestNonFSAPIS(BaseUnicodeTest):
"""Unicode tests for non fs-related APIs."""
+ funky_suffix = UNICODE_SUFFIX if PY3 else 'è'
@unittest.skipIf(not HAS_ENVIRON, "not supported")
@unittest.skipIf(PYPY and WINDOWS, "segfaults on PYPY + WINDOWS")
@@ -348,15 +340,14 @@ class TestNonFSAPIS(PsutilTestCase):
# we use "è", which is part of the extended ASCII table
# (unicode point <= 255).
env = os.environ.copy()
- funky_str = UNICODE_SUFFIX if PY3 else 'è'
- env['FUNNY_ARG'] = funky_str
+ env['FUNNY_ARG'] = self.funky_suffix
sproc = self.spawn_testproc(env=env)
p = psutil.Process(sproc.pid)
env = p.environ()
for k, v in env.items():
self.assertIsInstance(k, str)
self.assertIsInstance(v, str)
- self.assertEqual(env['FUNNY_ARG'], funky_str)
+ self.assertEqual(env['FUNNY_ARG'], self.funky_suffix)
if __name__ == '__main__':
diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py
index 23ad0584..aeb282c8 100755
--- a/psutil/tests/test_windows.py
+++ b/psutil/tests/test_windows.py
@@ -24,22 +24,25 @@ from psutil import WINDOWS
from psutil._compat import FileNotFoundError
from psutil._compat import super
from psutil.tests import APPVEYOR
-from psutil.tests import spawn_testproc
+from psutil.tests import GITHUB_ACTIONS
from psutil.tests import HAS_BATTERY
+from psutil.tests import IS_64BIT
from psutil.tests import mock
from psutil.tests import PsutilTestCase
from psutil.tests import PY3
from psutil.tests import PYPY
from psutil.tests import retry_on_failure
from psutil.tests import sh
+from psutil.tests import spawn_testproc
from psutil.tests import terminate
+from psutil.tests import TOLERANCE_DISK_USAGE
from psutil.tests import unittest
if WINDOWS and not PYPY:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
- import win32api # requires "pip install pypiwin32"
+ import win32api # requires "pip install pywin32"
import win32con
import win32process
import wmi # requires "pip install wmi" / "make setup-dev-env"
@@ -47,9 +50,6 @@ if WINDOWS and not PYPY:
cext = psutil._psplatform.cext
-# are we a 64 bit process
-IS_64_BIT = sys.maxsize > 2**32
-
def wrap_exceptions(fun):
def wrapper(self, *args, **kwargs):
@@ -65,8 +65,11 @@ def wrap_exceptions(fun):
return wrapper
-@unittest.skipIf(PYPY, "pywin32 not available on PYPY") # skip whole module
-class TestCase(PsutilTestCase):
+@unittest.skipIf(not WINDOWS, "WINDOWS only")
+@unittest.skipIf(PYPY, "pywin32 not available on PYPY")
+# https://github.com/giampaolo/psutil/pull/1762#issuecomment-632892692
+@unittest.skipIf(GITHUB_ACTIONS and not PY3, "pywin32 broken on GITHUB + PY2")
+class WindowsTestCase(PsutilTestCase):
pass
@@ -75,8 +78,7 @@ class TestCase(PsutilTestCase):
# ===================================================================
-@unittest.skipIf(not WINDOWS, "WINDOWS only")
-class TestCpuAPIs(TestCase):
+class TestCpuAPIs(WindowsTestCase):
@unittest.skipIf('NUMBER_OF_PROCESSORS' not in os.environ,
'NUMBER_OF_PROCESSORS env var is not available')
@@ -98,7 +100,7 @@ class TestCpuAPIs(TestCase):
proc = w.Win32_Processor()[0]
self.assertEqual(psutil.cpu_count(), proc.NumberOfLogicalProcessors)
- def test_cpu_count_phys_vs_wmi(self):
+ def test_cpu_count_cores_vs_wmi(self):
w = wmi.WMI()
proc = w.Win32_Processor()[0]
self.assertEqual(psutil.cpu_count(logical=False), proc.NumberOfCores)
@@ -114,8 +116,7 @@ class TestCpuAPIs(TestCase):
self.assertEqual(proc.MaxClockSpeed, psutil.cpu_freq().max)
-@unittest.skipIf(not WINDOWS, "WINDOWS only")
-class TestSystemAPIs(TestCase):
+class TestSystemAPIs(WindowsTestCase):
def test_nic_names(self):
out = sh('ipconfig /all')
@@ -166,6 +167,8 @@ class TestSystemAPIs(TestCase):
break
if 'cdrom' in ps_part.opts:
break
+ if ps_part.mountpoint.startswith('A:'):
+ break # floppy
try:
usage = psutil.disk_usage(ps_part.mountpoint)
except FileNotFoundError:
@@ -182,6 +185,7 @@ class TestSystemAPIs(TestCase):
else:
self.fail("can't find partition %s" % repr(ps_part))
+ @retry_on_failure()
def test_disk_usage(self):
for disk in psutil.disk_partitions():
if 'cdrom' in disk.opts:
@@ -189,9 +193,9 @@ class TestSystemAPIs(TestCase):
sys_value = win32api.GetDiskFreeSpaceEx(disk.mountpoint)
psutil_value = psutil.disk_usage(disk.mountpoint)
self.assertAlmostEqual(sys_value[0], psutil_value.free,
- delta=1024 * 1024)
+ delta=TOLERANCE_DISK_USAGE)
self.assertAlmostEqual(sys_value[1], psutil_value.total,
- delta=1024 * 1024)
+ delta=TOLERANCE_DISK_USAGE)
self.assertEqual(psutil_value.used,
psutil_value.total - psutil_value.free)
@@ -199,7 +203,8 @@ class TestSystemAPIs(TestCase):
sys_value = [
x + '\\' for x in win32api.GetLogicalDriveStrings().split("\\\x00")
if x and not x.startswith('A:')]
- psutil_value = [x.mountpoint for x in psutil.disk_partitions(all=True)]
+ psutil_value = [x.mountpoint for x in psutil.disk_partitions(all=True)
+ if not x.mountpoint.startswith('A:')]
self.assertEqual(sys_value, psutil_value)
def test_net_if_stats(self):
@@ -238,8 +243,7 @@ class TestSystemAPIs(TestCase):
# ===================================================================
-@unittest.skipIf(not WINDOWS, "WINDOWS only")
-class TestSensorsBattery(TestCase):
+class TestSensorsBattery(WindowsTestCase):
def test_has_battery(self):
if win32api.GetPwrCapabilities()['SystemBatteriesPresent']:
@@ -299,8 +303,7 @@ class TestSensorsBattery(TestCase):
# ===================================================================
-@unittest.skipIf(not WINDOWS, "WINDOWS only")
-class TestProcess(PsutilTestCase):
+class TestProcess(WindowsTestCase):
@classmethod
def setUpClass(cls):
@@ -491,8 +494,7 @@ class TestProcess(PsutilTestCase):
self.assertRaises(psutil.NoSuchProcess, proc.exe)
-@unittest.skipIf(not WINDOWS, "WINDOWS only")
-class TestProcessWMI(TestCase):
+class TestProcessWMI(WindowsTestCase):
"""Compare Process API results with WMI."""
@classmethod
@@ -508,6 +510,8 @@ class TestProcessWMI(TestCase):
p = psutil.Process(self.pid)
self.assertEqual(p.name(), w.Caption)
+ # This fail on github because using virtualenv for test environment
+ @unittest.skipIf(GITHUB_ACTIONS, "unreliable path on GITHUB_ACTIONS")
def test_exe(self):
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
@@ -528,15 +532,15 @@ class TestProcessWMI(TestCase):
username = "%s\\%s" % (domain, username)
self.assertEqual(p.username(), username)
+ @retry_on_failure()
def test_memory_rss(self):
- time.sleep(0.1)
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
rss = p.memory_info().rss
self.assertEqual(rss, int(w.WorkingSetSize))
+ @retry_on_failure()
def test_memory_vms(self):
- time.sleep(0.1)
w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0]
p = psutil.Process(self.pid)
vms = p.memory_info().vms
@@ -557,8 +561,11 @@ class TestProcessWMI(TestCase):
self.assertEqual(wmic_create, psutil_create)
+# ---
+
+
@unittest.skipIf(not WINDOWS, "WINDOWS only")
-class TestDualProcessImplementation(TestCase):
+class TestDualProcessImplementation(PsutilTestCase):
"""
Certain APIs on Windows have 2 internal implementations, one
based on documented Windows APIs, another one based
@@ -661,28 +668,25 @@ class RemoteProcessTestCase(PsutilTestCase):
stderr=subprocess.STDOUT)
output, _ = proc.communicate()
proc.wait()
- if output == str(not IS_64_BIT):
+ if output == str(not IS_64BIT):
return filename
- @classmethod
- def setUpClass(cls):
- other_python = cls.find_other_interpreter()
+ test_args = ["-c", "import sys; sys.stdin.read()"]
+
+ def setUp(self):
+ super().setUp()
+ other_python = self.find_other_interpreter()
if other_python is None:
raise unittest.SkipTest(
"could not find interpreter with opposite bitness")
-
- if IS_64_BIT:
- cls.python64 = sys.executable
- cls.python32 = other_python
+ if IS_64BIT:
+ self.python64 = sys.executable
+ self.python32 = other_python
else:
- cls.python64 = other_python
- cls.python32 = sys.executable
-
- test_args = ["-c", "import sys; sys.stdin.read()"]
+ self.python64 = other_python
+ self.python32 = sys.executable
- def setUp(self):
- super().setUp()
env = os.environ.copy()
env["THINK_OF_A_NUMBER"] = str(os.getpid())
self.proc32 = self.spawn_testproc(
@@ -737,7 +741,7 @@ class RemoteProcessTestCase(PsutilTestCase):
@unittest.skipIf(not WINDOWS, "WINDOWS only")
-class TestServices(TestCase):
+class TestServices(PsutilTestCase):
def test_win_service_iter(self):
valid_statuses = set([
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/convert_readme.py b/scripts/internal/convert_readme.py
new file mode 100755
index 00000000..d6cae918
--- /dev/null
+++ b/scripts/internal/convert_readme.py
@@ -0,0 +1,50 @@
+#!/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.
+
+"""
+Convert README.rst format to make it compatible with PyPI (no raw html).
+"""
+
+import re
+import sys
+
+summary = """\
+Quick links
+===========
+
+- `Home page <https://github.com/giampaolo/psutil>`_
+- `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_
+- `Documentation <http://psutil.readthedocs.io>`_
+- `Download <https://pypi.org/project/psutil/#files>`_
+- `Forum <http://groups.google.com/group/psutil/topics>`_
+- `StackOverflow <https://stackoverflow.com/questions/tagged/psutil>`_
+- `Blog <https://gmpy.dev/tags/psutil>`_
+- `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`_
+"""
+
+funding = """\
+Sponsors
+========
+
+.. image:: https://github.com/giampaolo/psutil/raw/master/docs/_static/tidelift-logo.png
+ :width: 200
+ :alt: Alternative text
+
+`Add your logo <https://github.com/sponsors/giampaolo>`__.
+
+Example usages"""
+
+
+def main():
+ with open(sys.argv[1]) as f:
+ data = f.read()
+ data = re.sub(r".. raw:: html\n+\s+<div align[\s\S]*?/div>", summary, data)
+ data = re.sub(r"Sponsors\n========[\s\S]*?Example usages", funding, data)
+ print(data)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/internal/win_download_wheels.py b/scripts/internal/download_wheels_appveyor.py
index 3720dd96..bc6c9717 100755
--- a/scripts/internal/win_download_wheels.py
+++ b/scripts/internal/download_wheels_appveyor.py
@@ -5,7 +5,7 @@
# found in the LICENSE file.
"""
-Script which downloads exe and wheel files hosted on AppVeyor:
+Script which downloads wheel files hosted on AppVeyor:
https://ci.appveyor.com/project/giampaolo/psutil
Readapted from the original recipe of Ibarra Corretge'
<saghul@gmail.com>:
@@ -13,12 +13,9 @@ 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
@@ -26,36 +23,17 @@ from psutil._common import bytes2human
from psutil._common import print_color
+USER = "giampaolo"
+PROJECT = "psutil"
BASE_URL = 'https://ci.appveyor.com/api'
-PY_VERSIONS = ['2.7', '3.5', '3.6', '3.7', '3.8']
+PY_VERSIONS = ['2.7', '3.6', '3.7', '3.8', '3.9']
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:
@@ -66,10 +44,10 @@ def download_file(url):
return local_fname
-def get_file_urls(options):
+def get_file_urls():
with requests.Session() as session:
data = session.get(
- BASE_URL + '/projects/' + options.user + '/' + options.project,
+ BASE_URL + '/projects/' + USER + '/' + PROJECT,
timeout=TIMEOUT)
data = data.json()
@@ -82,14 +60,14 @@ def get_file_urls(options):
file_url = job_url + '/' + item['fileName']
urls.append(file_url)
if not urls:
- print_color("no artifacts found", 'ret')
+ print_color("no artifacts found", 'red')
sys.exit(1)
else:
for url in sorted(urls, key=lambda x: os.path.basename(x)):
yield url
-def rename_27_wheels():
+def rename_win27_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
@@ -101,9 +79,8 @@ def rename_27_wheels():
os.rename(src, dst)
-def run(options):
- safe_rmtree('dist')
- urls = get_file_urls(options)
+def run():
+ urls = get_file_urls()
completed = 0
exc = None
with concurrent.futures.ThreadPoolExecutor() as e:
@@ -125,16 +102,11 @@ def run(options):
return exit("expected %s files, got %s" % (expected, completed))
if exc:
return exit()
- rename_27_wheels()
+ rename_win27_wheels()
def main():
- parser = argparse.ArgumentParser(
- description='AppVeyor artifact downloader')
- parser.add_argument('--user', required=True)
- parser.add_argument('--project', required=True)
- args = parser.parse_args()
- run(args)
+ run()
if __name__ == '__main__':
diff --git a/scripts/internal/download_wheels_github.py b/scripts/internal/download_wheels_github.py
new file mode 100755
index 00000000..a344ec49
--- /dev/null
+++ b/scripts/internal/download_wheels_github.py
@@ -0,0 +1,102 @@
+#!/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 sys
+import zipfile
+
+from psutil import __version__ as PSUTIL_VERSION
+from psutil._common import bytes2human
+from psutil.tests import safe_rmpath
+
+
+USER = "giampaolo"
+PROJECT = "psutil"
+OUTFILE = "wheels-github.zip"
+TOKEN = ""
+
+
+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 rename_win27_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
+ if os.path.exists(src):
+ 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
+ if os.path.exists(src):
+ print("rename: %s\n %s" % (src, dst))
+ os.rename(src, dst)
+
+
+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')
+ rename_win27_wheels()
+
+
+def main():
+ global TOKEN
+ parser = argparse.ArgumentParser(description='GitHub wheels downloader')
+ parser.add_argument('--token')
+ parser.add_argument('--tokenfile')
+ args = parser.parse_args()
+
+ if args.tokenfile:
+ with open(os.path.expanduser(args.tokenfile)) as f:
+ TOKEN = f.read().strip()
+ elif args.token:
+ TOKEN = args.token
+ else:
+ return sys.exit('specify --token or --tokenfile args')
+
+ try:
+ run()
+ finally:
+ safe_rmpath(OUTFILE)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/internal/generate_manifest.py b/scripts/internal/generate_manifest.py
index c0be6d99..384fb329 100755
--- a/scripts/internal/generate_manifest.py
+++ b/scripts/internal/generate_manifest.py
@@ -13,7 +13,7 @@ import subprocess
SKIP_EXTS = ('.png', '.jpg', '.jpeg')
-SKIP_FILES = ('.travis.yml', 'appveyor.yml')
+SKIP_FILES = ('appveyor.yml')
SKIP_PREFIXES = ('.ci/', '.github/')
diff --git a/scripts/internal/print_announce.py b/scripts/internal/print_announce.py
index 9569c367..c9948c1d 100755
--- a/scripts/internal/print_announce.py
+++ b/scripts/internal/print_announce.py
@@ -6,16 +6,22 @@
"""
Prints release announce based on HISTORY.rst file content.
+See: https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode
"""
import os
import re
+import subprocess
+import sys
from psutil import __version__ as PRJ_VERSION
HERE = os.path.abspath(os.path.dirname(__file__))
-HISTORY = os.path.abspath(os.path.join(HERE, '../../HISTORY.rst'))
+ROOT = os.path.realpath(os.path.join(HERE, '..', '..'))
+HISTORY = os.path.join(ROOT, 'HISTORY.rst')
+PRINT_HASHES_SCRIPT = os.path.join(
+ ROOT, 'scripts', 'internal', 'print_hashes.py')
PRJ_NAME = 'psutil'
PRJ_URL_HOME = 'https://github.com/giampaolo/psutil'
@@ -56,9 +62,14 @@ Links
- Documentation: {prj_urldoc}
- What's new: {prj_urlwhatsnew}
+Hashes
+======
+
+{hashes}
+
--
-Giampaolo - http://grodola.blogspot.com
+Giampaolo - https://gmpy.dev/about
"""
@@ -100,6 +111,8 @@ def get_changes():
def main():
changes = get_changes()
+ hashes = subprocess.check_output(
+ [sys.executable, PRINT_HASHES_SCRIPT, 'dist/']).strip().decode()
print(template.format(
prj_name=PRJ_NAME,
prj_version=PRJ_VERSION,
@@ -108,6 +121,7 @@ def main():
prj_urldoc=PRJ_URL_DOC,
prj_urlwhatsnew=PRJ_URL_WHATSNEW,
changes=changes,
+ hashes=hashes,
))
diff --git a/scripts/internal/print_downloads.py b/scripts/internal/print_downloads.py
new file mode 100755
index 00000000..7e5c4631
--- /dev/null
+++ b/scripts/internal/print_downloads.py
@@ -0,0 +1,161 @@
+#!/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"
+LAST_UPDATE = None
+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():
+ global LAST_UPDATE
+ ret = query("pypinfo --all --json --days %s --limit %s '' project" % (
+ DAYS, LIMIT))
+ LAST_UPDATE = ret['last_update']
+ return [(x['project'], x['download_count']) for x in ret['rows']]
+
+
+def ranking():
+ data = top_packages()
+ i = 1
+ for name, downloads in data:
+ if name == PKGNAME:
+ return i
+ i += 1
+ raise ValueError("can't find %s" % PKGNAME)
+
+
+def downloads():
+ data = top_packages()
+ for name, downloads in data:
+ if name == PKGNAME:
+ return downloads
+ raise ValueError("can't find %s" % PKGNAME)
+
+
+def downloads_pyver():
+ return query("pypinfo --json --days %s %s pyversion" % (DAYS, PKGNAME))
+
+
+def downloads_by_country():
+ return query("pypinfo --json --days %s %s country" % (DAYS, PKGNAME))
+
+
+def downloads_by_system():
+ return query("pypinfo --json --days %s %s system" % (DAYS, PKGNAME))
+
+
+def downloads_by_distro():
+ return query("pypinfo --json --days %s %s distro" % (DAYS, PKGNAME))
+
+
+# --- 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():
+ downs = downloads()
+
+ print("# Download stats")
+ print("")
+ s = "psutil download statistics of the last %s days (last update " % DAYS
+ s += "*%s*).\n" % LAST_UPDATE
+ s += "Generated via [pypistats.py](%s) script.\n" % GITHUB_SCRIPT_URL
+ print(s)
+
+ data = [
+ {'what': 'Per month', 'download_count': downs},
+ {'what': 'Per day', 'download_count': int(downs / 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_hashes.py b/scripts/internal/print_hashes.py
new file mode 100755
index 00000000..434c43d5
--- /dev/null
+++ b/scripts/internal/print_hashes.py
@@ -0,0 +1,35 @@
+#!/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.
+
+"""
+Prints files hashes.
+See: https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode
+"""
+
+import hashlib
+import os
+import sys
+
+
+def csum(file, kind):
+ h = hashlib.new(kind)
+ with open(file, "rb") as f:
+ h.update(f.read())
+ return h.hexdigest()
+
+
+def main():
+ dir = sys.argv[1]
+ for name in sorted(os.listdir(dir)):
+ file = os.path.join(dir, name)
+ md5 = csum(file, "md5")
+ sha256 = csum(file, "sha256")
+ print("%s\nmd5: %s\nsha256: %s\n" % (
+ os.path.basename(file), md5, sha256))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/internal/print_wheels.py b/scripts/internal/print_wheels.py
new file mode 100755
index 00000000..c2b8d36b
--- /dev/null
+++ b/scripts/internal/print_wheels.py
@@ -0,0 +1,97 @@
+#!/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
+
+
+class Wheel:
+
+ def __init__(self, path):
+ self._path = path
+ self._name = os.path.basename(path)
+
+ def __repr__(self):
+ return "<Wheel(name=%s, plat=%s, arch=%s, pyver=%s)>" % (
+ self.name, self.platform(), self.arch(), self.pyver())
+
+ __str__ = __repr__
+
+ @property
+ def name(self):
+ return self._name
+
+ def platform(self):
+ plat = self.name.split('-')[-1]
+ pyimpl = self.name.split('-')[3]
+ ispypy = 'pypy' in pyimpl
+ if 'linux' in plat:
+ if ispypy:
+ return 'pypy_on_linux'
+ else:
+ return 'linux'
+ elif 'win' in plat:
+ if ispypy:
+ return 'pypy_on_windows'
+ else:
+ return 'windows'
+ elif 'macosx' in plat:
+ if ispypy:
+ return 'pypy_on_macos'
+ else:
+ return 'macos'
+ else:
+ raise ValueError("unknown platform %r" % self.name)
+
+ def arch(self):
+ if self.name.endswith(('x86_64.whl', 'amd64.whl')):
+ return '64'
+ return '32'
+
+ def pyver(self):
+ pyver = 'pypy' if self.name.split('-')[3].startswith('pypy') else 'py'
+ pyver += self.name.split('-')[2][2:]
+ return pyver
+
+ def size(self):
+ return os.path.getsize(self._path)
+
+
+def main():
+ groups = collections.defaultdict(list)
+ for path in glob.glob('dist/*.whl'):
+ wheel = Wheel(path)
+ groups[wheel.platform()].append(wheel)
+
+ tot_files = 0
+ tot_size = 0
+ templ = "%-54s %7s %7s %7s"
+ for platf, wheels in groups.items():
+ ppn = "%s (total = %s)" % (platf, len(wheels))
+ s = templ % (ppn, "size", "arch", "pyver")
+ print_color('\n' + s, color=None, bold=True)
+ for wheel in sorted(wheels, key=lambda x: x.name):
+ tot_files += 1
+ tot_size += wheel.size()
+ s = templ % (wheel.name, bytes2human(wheel.size()), wheel.arch(),
+ wheel.pyver())
+ if 'pypy' in wheel.pyver():
+ print_color(s, color='violet')
+ else:
+ print_color(s, color='brown')
+
+ print_color("\ntotals: files=%s, size=%s" % (
+ tot_files, bytes2human(tot_size)), bold=True)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/internal/tidelift.py b/scripts/internal/tidelift.py
index fcba3e61..fcba3e61 100644..100755
--- a/scripts/internal/tidelift.py
+++ b/scripts/internal/tidelift.py
diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py
index 4f1e65ed..933951a2 100755
--- a/scripts/internal/winmake.py
+++ b/scripts/internal/winmake.py
@@ -47,21 +47,19 @@ DEPS = [
"pyreadline",
"setuptools",
"wheel",
- "wmi",
"requests"
]
-if sys.version_info[:2] <= (2, 6):
+if sys.version_info[:2] <= (2, 7):
DEPS.append('unittest2')
if sys.version_info[:2] <= (2, 7):
DEPS.append('mock')
if sys.version_info[:2] <= (3, 2):
DEPS.append('ipaddress')
-if PYPY:
- pass
-elif sys.version_info[:2] <= (3, 4):
- DEPS.append("pypiwin32==219")
-else:
- DEPS.append("pypiwin32")
+if sys.version_info[:2] <= (3, 4):
+ DEPS.append('enum34')
+if not PYPY:
+ DEPS.append("pywin32")
+ DEPS.append("wmi")
_cmds = {}
if PY3:
@@ -210,12 +208,6 @@ def recursive_rm(*patterns):
safe_rmtree(os.path.join(root, dir))
-def test_setup():
- os.environ['PYTHONWARNINGS'] = 'all'
- os.environ['PSUTIL_TESTING'] = '1'
- os.environ['PSUTIL_DEBUG'] = '1'
-
-
# ===================================================================
# commands
# ===================================================================
@@ -370,7 +362,6 @@ def clean():
"*__pycache__",
".coverage",
".failed-tests.txt",
- ".tox",
)
safe_rmtree("build")
safe_rmtree(".coverage")
@@ -400,7 +391,6 @@ def lint():
def test(name=RUNNER_PY):
"""Run tests"""
build()
- test_setup()
sh("%s %s" % (PYTHON, name))
@@ -408,7 +398,6 @@ def coverage():
"""Run coverage tests."""
# Note: coverage options are controlled by .coveragerc file
build()
- test_setup()
sh("%s -m coverage run %s" % (PYTHON, RUNNER_PY))
sh("%s -m coverage report" % PYTHON)
sh("%s -m coverage html" % PYTHON)
@@ -418,71 +407,67 @@ def coverage():
def test_process():
"""Run process tests"""
build()
- test_setup()
sh("%s psutil\\tests\\test_process.py" % PYTHON)
def test_system():
"""Run system tests"""
build()
- test_setup()
sh("%s psutil\\tests\\test_system.py" % PYTHON)
def test_platform():
"""Run windows only tests"""
build()
- test_setup()
sh("%s psutil\\tests\\test_windows.py" % PYTHON)
def test_misc():
"""Run misc tests"""
build()
- test_setup()
sh("%s psutil\\tests\\test_misc.py" % PYTHON)
def test_unicode():
"""Run unicode tests"""
build()
- test_setup()
sh("%s psutil\\tests\\test_unicode.py" % PYTHON)
def test_connections():
"""Run connections tests"""
build()
- test_setup()
sh("%s psutil\\tests\\test_connections.py" % PYTHON)
def test_contracts():
"""Run contracts tests"""
build()
- test_setup()
sh("%s psutil\\tests\\test_contracts.py" % PYTHON)
+def test_testutils():
+ """Run test utilities tests"""
+ build()
+ sh("%s psutil\\tests\\test_testutils.py" % PYTHON)
+
+
def test_by_name(name):
"""Run test by name"""
build()
- test_setup()
sh("%s -m unittest -v %s" % (PYTHON, name))
def test_failed():
"""Re-run tests which failed on last run."""
build()
- test_setup()
sh("%s %s --last-failed" % (PYTHON, RUNNER_PY))
def test_memleaks():
"""Run memory leaks tests"""
build()
- test_setup()
- sh("%s psutil\\tests\\test_memory_leaks.py" % PYTHON)
+ sh("%s psutil\\tests\\test_memleaks.py" % PYTHON)
def install_git_hooks():
@@ -510,17 +495,21 @@ def bench_oneshot_2():
def print_access_denied():
"""Print AD exceptions raised by all Process methods."""
build()
- test_setup()
sh("%s -Wa scripts\\internal\\print_access_denied.py" % PYTHON)
def print_api_speed():
"""Benchmark all API calls."""
build()
- test_setup()
sh("%s -Wa scripts\\internal\\print_api_speed.py" % PYTHON)
+def download_appveyor_wheels():
+ """Download appveyor wheels."""
+ sh("%s -Wa scripts\\internal\\download_wheels_appveyor.py "
+ "--user giampaolo --project psutil" % PYTHON)
+
+
def get_python(path):
if not path:
return sys.executable
@@ -528,9 +517,25 @@ def get_python(path):
return path
# try to look for a python installation given a shortcut name
path = path.replace('.', '')
- vers = ('26', '27', '36', '37', '38',
- '26-64', '27-64', '36-64', '37-64', '38-64'
- '26-32', '27-32', '36-32', '37-32', '38-32')
+ vers = (
+ '26',
+ '26-32',
+ '26-64',
+ '27',
+ '27-32',
+ '27-64',
+ '36',
+ '36-32',
+ '36-64',
+ '37',
+ '37-32',
+ '37-64',
+ '38',
+ '38-32',
+ '38-64',
+ '39-32',
+ '39-64',
+ )
for v in vers:
pypath = r'C:\\python%s\python.exe' % v
if path in pypath and os.path.isfile(pypath):
@@ -550,6 +555,7 @@ def main():
sp.add_parser('build', help="build")
sp.add_parser('clean', help="deletes dev files")
sp.add_parser('coverage', help="run coverage tests.")
+ sp.add_parser('download-appveyor-wheels', help="download wheels.")
sp.add_parser('help', help="print this help")
sp.add_parser('install', help="build + install in develop/edit mode")
sp.add_parser('install-git-hooks', help="install GIT pre-commit hook")
@@ -569,6 +575,7 @@ def main():
sp.add_parser('test-process', help="run process tests")
sp.add_parser('test-system', help="run system tests")
sp.add_parser('test-unicode', help="run unicode tests")
+ sp.add_parser('test-testutils', help="run test utils tests")
sp.add_parser('uninstall', help="uninstall psutil")
sp.add_parser('upload-wheels', help="upload wheel files on PyPI")
sp.add_parser('wheel', help="create wheel file")
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..1832a096 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
@@ -48,13 +48,14 @@ def main():
raddr = ""
if c.raddr:
raddr = "%s:%s" % (c.raddr)
+ name = proc_names.get(c.pid, '?') or ''
print(templ % (
proto_map[(c.family, c.type)],
laddr,
raddr or AD,
c.status,
c.pid or AD,
- proc_names.get(c.pid, '?')[:15],
+ name[:15],
))
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..743a1777 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)
@@ -102,17 +102,19 @@ RLIMITS_MAP = {
"RLIMIT_CPU": "cputime",
"RLIMIT_DATA": "datasize",
"RLIMIT_FSIZE": "filesize",
- "RLIMIT_LOCKS": "locks",
"RLIMIT_MEMLOCK": "memlock",
"RLIMIT_MSGQUEUE": "msgqueue",
"RLIMIT_NICE": "nice",
"RLIMIT_NOFILE": "openfiles",
"RLIMIT_NPROC": "maxprocesses",
+ "RLIMIT_NPTS": "pseudoterms",
"RLIMIT_RSS": "rss",
"RLIMIT_RTPRIO": "realtimeprio",
"RLIMIT_RTTIME": "rtimesched",
+ "RLIMIT_SBSIZE": "sockbufsize",
"RLIMIT_SIGPENDING": "sigspending",
"RLIMIT_STACK": "stack",
+ "RLIMIT_SWAP": "swapuse",
}
@@ -317,7 +319,7 @@ def run(pid, verbose=False):
def main(argv=None):
parser = argparse.ArgumentParser(
description="print information about a process")
- parser.add_argument("pid", type=int, help="process pid")
+ parser.add_argument("pid", type=int, help="process pid", nargs='?')
parser.add_argument('--verbose', '-v', action='store_true',
help="print more info")
args = parser.parse_args()
diff --git a/scripts/procsmem.py b/scripts/procsmem.py
index 259d79d4..1074c4c2 100755
--- a/scripts/procsmem.py
+++ b/scripts/procsmem.py
@@ -80,18 +80,19 @@ def main():
procs.append(p)
procs.sort(key=lambda p: p._uss)
- templ = "%-7s %-7s %-30s %7s %7s %7s %7s"
- print(templ % ("PID", "User", "Cmdline", "USS", "PSS", "Swap", "RSS"))
+ templ = "%-7s %-7s %7s %7s %7s %7s %7s"
+ print(templ % ("PID", "User", "USS", "PSS", "Swap", "RSS", "Cmdline"))
print("=" * 78)
for p in procs[:86]:
+ cmd = " ".join(p._info["cmdline"])[:50] if p._info["cmdline"] else ""
line = templ % (
p.pid,
p._info["username"][:7] if p._info["username"] else "",
- " ".join(p._info["cmdline"])[:30],
convert_bytes(p._uss),
convert_bytes(p._pss) if p._pss != "" else "",
convert_bytes(p._swap) if p._swap != "" else "",
convert_bytes(p._rss),
+ cmd,
)
print(line)
if ad_pids:
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 19153bda..16b476f2 100755
--- a/setup.py
+++ b/setup.py
@@ -14,6 +14,7 @@ import platform
import re
import shutil
import struct
+import subprocess
import sys
import tempfile
import warnings
@@ -47,6 +48,7 @@ from _compat import PY3 # NOQA
from _compat import which # NOQA
+PYPY = '__pypy__' in sys.builtin_module_names
macros = []
if POSIX:
macros.append(("PSUTIL_POSIX", 1))
@@ -67,9 +69,16 @@ if POSIX:
sources.append('psutil/_psutil_posix.c')
-extras_require = {}
-if sys.version_info[:2] <= (3, 3):
- extras_require.update(dict(enum='enum34'))
+extras_require = {"test": [
+ "enum34; python_version <= '3.4'",
+ "ipaddress; python_version < '3.0'",
+ "mock; python_version < '3.0'",
+ "unittest2; python_version < '3.0'",
+]}
+if not PYPY:
+ extras_require['test'].extend([
+ "pywin32; sys.platform == 'win32'",
+ "wmi; sys.platform == 'win32'"])
def get_version():
@@ -90,9 +99,17 @@ macros.append(('PSUTIL_VERSION', int(VERSION.replace('.', ''))))
def get_description():
- README = os.path.join(HERE, 'README.rst')
- with open(README, 'r') as f:
- return f.read()
+ script = os.path.join(HERE, "scripts", "internal", "convert_readme.py")
+ readme = os.path.join(HERE, 'README.rst')
+ p = subprocess.Popen([sys.executable, script, readme],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ if p.returncode != 0:
+ raise RuntimeError(stderr)
+ data = stdout.decode('utf8')
+ if WINDOWS:
+ data = data.replace('\r\n', '\n')
+ return data
@contextlib.contextmanager
@@ -159,7 +176,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"]
@@ -172,6 +189,7 @@ elif MACOS:
sources=sources + [
'psutil/_psutil_osx.c',
'psutil/arch/osx/process_info.c',
+ 'psutil/arch/osx/cpu.c',
],
define_macros=macros,
extra_link_args=[
@@ -184,6 +202,7 @@ elif FREEBSD:
'psutil._psutil_bsd',
sources=sources + [
'psutil/_psutil_bsd.c',
+ 'psutil/arch/freebsd/cpu.c',
'psutil/arch/freebsd/specific.c',
'psutil/arch/freebsd/sys_socks.c',
'psutil/arch/freebsd/proc_socks.c',
@@ -320,6 +339,7 @@ def main():
version=VERSION,
description=__doc__ .replace('\n', ' ').strip() if __doc__ else '',
long_description=get_description(),
+ long_description_content_type='text/x-rst',
keywords=[
'ps', 'top', 'kill', 'free', 'lsof', 'netstat', 'nice', 'tty',
'ionice', 'uptime', 'taskmgr', 'process', 'df', 'iotop', 'iostat',
@@ -412,14 +432,6 @@ def main():
elif SUNOS:
missdeps("sudo ln -s /usr/bin/gcc /usr/local/bin/cc && "
"pkg install gcc")
- elif not success and WINDOWS:
- if PY3:
- ur = "http://www.visualstudio.com/en-au/news/vs2015-preview-vs"
- else:
- ur = "http://www.microsoft.com/en-us/download/"
- ur += "details.aspx?id=44266"
- s = "VisualStudio is not installed; get it from %s" % ur
- print(hilite(s, color="red"), file=sys.stderr)
if __name__ == '__main__':
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index b148642b..00000000
--- a/tox.ini
+++ /dev/null
@@ -1,28 +0,0 @@
-# Tox (http://tox.testrun.org/) is a tool for running tests
-# in multiple virtualenvs. This configuration file will run the
-# test suite on all supported python versions.
-# To use it run "pip install tox" and then run "tox" from this
-# directory.
-
-[tox]
-envlist = py26, py27, py34, py35, py36, py37, py38, lint
-
-[testenv]
-deps =
- py26: ipaddress
- py26: mock==1.0.1
- py26: unittest2
- py27: ipaddress
- py27: mock
-
-setenv =
- TOX = 1
-
-commands = python psutil/tests/runner.py
-
-usedevelop = True
-
-[testenv:lint]
-deps = flake8
-commands = flake8
-skip_install = True