From ab896aeff80d7810be5d3ce15439fd4932042c12 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 17 Apr 2023 17:04:26 +0200 Subject: [PATCH 1/2] add CI testing for all *BSD platforms --- .github/workflows/bsd.yml | 67 ++++++ .github/workflows/build.yml | 44 +--- CREDITS | 2 +- HISTORY.rst | 26 ++ INSTALL.rst | 10 +- MANIFEST.in | 12 +- README.rst | 14 +- docs/index.rst | 44 ++-- psutil/__init__.py | 7 +- psutil/_psaix.py | 2 +- psutil/_psbsd.py | 96 ++++---- psutil/_pssunos.py | 2 +- psutil/_psutil_bsd.c | 16 +- psutil/_psutil_osx.c | 1 + psutil/_psutil_windows.c | 1 + psutil/_pswindows.py | 16 +- psutil/arch/netbsd/cpu.c | 102 ++++++++ psutil/arch/netbsd/cpu.h | 11 + psutil/arch/netbsd/disk.c | 75 ++++++ psutil/arch/netbsd/disk.h | 10 + psutil/arch/netbsd/mem.c | 113 +++++++++ psutil/arch/netbsd/mem.h | 11 + psutil/arch/netbsd/{specific.c => proc.c} | 276 +--------------------- psutil/arch/netbsd/{specific.h => proc.h} | 5 - psutil/arch/openbsd/proc.c | 233 +----------------- psutil/arch/openbsd/proc.h | 2 - psutil/arch/openbsd/socks.c | 180 ++++++++++++++ psutil/arch/openbsd/socks.h | 8 + psutil/arch/windows/mem.c | 51 ++++ psutil/arch/windows/mem.h | 1 + psutil/tests/__init__.py | 2 +- psutil/tests/test_bsd.py | 13 + psutil/tests/test_connections.py | 14 +- psutil/tests/test_contracts.py | 23 +- psutil/tests/test_posix.py | 19 +- psutil/tests/test_process.py | 29 ++- psutil/tests/test_system.py | 2 +- psutil/tests/test_unicode.py | 13 +- psutil/tests/test_windows.py | 21 ++ setup.py | 6 +- 40 files changed, 907 insertions(+), 673 deletions(-) create mode 100644 .github/workflows/bsd.yml create mode 100644 psutil/arch/netbsd/cpu.c create mode 100644 psutil/arch/netbsd/cpu.h create mode 100644 psutil/arch/netbsd/disk.c create mode 100644 psutil/arch/netbsd/disk.h create mode 100644 psutil/arch/netbsd/mem.c create mode 100644 psutil/arch/netbsd/mem.h rename psutil/arch/netbsd/{specific.c => proc.c} (59%) rename psutil/arch/netbsd/{specific.h => proc.h} (74%) create mode 100644 psutil/arch/openbsd/socks.c create mode 100644 psutil/arch/openbsd/socks.h diff --git a/.github/workflows/bsd.yml b/.github/workflows/bsd.yml new file mode 100644 index 000000000..9c811d183 --- /dev/null +++ b/.github/workflows/bsd.yml @@ -0,0 +1,67 @@ +# Execute tests on *BSD platforms. Does not produce wheels. +# Useful URLs: +# https://github.com/vmactions/freebsd-vm +# https://github.com/vmactions/openbsd-vm +# https://github.com/vmactions/netbsd-vm + +on: [push, pull_request] +name: bsd-tests +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.sha || '' }} + cancel-in-progress: true +jobs: + freebsd: + runs-on: macos-12 + steps: + - uses: actions/checkout@v3 + - name: Run tests + uses: vmactions/freebsd-vm@v0 + with: + usesh: true + prepare: | + pkg install -y gcc python3 + run: | + set -e -x + make install-pip + python3 -m pip install --user setuptools + make install + make test + make test-memleaks + openbsd: + runs-on: macos-12 + steps: + - uses: actions/checkout@v3 + - name: Run tests + uses: vmactions/openbsd-vm@v0 + with: + usesh: true + prepare: | + set -e + pkg_add gcc python3 + run: | + set -e + make install-pip + python3 -m pip install --user setuptools + make install + make test + make test-memleaks + netbsd: + runs-on: macos-12 + steps: + - uses: actions/checkout@v3 + - name: Run tests + uses: vmactions/netbsd-vm@v0 + with: + usesh: true + prepare: | + set -e + pkg_add -v pkgin + pkgin update + pkgin -y install python311-* py311-setuptools-* gcc12-* + run: | + set -e + make install-pip PYTHON=python3.11 + python3.11 -m pip install --user setuptools + make install PYTHON=python3.11 + make test PYTHON=python3.11 + make test-memleaks PYTHON=python3.11 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb6996daa..d735a0cee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,21 +1,15 @@ -# Executed on every push by GitHub Actions. This runs CI tests and -# generates wheels (not all) on the following platforms: +# Runs CI tests and generates wheels on the following platforms: # -# * Linux -# * macOS -# * Windows (disabled) -# * FreeBSD +# * Linux (py2 and py3) +# * macOS (py2 and py3) +# * Windows (py3, py2 is done by appveyor) # -# To skip certain builds see: -# https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip -# -# External GH actions: +# Useful URLs: # * https://github.com/pypa/cibuildwheel # * 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, pull_request] name: build @@ -110,34 +104,6 @@ jobs: python setup.py sdist mv dist/psutil*.tar.gz wheelhouse/ - # FreeBSD (tests only) - py3-freebsd: - runs-on: macos-12 - steps: - - name: Cancel previous runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v3 - - - name: Run tests - id: test - uses: vmactions/freebsd-vm@v0 - with: - usesh: true - prepare: pkg install -y gcc python3 - run: | - set +e - export \ - PYTHONUNBUFFERED=1 \ - PYTHONWARNINGS=always \ - PSUTIL_DEBUG=1 - python3 -m pip install --user setuptools - python3 setup.py install - python3 psutil/tests/runner.py - python3 psutil/tests/test_memleaks.py - # Run linters linters: runs-on: ubuntu-latest diff --git a/CREDITS b/CREDITS index f41463608..c845a8c66 100644 --- a/CREDITS +++ b/CREDITS @@ -804,7 +804,7 @@ I: 2150 N: Daniel Widdis W: https://github.com/dbwiddis -I: 2077 +I: 2077, 2160 N: Amir Rossert W: https://github.com/arossert diff --git a/HISTORY.rst b/HISTORY.rst index ac50e8979..003dad239 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -9,9 +9,24 @@ `KeyError` bit deriving from a missed cache hit. - 2217_: print the full traceback when a `DeprecationWarning` or `UserWarning` is raised. +- 2230_, [OpenBSD]: `psutil.net_connections`_ implementation was rewritten from + scratch: + - We're now able to retrieve the path of AF_UNIX sockets (before it was an + empty string) + - The function is faster since it no longer iterates over all processes. + - No longer produces duplicate connection entries. +- 2238_: there are cases where `Process.cwd()`_ cannot be determined + (e.g. directory no longer exists), in which case we returned either ``None`` + or an empty string. This was consolidated and we now return ``""`` on all + platforms. +- 2239_, [UNIX]: if process is a zombie, and we can only determine part of the + its truncated `Process.name()`_ (15 chars), don't fail with `ZombieProcess`_ + when we try to guess the full name from the `Process.cmdline()`_. Just + return the truncated name. **Bug fixes** +- 1043_, [OpenBSD] `psutil.net_connections`_ returns duplicate entries. - 1915_, [Linux]: on certain kernels, ``"MemAvailable"`` field from ``/proc/meminfo`` returns ``0`` (possibly a kernel bug), in which case we calculate an approximation for ``available`` memory which matches "free" @@ -25,6 +40,15 @@ Matthieu Darbois) - 2225_, [POSIX]: `users()`_ loses precision for ``started`` attribute (off by 1 minute). +- 2229_, [OpenBSD]: unable to properly recognize zombie processes. + `NoSuchProcess`_ may be raised instead of `ZombieProcess`_. +- 2231_, [NetBSD]: *available* `virtual_memory()`_ is higher than *total*. +- 2234_, [NetBSD]: `virtual_memory()`_ metrics are wrong: *available* and + *used* are too high. We now match values shown by *htop* CLI utility. +- 2236_, [NetBSD]: `Process.num_threads()`_ and `Process.threads()`_ return + threads that are already terminated. +- 2237_, [OpenBSD], [NetBSD]: `Process.cwd()`_ may raise ``FileNotFoundError`` + if cwd no longer exists. Return an empty string instead. 5.9.4 ===== @@ -45,6 +69,8 @@ ``SPEED_UNKNOWN`` definition. (patch by Amir Rossert) - 2010_, [macOS]: on MacOS, arm64 ``IFM_1000_TX`` and ``IFM_1000_T`` are the same value, causing a build failure. (patch by Lawrence D'Anna) +- 2160_, [Windows]: Get Windows percent swap usage from performance counters. + (patch by Daniel Widdis) 5.9.3 ===== diff --git a/INSTALL.rst b/INSTALL.rst index 4f5831e67..2e8a1cd51 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -62,18 +62,20 @@ OpenBSD :: export PKG_PATH=http://ftp.eu.openbsd.org/pub/OpenBSD/`uname -r`/packages/`uname -m`/ - pkg_add -v python gcc + pkg_add -v python3 gcc pip install psutil NetBSD ------ +Assuming Python 3.11 (the most recent at the time of writing): + :: - export PKG_PATH="ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/`uname -m`/`uname -r`/All" + export PKG_PATH="http://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/`uname -m`/`uname -r`/All" pkg_add -v pkgin - pkgin install python3 gcc - pip install psutil + pkgin install python311-* gcc12-* py311-setuptools-* py311-pip-* + python3.11 -m pip install psutil Sun Solaris ----------- diff --git a/MANIFEST.in b/MANIFEST.in index 0ed5c320e..a594328da 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -61,10 +61,16 @@ include psutil/arch/freebsd/sensors.c include psutil/arch/freebsd/sensors.h include psutil/arch/freebsd/sys_socks.c include psutil/arch/freebsd/sys_socks.h +include psutil/arch/netbsd/cpu.c +include psutil/arch/netbsd/cpu.h +include psutil/arch/netbsd/disk.c +include psutil/arch/netbsd/disk.h +include psutil/arch/netbsd/mem.c +include psutil/arch/netbsd/mem.h +include psutil/arch/netbsd/proc.c +include psutil/arch/netbsd/proc.h include psutil/arch/netbsd/socks.c include psutil/arch/netbsd/socks.h -include psutil/arch/netbsd/specific.c -include psutil/arch/netbsd/specific.h include psutil/arch/openbsd/cpu.c include psutil/arch/openbsd/cpu.h include psutil/arch/openbsd/disk.c @@ -73,6 +79,8 @@ include psutil/arch/openbsd/mem.c include psutil/arch/openbsd/mem.h include psutil/arch/openbsd/proc.c include psutil/arch/openbsd/proc.h +include psutil/arch/openbsd/socks.c +include psutil/arch/openbsd/socks.h include psutil/arch/osx/cpu.c include psutil/arch/osx/cpu.h include psutil/arch/osx/process_info.c diff --git a/README.rst b/README.rst index 361b0f018..2e991cb35 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ | |downloads| |stars| |forks| |contributors| |coverage| | |version| |py-versions| |packages| |license| -| |github-actions| |appveyor| |doc| |twitter| |tidelift| +| |github-actions-wheels| |github-actions-bsd| |appveyor| |doc| |twitter| |tidelift| .. |downloads| image:: https://img.shields.io/pypi/dm/psutil.svg :target: https://pepy.tech/project/psutil @@ -18,13 +18,17 @@ :target: https://github.com/giampaolo/psutil/graphs/contributors :alt: Contributors -.. |github-actions| image:: https://img.shields.io/github/actions/workflow/status/giampaolo/psutil/.github/workflows/build.yml?label=Linux%2C%20macOS%2C%20FreeBSD%20tests +.. |github-actions-wheels| image:: https://img.shields.io/github/actions/workflow/status/giampaolo/psutil/.github/workflows/build.yml?label=Linux%2C%20macOS%2C%20Windows :target: https://github.com/giampaolo/psutil/actions?query=workflow%3Abuild - :alt: Linux, macOS, Windows tests + :alt: Linux, macOS, Windows -.. |appveyor| image:: https://img.shields.io/appveyor/build/giampaolo/psutil/master.svg?maxAge=3600&label=Windows%20tests +.. |github-actions-bsd| image:: https://img.shields.io/github/actions/workflow/status/giampaolo/psutil/.github/workflows/bsd.yml?label=FreeBSD,%20NetBSD,%20OpenBSD + :target: https://github.com/giampaolo/psutil/actions?query=workflow%3Absd-tests + :alt: FreeBSD, NetBSD, OpenBSD + +.. |appveyor| image:: https://img.shields.io/appveyor/build/giampaolo/psutil/master.svg?maxAge=3600&label=Windows%20(py2) :target: https://ci.appveyor.com/project/giampaolo/psutil - :alt: Windows tests (Appveyor) + :alt: Windows (Appveyor) .. |coverage| image:: https://coveralls.io/repos/github/giampaolo/psutil/badge.svg?branch=master :target: https://coveralls.io/github/giampaolo/psutil?branch=master diff --git a/docs/index.rst b/docs/index.rst index 119102a8d..6f0132d2c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -334,14 +334,17 @@ Memory .. function:: virtual_memory() Return statistics about system memory usage as a named tuple including the - following fields, expressed in bytes. Main metrics: + following fields, expressed in bytes. + + Main metrics: - **total**: total physical memory (exclusive swap). - **available**: the memory that can be given instantly to processes without the system going into swap. - This is calculated by summing different memory values depending on the - platform and it is supposed to be used to monitor actual memory usage in a - cross platform fashion. + This is calculated by summing different memory metrics that vary depending + on the platform. It is supposed to be used to monitor actual memory usage + in a cross platform fashion. + - **percent**: the percentage usage calculated as ``(total - available) / total * 100``. Other metrics: @@ -369,7 +372,8 @@ Memory human readable form. .. note:: if you just want to know how much physical memory is left in a - cross platform fashion simply rely on the **available** field. + cross platform fashion simply rely on **available** and **percent** + fields. >>> import psutil >>> mem = psutil.virtual_memory() @@ -659,19 +663,18 @@ Network (Solaris) UNIX sockets are not supported. .. note:: - (Linux, FreeBSD) "raddr" field for UNIX sockets is always set to "". - This is a limitation of the OS. - - .. note:: - (OpenBSD) "laddr" and "raddr" fields for UNIX sockets are always set to - "". This is a limitation of the OS. + (Linux, FreeBSD, OpenBSD) *raddr* field for UNIX sockets is always set to + ``""`` (empty string). This is a limitation of the OS. .. versionadded:: 2.1.0 .. versionchanged:: 5.3.0 : socket "fd" is now set for real instead of being ``-1``. - .. versionchanged:: 5.3.0 : "laddr" and "raddr" are named tuples. + .. versionchanged:: 5.3.0 : *laddr* and *raddr* are named tuples. + + .. versionchanged:: 5.9.5 : OpenBSD: retrieve *laddr* path for AF_UNIX + sockets (before it was an empty string). .. function:: net_if_addrs() @@ -1177,9 +1180,10 @@ Process class .. method:: exe() - The process executable as an absolute path. - On some systems this may also be an empty string. - The return value is cached after first call. + The process executable as an absolute path. On some systems, if exe cannot + be determined for some internal reason (e.g. system process or path no + longer exists), this may be an empty string. The return value is cached + after first call. >>> import psutil >>> psutil.Process().exe() @@ -1278,7 +1282,9 @@ Process class .. method:: cwd() - The process current working directory as an absolute path. + The process current working directory as an absolute path. If cwd cannot be + determined for some internal reason (e.g. system process or directiory no + longer exists) it may return an empty string. .. versionchanged:: 5.6.4 added support for NetBSD @@ -1943,18 +1949,18 @@ Process class (Solaris) UNIX sockets are not supported. .. note:: - (Linux, FreeBSD) "raddr" field for UNIX sockets is always set to "". + (Linux, FreeBSD) *raddr* field for UNIX sockets is always set to "". This is a limitation of the OS. .. note:: - (OpenBSD) "laddr" and "raddr" fields for UNIX sockets are always set to + (OpenBSD) *laddr* and *raddr* fields for UNIX sockets are always set to "". This is a limitation of the OS. .. note:: (AIX) :class:`psutil.AccessDenied` is always raised unless running as root (lsof does the same). - .. versionchanged:: 5.3.0 : "laddr" and "raddr" are named tuples. + .. versionchanged:: 5.3.0 : *laddr* and *raddr* are named tuples. .. method:: is_running() diff --git a/psutil/__init__.py b/psutil/__init__.py index 6036cbe94..2b0b3c6b0 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -626,7 +626,12 @@ def name(self): # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon". try: cmdline = self.cmdline() - except AccessDenied: + except (AccessDenied, ZombieProcess): + # Just pass and return the truncated name: it's better + # than nothing. Note: there are actual cases where a + # zombie process can return a name() but not a + # cmdline(), see: + # https://github.com/giampaolo/psutil/issues/2239 pass else: if cmdline: diff --git a/psutil/_psaix.py b/psutil/_psaix.py index 5c41069cf..67f0314f7 100644 --- a/psutil/_psaix.py +++ b/psutil/_psaix.py @@ -487,7 +487,7 @@ def cwd(self): return result.rstrip('/') except FileNotFoundError: os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD - return None + return "" @wrap_exceptions def memory_info(self): diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index a25c96cd4..99808bd28 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -177,10 +177,9 @@ def virtual_memory(): - """System virtual memory as a namedtuple.""" mem = cext.virtual_mem() - total, free, active, inactive, wired, cached, buffers, shared = mem if NETBSD: + total, free, active, inactive, wired, cached = mem # On NetBSD buffers and shared mem is determined via /proc. # The C ext set them to 0. with open('/proc/meminfo', 'rb') as f: @@ -189,10 +188,25 @@ def virtual_memory(): buffers = int(line.split()[1]) * 1024 elif line.startswith(b'MemShared:'): shared = int(line.split()[1]) * 1024 - elif line.startswith(b'Cached:'): - cached = int(line.split()[1]) * 1024 - avail = inactive + cached + free - used = active + wired + cached + # Before avail was calculated as (inactive + cached + free), + # same as zabbix, but it turned out it could exceed total (see + # #2233), so zabbix seems to be wrong. Htop calculates it + # differently, and the used value seem more realistic, so let's + # match htop. + # https://github.com/htop-dev/htop/blob/e7f447b/netbsd/NetBSDProcessList.c#L162 # noqa + # https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/netbsd/memory.c#L135 # noqa + used = active + wired + avail = total - used + else: + total, free, active, inactive, wired, cached, buffers, shared = mem + # matches freebsd-memory CLI: + # * https://people.freebsd.org/~rse/dist/freebsd-memory + # * https://www.cyberciti.biz/files/scripts/freebsd-memory.pl.txt + # matches zabbix: + # * https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/freebsd/memory.c#L143 # noqa + avail = inactive + cached + free + used = active + wired + cached + percent = usage_percent((total - avail), total, round_=1) return svmem(total, avail, percent, used, free, active, inactive, buffers, cached, shared, wired) @@ -401,36 +415,28 @@ def net_if_stats(): def net_connections(kind): """System-wide network connections.""" - if OPENBSD: - ret = [] - for pid in pids(): - try: - cons = Process(pid).connections(kind) - except (NoSuchProcess, ZombieProcess): - continue - else: - for conn in cons: - conn = list(conn) - conn.append(pid) - ret.append(_common.sconn(*conn)) - return ret - if kind not in _common.conn_tmap: raise ValueError("invalid %r kind argument; choose between %s" % (kind, ', '.join([repr(x) for x in conn_tmap]))) families, types = conn_tmap[kind] ret = set() - if NETBSD: + + if OPENBSD: + rawlist = cext.net_connections(-1, families, types) + elif NETBSD: rawlist = cext.net_connections(-1) - else: + else: # FreeBSD rawlist = cext.net_connections() + for item in rawlist: fd, fam, type, laddr, raddr, status, pid = item - # TODO: apply filter at C level - if fam in families and type in types: - nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, - TCP_STATUSES, pid) - ret.add(nt) + if NETBSD or FREEBSD: + # OpenBSD implements filtering in C + if (fam not in families) or (type not in types): + continue + nt = conn_to_ntuple(fd, fam, type, laddr, raddr, + status, TCP_STATUSES, pid) + ret.add(nt) return list(ret) @@ -545,7 +551,7 @@ def pid_exists(pid): def is_zombie(pid): try: st = cext.proc_oneshot_info(pid)[kinfo_proc_map['status']] - return st == cext.SZOMB + return PROC_STATUSES.get(st) == _common.STATUS_ZOMBIE except Exception: return False @@ -773,33 +779,27 @@ def connections(self, kind='inet'): if kind not in conn_tmap: raise ValueError("invalid %r kind argument; choose between %s" % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + ret = [] if NETBSD: - families, types = conn_tmap[kind] - ret = [] rawlist = cext.net_connections(self.pid) - for item in rawlist: - fd, fam, type, laddr, raddr, status, pid = item - assert pid == self.pid - if fam in families and type in types: - nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, - TCP_STATUSES) - ret.append(nt) - self._assert_alive() - return list(ret) + elif OPENBSD: + rawlist = cext.net_connections(self.pid, families, types) + else: # FreeBSD + rawlist = cext.proc_connections(self.pid, families, types) - families, types = conn_tmap[kind] - rawlist = cext.proc_connections(self.pid, families, types) - ret = [] for item in rawlist: - fd, fam, type, laddr, raddr, status = item + fd, fam, type, laddr, raddr, status = item[:6] + if NETBSD: + # FreeBSD and OpenBSD implement filtering in C + if (fam not in families) or (type not in types): + continue nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, TCP_STATUSES) ret.append(nt) - if OPENBSD: - self._assert_alive() - + self._assert_alive() return ret @wrap_exceptions @@ -835,11 +835,11 @@ def cwd(self): # sometimes we get an empty string, in which case we turn # it into None if OPENBSD and self.pid == 0: - return None # ...else it would raise EINVAL + return "" # ...else it would raise EINVAL elif NETBSD or HAS_PROC_OPEN_FILES: # FreeBSD < 8 does not support functions based on # kinfo_getfile() and kinfo_getvmmap() - return cext.proc_cwd(self.pid) or None + return cext.proc_cwd(self.pid) else: raise NotImplementedError( "supported only starting from FreeBSD 8" if diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py index 8663de3cd..d44bf2d78 100644 --- a/psutil/_pssunos.py +++ b/psutil/_pssunos.py @@ -542,7 +542,7 @@ def cwd(self): return os.readlink("%s/%s/path/cwd" % (procfs_path, self.pid)) except FileNotFoundError: os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD - return None + return "" @wrap_exceptions def memory_info(self): diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c index c2de7c911..ff5fd72da 100644 --- a/psutil/_psutil_bsd.c +++ b/psutil/_psutil_bsd.c @@ -84,6 +84,7 @@ #include "arch/openbsd/disk.h" #include "arch/openbsd/mem.h" #include "arch/openbsd/proc.h" + #include "arch/openbsd/socks.h" #include #include // for VREG @@ -92,7 +93,10 @@ #undef _KERNEL #include // for CPUSTATES & CP_* #elif PSUTIL_NETBSD - #include "arch/netbsd/specific.h" + #include "arch/netbsd/cpu.h" + #include "arch/netbsd/disk.h" + #include "arch/netbsd/mem.h" + #include "arch/netbsd/proc.h" #include "arch/netbsd/socks.h" #include @@ -1016,11 +1020,11 @@ psutil_users(PyObject *self, PyObject *args) { goto error; py_tuple = Py_BuildValue( - "(OOOfO)", + "(OOOdO)", py_username, // username py_tty, // tty py_hostname, // hostname - (float)utx->ut_tv.tv_sec, // start time + (double)utx->ut_tv.tv_sec, // start time py_pid // process id ); @@ -1064,7 +1068,7 @@ static PyMethodDef mod_methods[] = { {"proc_name", psutil_proc_name, METH_VARARGS}, {"proc_oneshot_info", psutil_proc_oneshot_info, METH_VARARGS}, {"proc_threads", psutil_proc_threads, METH_VARARGS}, -#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_OPENBSD) +#if defined(PSUTIL_FREEBSD) {"proc_connections", psutil_proc_connections, METH_VARARGS}, #endif {"proc_cwd", psutil_proc_cwd, METH_VARARGS}, @@ -1093,6 +1097,7 @@ static PyMethodDef mod_methods[] = { {"cpu_times", psutil_cpu_times, METH_VARARGS}, {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS}, {"disk_partitions", psutil_disk_partitions, METH_VARARGS}, + {"net_connections", psutil_net_connections, METH_VARARGS}, {"net_io_counters", psutil_net_io_counters, METH_VARARGS}, {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS}, {"pids", psutil_pids, METH_VARARGS}, @@ -1102,9 +1107,6 @@ static PyMethodDef mod_methods[] = { #if defined(PSUTIL_FREEBSD) || defined(PSUTIL_OPENBSD) {"cpu_freq", psutil_cpu_freq, METH_VARARGS}, #endif -#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_NETBSD) - {"net_connections", psutil_net_connections, METH_VARARGS}, -#endif #if defined(PSUTIL_FREEBSD) {"sensors_battery", psutil_sensors_battery, METH_VARARGS}, {"sensors_cpu_temperature", psutil_sensors_cpu_temperature, METH_VARARGS}, diff --git a/psutil/_psutil_osx.c b/psutil/_psutil_osx.c index ed29b33b1..9ba0fd2b7 100644 --- a/psutil/_psutil_osx.c +++ b/psutil/_psutil_osx.c @@ -1152,6 +1152,7 @@ psutil_proc_connections(PyObject *self, PyObject *args) { // int fd, family, type, lport, rport, state; + // TODO: use INET6_ADDRSTRLEN instead of 200 char lip[200], rip[200]; int inseq; PyObject *py_family; diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index 2ed937ee9..11176de73 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -1560,6 +1560,7 @@ PsutilMethods[] = { {"disk_usage", psutil_disk_usage, METH_VARARGS}, {"getloadavg", (PyCFunction)psutil_get_loadavg, METH_VARARGS}, {"getpagesize", psutil_getpagesize, METH_VARARGS}, + {"swap_percent", psutil_swap_percent, METH_VARARGS}, {"init_loadavg_counter", (PyCFunction)psutil_init_loadavg_counter, METH_VARARGS}, {"net_connections", psutil_net_connections, METH_VARARGS}, {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS}, diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index 3802f3edb..4081aa17f 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -244,16 +244,22 @@ def swap_memory(): mem = cext.virtual_mem() total_phys = mem[0] - free_phys = mem[1] total_system = mem[2] - free_system = mem[3] # system memory (commit total/limit) is the sum of physical and swap # 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) + # commit total is incremented immediately (decrementing free_system) + # while the corresponding free physical value is not decremented until + # pages are accessed, so we can't use free system memory for swap. + # instead, we calculate page file usage based on performance counter + if (total > 0): + percentswap = cext.swap_percent() + used = int(0.01 * percentswap * total) + else: + used = 0 + free = total - used + percent = round(percentswap, 1) return _common.sswap(total, used, free, percent, 0, 0) diff --git a/psutil/arch/netbsd/cpu.c b/psutil/arch/netbsd/cpu.c new file mode 100644 index 000000000..33eb906f1 --- /dev/null +++ b/psutil/arch/netbsd/cpu.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include + + +/* +CPU related functions. Original code was refactored and moved from +psutil/arch/netbsd/specific.c in 2023 (and was moved in there previously +already) from cset 84219ad. For reference, here's the git history with +original(ish) implementations: +- per CPU times: 312442ad2a5b5d0c608476c5ab3e267735c3bc59 (Jan 2016) +- CPU stats: a991494e4502e1235ebc62b5ba450287d0dedec0 (Jan 2016) +*/ + + +PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + size_t size; + struct uvmexp_sysctl uv; + int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2}; + + size = sizeof(uv); + if (sysctl(uvmexp_mib, 2, &uv, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue( + "IIIIIII", + uv.swtch, // ctx switches + uv.intrs, // interrupts - XXX always 0, will be determined via /proc + uv.softs, // soft interrupts + uv.syscalls, // syscalls - XXX always 0 + uv.traps, // traps + uv.faults, // faults + uv.forks // forks + ); +} + + +PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + int mib[3]; + int ncpu; + size_t len; + size_t size; + int i; + PyObject *py_cputime = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + // retrieve the number of cpus + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + uint64_t cpu_time[CPUSTATES]; + + for (i = 0; i < ncpu; i++) { + // per-cpu info + mib[0] = CTL_KERN; + mib[1] = KERN_CP_TIME; + mib[2] = i; + size = sizeof(cpu_time); + if (sysctl(mib, 3, &cpu_time, &size, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + py_cputime = Py_BuildValue( + "(ddddd)", + (double)cpu_time[CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_INTR] / CLOCKS_PER_SEC + ); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + } + + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + return NULL; +} diff --git a/psutil/arch/netbsd/cpu.h b/psutil/arch/netbsd/cpu.h new file mode 100644 index 000000000..6c8610325 --- /dev/null +++ b/psutil/arch/netbsd/cpu.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +PyObject *psutil_cpu_stats(PyObject *self, PyObject *args); +PyObject *psutil_per_cpu_times(PyObject *self, PyObject *args); diff --git a/psutil/arch/netbsd/disk.c b/psutil/arch/netbsd/disk.c new file mode 100644 index 000000000..5481f65df --- /dev/null +++ b/psutil/arch/netbsd/disk.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* +Disk related functions. Original code was refactored and moved from +psutil/arch/netbsd/specific.c in 2023 (and was moved in there previously +already) from cset 84219ad. For reference, here's the git history with +original(ish) implementations: +- disk IO counters: 312442ad2a5b5d0c608476c5ab3e267735c3bc59 (Jan 2016) +*/ + +#include +#include +#include + + +PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + int i, dk_ndrive, mib[3]; + size_t len; + struct io_sysctl *stats = NULL; + PyObject *py_disk_info = NULL; + PyObject *py_retdict = PyDict_New(); + + if (py_retdict == NULL) + return NULL; + mib[0] = CTL_HW; + mib[1] = HW_IOSTATS; + mib[2] = sizeof(struct io_sysctl); + len = 0; + if (sysctl(mib, 3, NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + dk_ndrive = (int)(len / sizeof(struct io_sysctl)); + + stats = malloc(len); + if (stats == NULL) { + PyErr_NoMemory(); + goto error; + } + if (sysctl(mib, 3, stats, &len, NULL, 0) < 0 ) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < dk_ndrive; i++) { + py_disk_info = Py_BuildValue( + "(KKKK)", + stats[i].rxfer, + stats[i].wxfer, + stats[i].rbytes, + stats[i].wbytes + ); + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, stats[i].name, py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + + free(stats); + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (stats != NULL) + free(stats); + return NULL; +} diff --git a/psutil/arch/netbsd/disk.h b/psutil/arch/netbsd/disk.h new file mode 100644 index 000000000..77691c0df --- /dev/null +++ b/psutil/arch/netbsd/disk.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +PyObject *psutil_disk_io_counters(PyObject *self, PyObject *args); diff --git a/psutil/arch/netbsd/mem.c b/psutil/arch/netbsd/mem.c new file mode 100644 index 000000000..456479ba1 --- /dev/null +++ b/psutil/arch/netbsd/mem.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* +Memory related functions. Original code was refactored and moved from +psutil/arch/netbsd/specific.c in 2023 (and was moved in there previously +already) from cset 84219ad. For reference, here's the git history with +original(ish) implementations: +- virtual memory: 0749a69c01b374ca3e2180aaafc3c95e3b2d91b9 (Oct 2016) +- swap memory: 312442ad2a5b5d0c608476c5ab3e267735c3bc59 (Jan 2016) +*/ + +#include +#include +#include +#include + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + + +// Virtual memory stats, taken from: +// https://github.com/satterly/zabbix-stats/blob/master/src/libs/zbxsysinfo/ +// netbsd/memory.c +PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + size_t size; + struct uvmexp_sysctl uv; + int mib[] = {CTL_VM, VM_UVMEXP2}; + long long cached; + + size = sizeof(uv); + if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + // Note: zabbix does not include anonpages, but that doesn't match the + // "Cached" value in /proc/meminfo. + // https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/netbsd/memory.c#L182 + cached = (uv.filepages + uv.execpages + uv.anonpages) << uv.pageshift; + return Py_BuildValue( + "LLLLLL", + (long long) uv.npages << uv.pageshift, // total + (long long) uv.free << uv.pageshift, // free + (long long) uv.active << uv.pageshift, // active + (long long) uv.inactive << uv.pageshift, // inactive + (long long) uv.wired << uv.pageshift, // wired + cached // cached + ); +} + + +PyObject * +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) { + // This means there's no swap partition. + return Py_BuildValue("(iiiii)", 0, 0, 0, 0, 0); + } + + swdev = calloc(nswap, sizeof(*swdev)); + if (swdev == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if (swapctl(SWAP_STATS, swdev, nswap) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // Total things up. + swap_total = swap_free = 0; + for (i = 0; i < nswap; i++) { + if (swdev[i].se_flags & SWF_ENABLE) { + swap_total += (uint64_t)swdev[i].se_nblks * DEV_BSIZE; + swap_free += (uint64_t)(swdev[i].se_nblks - swdev[i].se_inuse) * DEV_BSIZE; + } + } + free(swdev); + + // Get swap in/out + unsigned int total; + size_t size = sizeof(total); + struct uvmexp_sysctl uv; + int mib[] = {CTL_VM, VM_UVMEXP2}; + size = sizeof(uv); + if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + return Py_BuildValue("(LLLll)", + swap_total, + (swap_total - swap_free), + swap_free, + (long) uv.pgswapin * pagesize, // swap in + (long) uv.pgswapout * pagesize); // swap out + +error: + free(swdev); + return NULL; +} diff --git a/psutil/arch/netbsd/mem.h b/psutil/arch/netbsd/mem.h new file mode 100644 index 000000000..1de3f3c43 --- /dev/null +++ b/psutil/arch/netbsd/mem.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); +PyObject *psutil_swap_mem(PyObject *self, PyObject *args); diff --git a/psutil/arch/netbsd/specific.c b/psutil/arch/netbsd/proc.c similarity index 59% rename from psutil/arch/netbsd/specific.c rename to psutil/arch/netbsd/proc.c index 11b8c1dd2..d4fa21266 100644 --- a/psutil/arch/netbsd/specific.c +++ b/psutil/arch/netbsd/proc.c @@ -7,39 +7,13 @@ * Platform-specific module methods for NetBSD. */ -#if defined(PSUTIL_NETBSD) - #define _KMEMUSER -#endif - #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include // for swap_mem -#include #include -// connection stuff -#include // for NI_MAXHOST -#include -#include // for CPUSTATES & CP_* -#define _KERNEL // for DTYPE_* - #include -#undef _KERNEL -#include // struct diskstats -#include -#include #include "../../_psutil_common.h" #include "../../_psutil_posix.h" -#include "specific.h" +#include "proc.h" #define PSUTIL_KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0) @@ -141,10 +115,13 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { ssize_t len = readlink(buf, path, sizeof(path) - 1); free(buf); if (len == -1) { - if (errno == ENOENT) - NoSuchProcess("readlink -> ENOENT"); - else + if (errno == ENOENT) { + psutil_debug("sysctl(KERN_PROC_CWD) -> ENOENT converted to ''"); + return Py_BuildValue("s", ""); + } + else { PyErr_SetFromErrno(PyExc_OSError); + } return NULL; } path[len] = '\0'; @@ -273,6 +250,10 @@ psutil_proc_threads(PyObject *self, PyObject *args) { nlwps = (int)(size / sizeof(struct kinfo_lwp)); for (i = 0; i < nlwps; i++) { + if ((&kl[i])->l_stat == LSIDL || (&kl[i])->l_stat == LSZOMB) + continue; + // XXX: we return 2 "user" times because the struct does not provide + // any "system" time. py_tuple = Py_BuildValue("idd", (&kl[i])->l_lid, PSUTIL_KPT2DOUBLE((&kl[i])->l_rtime), @@ -295,10 +276,6 @@ psutil_proc_threads(PyObject *self, PyObject *args) { } -// ============================================================================ -// APIS -// ============================================================================ - int psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) { // Returns a list of all BSD processes on the system. This routine @@ -435,96 +412,6 @@ psutil_get_cmdline(pid_t pid) { } -/* - * Virtual memory stats, taken from: - * https://github.com/satterly/zabbix-stats/blob/master/src/libs/zbxsysinfo/ - * netbsd/memory.c - */ -PyObject * -psutil_virtual_mem(PyObject *self, PyObject *args) { - size_t size; - struct uvmexp_sysctl uv; - int mib[] = {CTL_VM, VM_UVMEXP2}; - long pagesize = psutil_getpagesize(); - - size = sizeof(uv); - if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - - return Py_BuildValue("KKKKKKKK", - (unsigned long long) uv.npages << uv.pageshift, // total - (unsigned long long) uv.free << uv.pageshift, // free - (unsigned long long) uv.active << uv.pageshift, // active - (unsigned long long) uv.inactive << uv.pageshift, // inactive - (unsigned long long) uv.wired << uv.pageshift, // wired - (unsigned long long) (uv.filepages + uv.execpages) * pagesize, // cached - // These are determined from /proc/meminfo in Python. - (unsigned long long) 0, // buffers - (unsigned long long) 0 // shared - ); -} - - -PyObject * -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) { - // This means there's no swap partition. - return Py_BuildValue("(iiiii)", 0, 0, 0, 0, 0); - } - - swdev = calloc(nswap, sizeof(*swdev)); - if (swdev == NULL) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - - if (swapctl(SWAP_STATS, swdev, nswap) == -1) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - // Total things up. - swap_total = swap_free = 0; - for (i = 0; i < nswap; i++) { - if (swdev[i].se_flags & SWF_ENABLE) { - swap_total += (uint64_t)swdev[i].se_nblks * DEV_BSIZE; - swap_free += (uint64_t)(swdev[i].se_nblks - swdev[i].se_inuse) * DEV_BSIZE; - } - } - free(swdev); - - // Get swap in/out - unsigned int total; - size_t size = sizeof(total); - struct uvmexp_sysctl uv; - int mib[] = {CTL_VM, VM_UVMEXP2}; - size = sizeof(uv); - if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - return Py_BuildValue("(LLLll)", - swap_total, - (swap_total - swap_free), - swap_free, - (long) uv.pgswapin * pagesize, // swap in - (long) uv.pgswapout * pagesize); // swap out - -error: - free(swdev); - return NULL; -} - - PyObject * psutil_proc_num_fds(PyObject *self, PyObject *args) { long pid; @@ -545,144 +432,3 @@ psutil_proc_num_fds(PyObject *self, PyObject *args) { return Py_BuildValue("i", cnt); } - - -PyObject * -psutil_per_cpu_times(PyObject *self, PyObject *args) { - // XXX: why static? - int mib[3]; - int ncpu; - size_t len; - size_t size; - int i; - PyObject *py_cputime = NULL; - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; - // retrieve the number of cpus - mib[0] = CTL_HW; - mib[1] = HW_NCPU; - len = sizeof(ncpu); - if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - uint64_t cpu_time[CPUSTATES]; - - for (i = 0; i < ncpu; i++) { - // per-cpu info - mib[0] = CTL_KERN; - mib[1] = KERN_CP_TIME; - mib[2] = i; - size = sizeof(cpu_time); - if (sysctl(mib, 3, &cpu_time, &size, NULL, 0) == -1) { - warn("failed to get kern.cptime2"); - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - - py_cputime = Py_BuildValue( - "(ddddd)", - (double)cpu_time[CP_USER] / CLOCKS_PER_SEC, - (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC, - (double)cpu_time[CP_SYS] / CLOCKS_PER_SEC, - (double)cpu_time[CP_IDLE] / CLOCKS_PER_SEC, - (double)cpu_time[CP_INTR] / CLOCKS_PER_SEC); - if (!py_cputime) - goto error; - if (PyList_Append(py_retlist, py_cputime)) - goto error; - Py_DECREF(py_cputime); - } - - return py_retlist; - -error: - Py_XDECREF(py_cputime); - Py_DECREF(py_retlist); - return NULL; -} - - -PyObject * -psutil_disk_io_counters(PyObject *self, PyObject *args) { - int i, dk_ndrive, mib[3]; - size_t len; - struct io_sysctl *stats = NULL; - PyObject *py_disk_info = NULL; - PyObject *py_retdict = PyDict_New(); - - if (py_retdict == NULL) - return NULL; - mib[0] = CTL_HW; - mib[1] = HW_IOSTATS; - mib[2] = sizeof(struct io_sysctl); - len = 0; - if (sysctl(mib, 3, NULL, &len, NULL, 0) < 0) { - warn("can't get HW_IOSTATS"); - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - dk_ndrive = (int)(len / sizeof(struct io_sysctl)); - - stats = malloc(len); - if (stats == NULL) { - PyErr_NoMemory(); - goto error; - } - if (sysctl(mib, 3, stats, &len, NULL, 0) < 0 ) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - for (i = 0; i < dk_ndrive; i++) { - py_disk_info = Py_BuildValue( - "(KKKK)", - stats[i].rxfer, - stats[i].wxfer, - stats[i].rbytes, - stats[i].wbytes - ); - if (!py_disk_info) - goto error; - if (PyDict_SetItemString(py_retdict, stats[i].name, py_disk_info)) - goto error; - Py_DECREF(py_disk_info); - } - - free(stats); - return py_retdict; - -error: - Py_XDECREF(py_disk_info); - Py_DECREF(py_retdict); - if (stats != NULL) - free(stats); - return NULL; -} - - -PyObject * -psutil_cpu_stats(PyObject *self, PyObject *args) { - size_t size; - struct uvmexp_sysctl uv; - int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2}; - - size = sizeof(uv); - if (sysctl(uvmexp_mib, 2, &uv, &size, NULL, 0) < 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - - return Py_BuildValue( - "IIIIIII", - uv.swtch, // ctx switches - uv.intrs, // interrupts - XXX always 0, will be determined via /proc - uv.softs, // soft interrupts - uv.syscalls, // syscalls - XXX always 0 - uv.traps, // traps - uv.faults, // faults - uv.forks // forks - ); -} diff --git a/psutil/arch/netbsd/specific.h b/psutil/arch/netbsd/proc.h similarity index 74% rename from psutil/arch/netbsd/specific.h rename to psutil/arch/netbsd/proc.h index 391ed164a..b4c88851f 100644 --- a/psutil/arch/netbsd/specific.h +++ b/psutil/arch/netbsd/proc.h @@ -17,13 +17,8 @@ char *psutil_get_cmd_args(pid_t pid, size_t *argsize); // PyObject *psutil_get_cmdline(pid_t pid); PyObject *psutil_proc_threads(PyObject *self, PyObject *args); -PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); -PyObject *psutil_swap_mem(PyObject *self, PyObject *args); PyObject *psutil_proc_num_fds(PyObject *self, PyObject *args); PyObject *psutil_proc_connections(PyObject *self, PyObject *args); -PyObject *psutil_per_cpu_times(PyObject *self, PyObject *args); -PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); PyObject* psutil_proc_exe(PyObject* self, PyObject* args); PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args); -PyObject* psutil_cpu_stats(PyObject* self, PyObject* args); PyObject *psutil_proc_cwd(PyObject *self, PyObject *args); diff --git a/psutil/arch/openbsd/proc.c b/psutil/arch/openbsd/proc.c index 35acab252..285467bf6 100644 --- a/psutil/arch/openbsd/proc.c +++ b/psutil/arch/openbsd/proc.c @@ -6,25 +6,12 @@ */ #include -#include -#include -#include -#include -#include #include #include #include #include -#include #include -#include #include -#include // for NI_MAXHOST -#include -#define _KERNEL // for DTYPE_* -#include -#undef _KERNEL -#include // for inet_ntoa() #include "../../_psutil_common.h" #include "../../_psutil_posix.h" @@ -155,6 +142,7 @@ psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount) { } +// TODO: refactor this (it's clunky) static char ** _psutil_get_argv(pid_t pid) { static char **argv; @@ -317,217 +305,14 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { int name[] = { CTL_KERN, KERN_PROC_CWD, pid }; if (sysctl(name, 3, path, &pathlen, NULL, 0) != 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - return PyUnicode_DecodeFSDefault(path); -} - - -// see sys/kern/kern_sysctl.c lines 1100 and -// usr.bin/fstat/fstat.c print_inet_details() -static char * -psutil_convert_ipv4(int family, uint32_t addr[4]) { - struct in_addr a; - memcpy(&a, addr, sizeof(a)); - return inet_ntoa(a); -} - - -static char * -psutil_inet6_addrstr(struct in6_addr *p) -{ - struct sockaddr_in6 sin6; - static char hbuf[NI_MAXHOST]; - const int niflags = NI_NUMERICHOST; - - memset(&sin6, 0, sizeof(sin6)); - sin6.sin6_family = AF_INET6; - sin6.sin6_len = sizeof(struct sockaddr_in6); - sin6.sin6_addr = *p; - if (IN6_IS_ADDR_LINKLOCAL(p) && - *(u_int16_t *)&sin6.sin6_addr.s6_addr[2] != 0) { - sin6.sin6_scope_id = - ntohs(*(u_int16_t *)&sin6.sin6_addr.s6_addr[2]); - sin6.sin6_addr.s6_addr[2] = sin6.sin6_addr.s6_addr[3] = 0; - } - - if (getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len, - hbuf, sizeof(hbuf), NULL, 0, niflags)) - return "invalid"; - - return hbuf; -} - - -/* - * List process connections. - * Note: there is no net_connections() on OpenBSD. The Python - * implementation will iterate over all processes and use this - * function. - * Note: local and remote paths cannot be determined for UNIX sockets. - */ -PyObject * -psutil_proc_connections(PyObject *self, PyObject *args) { - pid_t pid; - int i; - int cnt; - struct kinfo_file *freep = NULL; - struct kinfo_file *kif; - char *tcplist = NULL; - PyObject *py_retlist = PyList_New(0); - PyObject *py_tuple = NULL; - PyObject *py_laddr = NULL; - PyObject *py_raddr = NULL; - PyObject *py_af_filter = NULL; - PyObject *py_type_filter = NULL; - PyObject *py_family = NULL; - PyObject *_type = NULL; - - if (py_retlist == NULL) - return NULL; - if (! PyArg_ParseTuple(args, _Py_PARSE_PID "OO", &pid, &py_af_filter, - &py_type_filter)) - goto error; - if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { - PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); - goto error; - } - - freep = kinfo_getfile(pid, &cnt); - if (freep == NULL) { - goto error; - } - - for (i = 0; i < cnt; i++) { - int state; - int lport; - int rport; - char addrbuf[NI_MAXHOST + 2]; - int inseq; - struct in6_addr laddr6; - py_tuple = NULL; - py_laddr = NULL; - py_raddr = NULL; - - kif = &freep[i]; - if (kif->f_type == DTYPE_SOCKET) { - // apply filters - py_family = PyLong_FromLong((long)kif->so_family); - inseq = PySequence_Contains(py_af_filter, py_family); - Py_DECREF(py_family); - if (inseq == 0) - continue; - _type = PyLong_FromLong((long)kif->so_type); - inseq = PySequence_Contains(py_type_filter, _type); - Py_DECREF(_type); - if (inseq == 0) - continue; - - // IPv4 / IPv6 socket - if ((kif->so_family == AF_INET) || (kif->so_family == AF_INET6)) { - // fill status - if (kif->so_type == SOCK_STREAM) - state = kif->t_state; - else - state = PSUTIL_CONN_NONE; - - // ports - lport = ntohs(kif->inp_lport); - rport = ntohs(kif->inp_fport); - - // local address, IPv4 - if (kif->so_family == AF_INET) { - py_laddr = Py_BuildValue( - "(si)", - psutil_convert_ipv4(kif->so_family, kif->inp_laddru), - lport); - if (!py_laddr) - goto error; - } - else { - // local address, IPv6 - memcpy(&laddr6, kif->inp_laddru, sizeof(laddr6)); - snprintf(addrbuf, sizeof(addrbuf), "%s", - psutil_inet6_addrstr(&laddr6)); - py_laddr = Py_BuildValue("(si)", addrbuf, lport); - if (!py_laddr) - goto error; - } - - if (rport != 0) { - // remote address, IPv4 - if (kif->so_family == AF_INET) { - py_raddr = Py_BuildValue( - "(si)", - psutil_convert_ipv4( - kif->so_family, kif->inp_faddru), - rport); - } - else { - // remote address, IPv6 - memcpy(&laddr6, kif->inp_faddru, sizeof(laddr6)); - snprintf(addrbuf, sizeof(addrbuf), "%s", - psutil_inet6_addrstr(&laddr6)); - py_raddr = Py_BuildValue("(si)", addrbuf, rport); - if (!py_raddr) - goto error; - } - } - else { - py_raddr = Py_BuildValue("()"); - } - - if (!py_raddr) - goto error; - py_tuple = Py_BuildValue( - "(iiiNNi)", - kif->fd_fd, - kif->so_family, - kif->so_type, - py_laddr, - py_raddr, - state); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_DECREF(py_tuple); - } - // UNIX socket. - // XXX: local addr is supposed to be in "unp_path" but it - // always empty; also "fstat" command is not able to show - // UNIX socket paths. - else if (kif->so_family == AF_UNIX) { - py_tuple = Py_BuildValue( - "(iiissi)", - kif->fd_fd, - kif->so_family, - kif->so_type, - "", // laddr (kif->unp_path is empty) - "", // raddr - PSUTIL_CONN_NONE); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_DECREF(py_tuple); - Py_INCREF(Py_None); - } + if (errno == ENOENT) { + psutil_debug("sysctl(KERN_PROC_CWD) -> ENOENT converted to ''"); + return Py_BuildValue("s", ""); + } + else { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; } } - free(freep); - free(tcplist); - return py_retlist; - -error: - Py_XDECREF(py_tuple); - Py_XDECREF(py_laddr); - Py_XDECREF(py_raddr); - Py_DECREF(py_retlist); - if (freep != NULL) - free(freep); - if (tcplist != NULL) - free(tcplist); - return NULL; + return PyUnicode_DecodeFSDefault(path); } diff --git a/psutil/arch/openbsd/proc.h b/psutil/arch/openbsd/proc.h index 5ca5890d2..747507dd4 100644 --- a/psutil/arch/openbsd/proc.h +++ b/psutil/arch/openbsd/proc.h @@ -18,5 +18,3 @@ PyObject *psutil_get_cmdline(pid_t pid); PyObject *psutil_proc_threads(PyObject *self, PyObject *args); PyObject *psutil_proc_num_fds(PyObject *self, PyObject *args); PyObject *psutil_proc_cwd(PyObject *self, PyObject *args); -PyObject *psutil_proc_connections(PyObject *self, PyObject *args); - diff --git a/psutil/arch/openbsd/socks.c b/psutil/arch/openbsd/socks.c new file mode 100644 index 000000000..69daa447b --- /dev/null +++ b/psutil/arch/openbsd/socks.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#define _KERNEL // silence compiler warning +#include // DTYPE_SOCKET +#include // INET6_ADDRSTRLEN, in6_addr +#undef _KERNEL + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + + +PyObject * +psutil_net_connections(PyObject *self, PyObject *args) { + pid_t pid; + int i; + int cnt; + int state; + int lport; + int rport; + char lip[INET6_ADDRSTRLEN]; + char rip[INET6_ADDRSTRLEN]; + int inseq; + + char errbuf[_POSIX2_LINE_MAX]; + kvm_t *kd = NULL; + + struct kinfo_file *kif; + struct kinfo_file *ikf; + struct in6_addr laddr6; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + PyObject *py_lpath = NULL; + PyObject *py_af_filter = NULL; + PyObject *py_type_filter = NULL; + PyObject *py_family = NULL; + PyObject *_type = NULL; + + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, _Py_PARSE_PID "OO", &pid, &py_af_filter, + &py_type_filter)) { + goto error; + } + if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + goto error; + } + + kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + if (! kd) { + convert_kvm_err("kvm_openfiles", errbuf); + goto error; + } + + ikf = kvm_getfiles(kd, KERN_FILE_BYPID, -1, sizeof(*ikf), &cnt); + if (! ikf) { + PyErr_SetFromOSErrnoWithSyscall("kvm_getfiles"); + goto error; + } + + for (int i = 0; i < cnt; i++) { + const struct kinfo_file *kif = ikf + i; + py_tuple = NULL; + py_laddr = NULL; + py_raddr = NULL; + py_lpath = NULL; + + // apply filters + if (kif->f_type != DTYPE_SOCKET) + continue; + if (pid != -1 && kif->p_pid != (uint32_t)pid) + continue; + py_family = PyLong_FromLong((long)kif->so_family); + inseq = PySequence_Contains(py_af_filter, py_family); + Py_DECREF(py_family); + if (inseq == 0) + continue; + _type = PyLong_FromLong((long)kif->so_type); + inseq = PySequence_Contains(py_type_filter, _type); + Py_DECREF(_type); + if (inseq == 0) + continue; + + // IPv4 / IPv6 socket + if ((kif->so_family == AF_INET) || (kif->so_family == AF_INET6)) { + // status + if (kif->so_type == SOCK_STREAM) + state = kif->t_state; + else + state = PSUTIL_CONN_NONE; + + // local & remote port + lport = ntohs(kif->inp_lport); + rport = ntohs(kif->inp_fport); + + // local addr + inet_ntop(kif->so_family, &kif->inp_laddru, lip, sizeof(lip)); + py_laddr = Py_BuildValue("(si)", lip, lport); + if (! py_laddr) + goto error; + + // remote addr + if (rport != 0) { + inet_ntop(kif->so_family, &kif->inp_faddru, rip, sizeof(rip)); + py_raddr = Py_BuildValue("(si)", rip, rport); + } + else { + py_raddr = Py_BuildValue("()"); + } + if (! py_raddr) + goto error; + + // populate tuple and list + py_tuple = Py_BuildValue( + "(iiiNNil)", + kif->fd_fd, + kif->so_family, + kif->so_type, + py_laddr, + py_raddr, + state, + kif->p_pid + ); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + // UNIX socket + else if (kif->so_family == AF_UNIX) { + py_lpath = PyUnicode_DecodeFSDefault(kif->unp_path); + if (! py_lpath) + goto error; + + py_tuple = Py_BuildValue( + "(iiiOsil)", + kif->fd_fd, + kif->so_family, + kif->so_type, + py_lpath, + "", // raddr + PSUTIL_CONN_NONE, + kif->p_pid + ); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_lpath); + Py_DECREF(py_tuple); + Py_INCREF(Py_None); + } + } + + kvm_close(kd); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + Py_DECREF(py_retlist); + if (kd != NULL) + kvm_close(kd); + return NULL; +} diff --git a/psutil/arch/openbsd/socks.h b/psutil/arch/openbsd/socks.h new file mode 100644 index 000000000..90b678bbb --- /dev/null +++ b/psutil/arch/openbsd/socks.h @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +PyObject *psutil_net_connections(PyObject* self, PyObject* args); diff --git a/psutil/arch/windows/mem.c b/psutil/arch/windows/mem.c index 18b535e6a..24dc15ad0 100644 --- a/psutil/arch/windows/mem.c +++ b/psutil/arch/windows/mem.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "../../_psutil_common.h" @@ -41,3 +42,53 @@ psutil_virtual_mem(PyObject *self, PyObject *args) { totalSys, availSys); } + + +// Return a float representing the percent usage of all paging files on +// the system. +PyObject * +psutil_swap_percent(PyObject *self, PyObject *args) { + WCHAR *szCounterPath = L"\\Paging File(_Total)\\% Usage"; + PDH_STATUS s; + HQUERY hQuery; + HCOUNTER hCounter; + PDH_FMT_COUNTERVALUE counterValue; + double percentUsage; + + 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) { + PdhCloseQuery(hQuery); + PyErr_Format( + PyExc_RuntimeError, + "PdhAddEnglishCounterW failed. Performance counters may be disabled." + ); + return NULL; + } + + s = PdhCollectQueryData(hQuery); + if (s != ERROR_SUCCESS) { + // If swap disabled this will fail. + psutil_debug("PdhCollectQueryData failed; assume swap percent is 0"); + percentUsage = 0; + } + else { + s = PdhGetFormattedCounterValue( + (PDH_HCOUNTER)hCounter, PDH_FMT_DOUBLE, 0, &counterValue); + if (s != ERROR_SUCCESS) { + PdhCloseQuery(hQuery); + PyErr_Format( + PyExc_RuntimeError, "PdhGetFormattedCounterValue failed"); + return NULL; + } + percentUsage = counterValue.doubleValue; + } + + PdhRemoveCounter(hCounter); + PdhCloseQuery(hQuery); + return Py_BuildValue("d", percentUsage); +} diff --git a/psutil/arch/windows/mem.h b/psutil/arch/windows/mem.h index a10781dfc..48d3dadee 100644 --- a/psutil/arch/windows/mem.h +++ b/psutil/arch/windows/mem.h @@ -8,3 +8,4 @@ PyObject *psutil_getpagesize(PyObject *self, PyObject *args); PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); +PyObject *psutil_swap_percent(PyObject *self, PyObject *args); diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index e82bb38d8..e6f0abc35 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -836,7 +836,7 @@ def create_exe(outpath, c_code=None): assert not os.path.exists(outpath), outpath if c_code: if not which("gcc"): - raise ValueError("gcc is not installed") + raise unittest.SkipTest("gcc is not installed") if isinstance(c_code, bool): # c_code is True c_code = textwrap.dedent( """ diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py index 29ea384d5..69f50732b 100755 --- a/psutil/tests/test_bsd.py +++ b/psutil/tests/test_bsd.py @@ -127,10 +127,12 @@ def test_cpu_count_logical(self): self.assertEqual(psutil.cpu_count(logical=True), syst) @unittest.skipIf(not which('sysctl'), "sysctl cmd not available") + @unittest.skipIf(NETBSD, "skipped on NETBSD") # we check /proc/meminfo def test_virtual_memory_total(self): num = sysctl('hw.physmem') self.assertEqual(num, psutil.virtual_memory().total) + @unittest.skipIf(not which('ifconfig'), "ifconfig cmd not available") def test_net_if_stats(self): for name, stats in psutil.net_if_stats().items(): try: @@ -507,6 +509,8 @@ def parse_meminfo(look_for): return int(line.split()[1]) * 1024 raise ValueError("can't find %s" % look_for) + # --- virtual mem + def test_vmem_total(self): self.assertEqual( psutil.virtual_memory().total, self.parse_meminfo("MemTotal:")) @@ -526,6 +530,13 @@ def test_vmem_shared(self): psutil.virtual_memory().shared, self.parse_meminfo("MemShared:"), delta=TOLERANCE_SYS_MEM) + def test_vmem_cached(self): + self.assertAlmostEqual( + psutil.virtual_memory().cached, self.parse_meminfo("Cached:"), + delta=TOLERANCE_SYS_MEM) + + # --- swap mem + def test_swapmem_total(self): self.assertAlmostEqual( psutil.swap_memory().total, self.parse_meminfo("SwapTotal:"), @@ -540,6 +551,8 @@ def test_swapmem_used(self): smem = psutil.swap_memory() self.assertEqual(smem.used, smem.total - smem.free) + # --- others + def test_cpu_stats_interrupts(self): with open('/proc/stat', 'rb') as f: for line in f: diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py index d47233bc8..ad615ed07 100755 --- a/psutil/tests/test_connections.py +++ b/psutil/tests/test_connections.py @@ -143,11 +143,7 @@ def check_socket(self, sock): laddr = laddr.decode() if sock.family == AF_INET6: laddr = laddr[:2] - if sock.family == AF_UNIX and OPENBSD: - # No addresses are set for UNIX sockets on OpenBSD. - pass - else: - self.assertEqual(conn.laddr, laddr) + self.assertEqual(conn.laddr, laddr) # XXX Solaris can't retrieve system-wide UNIX sockets if sock.family == AF_UNIX and HAS_CONNECTIONS_UNIX: @@ -243,22 +239,16 @@ def test_unix(self): # a UNIX connection to /var/run/log. cons = [c for c in cons if c.raddr != '/var/run/log'] self.assertEqual(len(cons), 2, msg=cons) - if LINUX or FREEBSD or SUNOS: + if LINUX or FREEBSD or SUNOS or OPENBSD: # remote path is never set self.assertEqual(cons[0].raddr, "") self.assertEqual(cons[1].raddr, "") # one local address should though self.assertEqual(testfn, cons[0].laddr or cons[1].laddr) - elif OPENBSD: - # No addresses whatsoever here. - for addr in (cons[0].laddr, cons[0].raddr, - cons[1].laddr, cons[1].raddr): - self.assertEqual(addr, "") else: # On other systems either the laddr or raddr # of both peers are set. self.assertEqual(cons[0].laddr or cons[1].laddr, testfn) - self.assertEqual(cons[0].raddr or cons[1].raddr, testfn) finally: server.close() client.close() diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index b767e3ebf..7f299a624 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -258,8 +258,8 @@ def test_disk_partitions(self): 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) + self.assertIsInstance(disk.maxfile, (int, type(None))) + self.assertIsInstance(disk.maxpath, (int, type(None))) @unittest.skipIf(SKIP_SYSCONS, "requires root") def test_net_connections(self): @@ -438,8 +438,7 @@ def test_all(self): name, info['pid'], repr(value)) s += '-' * 70 s += "\n%s" % traceback.format_exc() - s = "\n".join((" " * 4) + i for i in s.splitlines()) - s += '\n' + s = "\n".join((" " * 4) + i for i in s.splitlines()) + "\n" failures.append(s) else: if value not in (0, 0.0, [], None, '', {}): @@ -453,10 +452,9 @@ def cmdline(self, ret, info): self.assertIsInstance(part, str) def exe(self, ret, info): - self.assertIsInstance(ret, (str, unicode, type(None))) - if not ret: - self.assertEqual(ret, '') - else: + self.assertIsInstance(ret, (str, unicode)) + self.assertEqual(ret.strip(), ret) + if ret: if WINDOWS and not ret.endswith('.exe'): return # May be "Registry", "MemCompression", ... assert os.path.isabs(ret), ret @@ -521,7 +519,8 @@ def gids(self, ret, info): def username(self, ret, info): self.assertIsInstance(ret, str) - assert ret + self.assertEqual(ret.strip(), ret) + assert ret.strip() def status(self, ret, info): self.assertIsInstance(ret, str) @@ -620,6 +619,7 @@ def open_files(self, ret, info): for f in ret: self.assertIsInstance(f.fd, int) self.assertIsInstance(f.path, str) + self.assertEqual(f.path.strip(), f.path) if WINDOWS: self.assertEqual(f.fd, -1) elif LINUX: @@ -652,8 +652,9 @@ def connections(self, ret, info): check_connection_ntuple(conn) def cwd(self, ret, info): - if ret: # 'ret' can be None or empty - self.assertIsInstance(ret, str) + self.assertIsInstance(ret, (str, unicode)) + self.assertEqual(ret.strip(), ret) + if ret: assert os.path.isabs(ret), ret try: st = os.stat(ret) diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py index a37899fde..8ea6cf7c2 100755 --- a/psutil/tests/test_posix.py +++ b/psutil/tests/test_posix.py @@ -115,7 +115,10 @@ def ps_args(pid): field = "command" if AIX or SUNOS: field = "args" - return ps(field, pid) + out = ps(field, pid) + # observed on BSD + Github CI: '/usr/local/bin/python3 -E -O (python3.9)' + out = re.sub(r"\(python.*?\)$", "", out) + return out.strip() def ps_rss(pid): @@ -347,11 +350,8 @@ def test_users(self): for idx, u in enumerate(psutil.users()): self.assertEqual(u.name, users[idx]) self.assertEqual(u.terminal, terminals[idx]) - p = psutil.Process(u.pid) - # on macOS time is off by ~47 secs for some reason, but - # the next test against 'who' CLI succeeds - delta = 60 if MACOS else 1 - self.assertAlmostEqual(u.started, p.create_time(), delta=delta) + if u.pid is not None: # None on OpenBSD + psutil.Process(u.pid) @retry_on_failure() def test_users_started(self): @@ -415,7 +415,12 @@ def test_os_waitpid_bad_ret_status(self): @retry_on_failure() def test_disk_usage(self): def df(device): - out = sh("df -k %s" % device).strip() + try: + out = sh("df -k %s" % device).strip() + except RuntimeError as err: + if "device busy" in str(err).lower(): + raise self.skipTest("df returned EBUSY") + raise line = out.split('\n')[1] fields = line.split() total = int(fields[1]) * 1024 diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index ec15ffda5..b3cf9ff54 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -159,6 +159,7 @@ def test_wait_exited(self): self.assertEqual(code, 5) self.assertProcessGone(p) + @unittest.skipIf(NETBSD, "fails on NETBSD") def test_wait_stopped(self): p = self.spawn_psproc() if POSIX: @@ -732,7 +733,15 @@ def test_long_cmdline(self): create_exe(testfn) cmdline = [testfn] + (["0123456789"] * 20) p = self.spawn_psproc(cmdline) - self.assertEqual(p.cmdline(), cmdline) + if OPENBSD: + # XXX: for some reason the test process may turn into a + # zombie (don't know why). + try: + self.assertEqual(p.cmdline(), cmdline) + except psutil.ZombieProcess: + raise self.skipTest("OPENBSD: process turned into zombie") + else: + self.assertEqual(p.cmdline(), cmdline) def test_name(self): p = self.spawn_psproc(PYTHON_EXE) @@ -745,7 +754,23 @@ def test_long_name(self): testfn = self.get_testfn(suffix="0123456789" * 2) create_exe(testfn) p = self.spawn_psproc(testfn) - self.assertEqual(p.name(), os.path.basename(testfn)) + if OPENBSD: + # XXX: for some reason the test process may turn into a + # zombie (don't know why). Because the name() is long, all + # UNIX kernels truncate it to 15 chars, so internally psutil + # tries to guess the full name() from the cmdline(). But the + # cmdline() of a zombie on OpenBSD fails (internally), so we + # just compare the first 15 chars. Full explanation: + # https://github.com/giampaolo/psutil/issues/2239 + try: + self.assertEqual(p.name(), os.path.basename(testfn)) + except AssertionError: + if p.status() == psutil.STATUS_ZOMBIE: + assert os.path.basename(testfn).startswith(p.name()) + else: + raise + else: + self.assertEqual(p.name(), os.path.basename(testfn)) # XXX @unittest.skipIf(SUNOS, "broken on SUNOS") diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index c2b1df847..414c86e99 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -187,7 +187,7 @@ def test_pid_exists_2(self): # if it is no longer in psutil.pids() time.sleep(.1) self.assertNotIn(pid, psutil.pids()) - pids = range(max(pids) + 5000, max(pids) + 6000) + pids = range(max(pids) + 15000, max(pids) + 16000) for pid in pids: self.assertFalse(psutil.pid_exists(pid), msg=pid) diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py index 43cf2b49b..c7d8dfbc0 100755 --- a/psutil/tests/test_unicode.py +++ b/psutil/tests/test_unicode.py @@ -82,7 +82,6 @@ import psutil from psutil import BSD -from psutil import OPENBSD from psutil import POSIX from psutil import WINDOWS from psutil._compat import PY3 @@ -257,9 +256,7 @@ def test_proc_connections(self): with closing(sock): conn = psutil.Process().connections('unix')[0] self.assertIsInstance(conn.laddr, str) - # AF_UNIX addr not set on OpenBSD - if not OPENBSD: # XXX - self.assertEqual(conn.laddr, name) + self.assertEqual(conn.laddr, name) @unittest.skipIf(not POSIX, "POSIX only") @unittest.skipIf(not HAS_CONNECTIONS_UNIX, "can't list UNIX sockets") @@ -281,11 +278,9 @@ def find_sock(cons): raise unittest.SkipTest("not supported") with closing(sock): cons = psutil.net_connections(kind='unix') - # AF_UNIX addr not set on OpenBSD - if not OPENBSD: - conn = find_sock(cons) - self.assertIsInstance(conn.laddr, str) - self.assertEqual(conn.laddr, name) + conn = find_sock(cons) + self.assertIsInstance(conn.laddr, str) + self.assertEqual(conn.laddr, name) def test_disk_usage(self): dname = self.funky_name + "2" diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py index 9b163a185..a9f689336 100755 --- a/psutil/tests/test_windows.py +++ b/psutil/tests/test_windows.py @@ -162,6 +162,27 @@ def test_free_phymem(self): int(w.AvailableBytes), psutil.virtual_memory().free, delta=TOLERANCE_SYS_MEM) + def test_total_swapmem(self): + w = wmi.WMI().Win32_PerfRawData_PerfOS_Memory()[0] + self.assertEqual(int(w.CommitLimit) - psutil.virtual_memory().total, + psutil.swap_memory().total) + if (psutil.swap_memory().total == 0): + self.assertEqual(0, psutil.swap_memory().free) + self.assertEqual(0, psutil.swap_memory().used) + + def test_percent_swapmem(self): + if (psutil.swap_memory().total > 0): + w = wmi.WMI().Win32_PerfRawData_PerfOS_PagingFile( + Name="_Total")[0] + # calculate swap usage to percent + percentSwap = int(w.PercentUsage) * 100 / int(w.PercentUsage_Base) + # exact percent may change but should be reasonable + # assert within +/- 5% and between 0 and 100% + self.assertGreaterEqual(psutil.swap_memory().percent, 0) + self.assertAlmostEqual(psutil.swap_memory().percent, percentSwap, + delta=5) + self.assertLessEqual(psutil.swap_memory().percent, 100) + # @unittest.skipIf(wmi is None, "wmi module is not installed") # def test__UPTIME(self): # # _UPTIME constant is not public but it is used internally diff --git a/setup.py b/setup.py index a4f719b2b..9fda1b303 100755 --- a/setup.py +++ b/setup.py @@ -275,6 +275,7 @@ def get_winver(): 'psutil/arch/openbsd/disk.c', 'psutil/arch/openbsd/mem.c', 'psutil/arch/openbsd/proc.c', + 'psutil/arch/openbsd/socks.c', ], define_macros=macros, libraries=["kvm"], @@ -286,7 +287,10 @@ def get_winver(): 'psutil._psutil_bsd', sources=sources + [ 'psutil/_psutil_bsd.c', - 'psutil/arch/netbsd/specific.c', + 'psutil/arch/netbsd/cpu.c', + 'psutil/arch/netbsd/disk.c', + 'psutil/arch/netbsd/mem.c', + 'psutil/arch/netbsd/proc.c', 'psutil/arch/netbsd/socks.c', ], define_macros=macros, From 5203932d8bcc3e3f8134bdcb4e6673084e757a39 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 17 Apr 2023 17:06:45 +0200 Subject: [PATCH 2/2] update HISTORY --- HISTORY.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 003dad239..fec64c0b2 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -23,6 +23,8 @@ its truncated `Process.name()`_ (15 chars), don't fail with `ZombieProcess`_ when we try to guess the full name from the `Process.cmdline()`_. Just return the truncated name. +- 2240_, [NetBSD], [OpenBSD]: add CI testing on every commit for NetBSD and + OpenBSD platforms (python 3 only). **Bug fixes**