https://github.com/python/cpython/commit/dfe7ef6292ec0618f0193dfe993683f46645e723
commit: dfe7ef6292ec0618f0193dfe993683f46645e723
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-05-25T13:45:55Z
summary:

gh-150114: Log the memory usage in regrtest on FreeBSD (#150280)

Add _testcapi.get_process_memory_usage().
On FreeBSD, _testcapi is now linked to libkvm.

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

diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py
index 21b84f7555b7713..b5b31bdce919285 100644
--- a/Lib/test/libregrtest/utils.py
+++ b/Lib/test/libregrtest/utils.py
@@ -19,6 +19,10 @@
     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."
diff --git a/Modules/_testcapi/mem.c b/Modules/_testcapi/mem.c
index b4896f984510bd6..f965b7cd390cd62 100644
--- a/Modules/_testcapi/mem.c
+++ b/Modules/_testcapi/mem.c
@@ -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 @@ tracemalloc_track_race(PyObject *self, PyObject *args)
 }
 
 
+#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},
diff --git a/configure b/configure
index 1377b1eff4d2026..8135fd7d184c055 100755
--- a/configure
+++ b/configure
@@ -34499,6 +34499,15 @@ fi
 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
diff --git a/configure.ac b/configure.ac
index 0c339c3c3a3a013..a84ac25c1c4c503 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to