From f5e1df0674460d04856ad502c5d7a5b512cf593c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 23 May 2026 00:26:30 +0200 Subject: [PATCH] gh-150114: Log the memory usage in regrtest on FreeBSD Add _testcapi.get_process_memory_usage(). On FreeBSD, _testcapi is now linked to libkvm. --- Lib/test/libregrtest/utils.py | 8 ++++- Modules/_testcapi/mem.c | 64 +++++++++++++++++++++++++++++++++++ configure | 11 +++++- configure.ac | 7 +++- 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 21b84f7555b7713..d23f3f4b7424263 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,7 +797,9 @@ 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: + get_process_memory_usage = _get_process_memory_usage +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 diff --git a/Modules/_testcapi/mem.c b/Modules/_testcapi/mem.c index b4896f984510bd6..ec661d83bf21cd8 100644 --- a/Modules/_testcapi/mem.c +++ b/Modules/_testcapi/mem.c @@ -2,6 +2,16 @@ #include +#ifdef __FreeBSD__ +# include // O_RDONLY +# include +# include // _POSIX2_LINE_MAX +# include +# include +# include // kinfo_proc definition +# include // sysconf() +#endif + typedef struct { PyMemAllocatorEx alloc; @@ -684,6 +694,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 +759,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 a1b635ffd15c4a3..605474a997a2b8d 100755 --- a/configure +++ b/configure @@ -34432,6 +34432,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; } @@ -34458,7 +34467,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 082c6c2d756cdca..5f926dfbdc0574b 100644 --- a/configure.ac +++ b/configure.ac @@ -8377,10 +8377,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])