]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-150114: Log the memory usage in regrtest on FreeBSD (#150280)
authorVictor Stinner <vstinner@python.org>
Mon, 25 May 2026 13:45:55 +0000 (15:45 +0200)
committerGitHub <noreply@github.com>
Mon, 25 May 2026 13:45:55 +0000 (13:45 +0000)
Add _testcapi.get_process_memory_usage().
On FreeBSD, _testcapi is now linked to libkvm.

Lib/test/libregrtest/utils.py
Modules/_testcapi/mem.c
configure
configure.ac

index 21b84f7555b7713475f8ff63f9968b107d277428..b5b31bdce91928586ad8579c9ce74c92414e0ddf 100644 (file)
@@ -19,6 +19,10 @@ try:
     import _winapi
 except ImportError:
     _winapi = None
+try:
+    from _testcapi import get_process_memory_usage as _get_process_memory_usage
+except ImportError:
+    _get_process_memory_usage = None
 
 from test import support
 from test.support import os_helper
@@ -793,13 +797,17 @@ def _get_process_memory_usage_windows(pid: int) -> int | None:
     return mem_info['WorkingSetSize']
 
 
-if _winapi is not None:
+if _get_process_memory_usage is not None:
+    def get_process_memory_usage(pid: int) -> int | None:
+        try:
+            return _get_process_memory_usage(pid)
+        except ProcessLookupError:
+            return None
+elif _winapi is not None:
     get_process_memory_usage = _get_process_memory_usage_windows
 elif sys.platform == 'linux':
     get_process_memory_usage = _get_process_memory_usage_linux
 else:
     def get_process_memory_usage(pid: int) -> int | None:
-        """
-        Get process memory usage in bytes.
-        """
         return None
+get_process_memory_usage.__doc__ = "Get process memory usage in bytes."
index b4896f984510bd6e774d0ac56748fa4197f23838..f965b7cd390cd6276eebecb54ca6c9c8bbf399aa 100644 (file)
@@ -2,6 +2,15 @@
 
 #include <stddef.h>
 
+#ifdef __FreeBSD__
+#  include <fcntl.h>              // O_RDONLY
+#  include <kvm.h>                // kvm_openfiles()
+#  include <limits.h>             // _POSIX2_LINE_MAX
+#  include <sys/sysctl.h>         // KERN_PROC_PID
+#  include <sys/user.h>           // kinfo_proc definition
+#  include <unistd.h>             // sysconf()
+#endif
+
 
 typedef struct {
     PyMemAllocatorEx alloc;
@@ -684,6 +693,57 @@ error:
 }
 
 
+#ifdef __FreeBSD__
+// Return RSS only. Per-process swap usage isn't readily available
+static PyObject*
+get_process_memory_usage(PyObject *self, PyObject *args)
+{
+    int pid;
+    if (!PyArg_ParseTuple(args, "i", &pid)) {
+        return NULL;
+    }
+
+    long page_size = sysconf(_SC_PAGESIZE);
+    if (page_size <= 0) {
+        return PyErr_SetFromErrno(PyExc_OSError);
+    }
+
+    // Using /dev/null for vmcore avoids needing dump file.
+    // NULL for kernel file uses running kernel.
+    char errbuf[_POSIX2_LINE_MAX];
+    kvm_t *kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, errbuf);
+    if (kd == NULL) {
+        return PyErr_SetFromErrno(PyExc_OSError);
+    }
+
+    // KERN_PROC_PID filters for the specific process ID.
+    int n_procs;
+    struct kinfo_proc *kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &n_procs);
+    if (kp == NULL) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+    if (n_procs <= 0) {
+        // Process with PID not found
+        errno = ESRCH;
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+    assert(n_procs == 1);
+
+    // ki_rssize is in pages. Convert to bytes.
+    size_t rss = (size_t)kp[0].ki_rssize * page_size;
+    kvm_close(kd);
+
+    return PyLong_FromSize_t(rss);
+
+error:
+    kvm_close(kd);
+    return NULL;
+}
+#endif
+
+
 static PyMethodDef test_methods[] = {
     {"pymem_api_misuse",              pymem_api_misuse,              METH_NOARGS},
     {"pymem_buffer_overflow",         pymem_buffer_overflow,         METH_NOARGS},
@@ -698,6 +758,9 @@ static PyMethodDef test_methods[] = {
     {"test_pymem_setrawallocators",   test_pymem_setrawallocators,   METH_NOARGS},
     {"test_pyobject_new",             test_pyobject_new,             METH_NOARGS},
     {"test_pyobject_setallocators",   test_pyobject_setallocators,   METH_NOARGS},
+#ifdef __FreeBSD__
+    {"get_process_memory_usage",      get_process_memory_usage,      METH_VARARGS},
+#endif
 
     // Tracemalloc tests
     {"tracemalloc_track",             tracemalloc_track,             METH_VARARGS},
index 1377b1eff4d2026714d7ad34da403de1b732ea62..8135fd7d184c055efa3a581d9e83d0e10e3ebd49 100755 (executable)
--- a/configure
+++ b/configure
 printf "%s\n" "$py_cv_module__hashlib" >&6; }
 
 
+case $ac_sys_system in #(
+  # On FreeBSD, _testcapi.get_process_memory_usage() calls kvm_openfiles()
+  # and so needs libkvm.
+  FreeBSD*) :
+    LIBKVM="-lkvm"
+ ;; #(
+  *) :
+     ;;
+esac
 
   { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _testcapi" >&5
 printf %s "checking for stdlib extension module _testcapi... " >&6; }
@@ -34525,7 +34534,7 @@ fi
 then :
 
 
-    as_fn_append MODULE_BLOCK "MODULE__TESTCAPI_LDFLAGS=$LIBATOMIC$as_nl"
+    as_fn_append MODULE_BLOCK "MODULE__TESTCAPI_LDFLAGS=$LIBATOMIC $LIBKVM$as_nl"
 
 fi
    if test "$py_cv_module__testcapi" = yes; then
index 0c339c3c3a3a01310d91095c27c530f0098af026..a84ac25c1c4c503dec35f5bc826f89cdb184debc 100644 (file)
@@ -8428,10 +8428,15 @@ PY_STDLIB_MOD([_hashlib], [], [test "$ac_cv_working_openssl_hashlib" = yes],
   [$OPENSSL_INCLUDES], [$OPENSSL_LDFLAGS $OPENSSL_LDFLAGS_RPATH $LIBCRYPTO_LIBS])
 
 dnl test modules
+AS_CASE([$ac_sys_system],
+  # On FreeBSD, _testcapi.get_process_memory_usage() calls kvm_openfiles()
+  # and so needs libkvm.
+  [FreeBSD*], [LIBKVM="-lkvm"]
+)
 PY_STDLIB_MOD([_testcapi],
     [test "$TEST_MODULES" = yes],
     dnl Modules/_testcapi needs -latomic for 32bit AIX build
-    [], [], [$LIBATOMIC])
+    [], [], [$LIBATOMIC $LIBKVM])
 PY_STDLIB_MOD([_testclinic], [test "$TEST_MODULES" = yes])
 PY_STDLIB_MOD([_testclinic_limited], [test "$TEST_MODULES" = yes])
 PY_STDLIB_MOD([_testlimitedcapi], [test "$TEST_MODULES" = yes])