From d40572c8bfecf9b3b31802717a1bd9700b700719 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 22:17:53 +0200 Subject: [PATCH 01/10] gh-150114: Log the memory usage in regrtest on Windows --- Lib/test/libregrtest/utils.py | 11 ++++++++++- Modules/_testcapi/mem.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 1b4cb96406d6f60..25531808589455f 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -13,6 +13,10 @@ import tempfile import textwrap from collections.abc import Callable +try: + from _testcapi import get_process_memory_usage as _get_process_memory_usage +except AttributeError: + _get_process_memory_usage = None from test import support from test.support import os_helper @@ -756,8 +760,13 @@ def display_title(title): def get_process_memory_usage(pid: int) -> int | None: """ - Read the private memory in bytes from /proc/pid/smaps. + Get process memory usage in bytes. """ + if _get_process_memory_usage is not None: + return _get_process_memory_usage(pid) + + # Linux implementation: read the private memory in bytes from + # /proc/pid/smaps. try: fp = open(f"/proc/{pid}/smaps", "rb") except OSError: diff --git a/Modules/_testcapi/mem.c b/Modules/_testcapi/mem.c index b4896f984510bd6..b23169f095a0fe1 100644 --- a/Modules/_testcapi/mem.c +++ b/Modules/_testcapi/mem.c @@ -1,5 +1,9 @@ #include "parts.h" +#ifdef MS_WINDOWS +# include +# include // GetProcessMemoryInfo() +#endif #include @@ -684,6 +688,34 @@ tracemalloc_track_race(PyObject *self, PyObject *args) } +#ifdef MS_WINDOWS +// Get process memory usage in bytes. +static PyObject * +get_process_memory_usage(PyObject *self, PyObject *args) +{ + int pid; + if (!PyArg_ParseTuple(args, "i", &pid)) { + return NULL; + } + + HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid); + if (handle == NULL) { + return PyErr_SetFromWindowsErr(0); + } + + PROCESS_MEMORY_COUNTERS pmc; + if (!GetProcessMemoryInfo(handle, &pmc, sizeof(pmc))) { + CloseHandle(handle); + return PyErr_SetFromWindowsErr(0); + } + CloseHandle(handle); + + size_t size = (pmc.WorkingSetSize + pmc.PagefileUsage); + return PyLong_FromSize_t(size); +} +#endif + + static PyMethodDef test_methods[] = { {"pymem_api_misuse", pymem_api_misuse, METH_NOARGS}, {"pymem_buffer_overflow", pymem_buffer_overflow, METH_NOARGS}, @@ -698,6 +730,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 MS_WINDOWS + {"get_process_memory_usage", get_process_memory_usage, METH_VARARGS}, +#endif // Tracemalloc tests {"tracemalloc_track", tracemalloc_track, METH_VARARGS}, From 8c24a93c12630693856e92e64082c06ee8a56589 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 22:24:10 +0200 Subject: [PATCH 02/10] Fix typo --- Lib/test/libregrtest/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 25531808589455f..0ff9dbf6027277c 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -15,7 +15,7 @@ from collections.abc import Callable try: from _testcapi import get_process_memory_usage as _get_process_memory_usage -except AttributeError: +except ImportError: _get_process_memory_usage = None from test import support From aff7bedff36370314de1efa8eb06af84220e42b8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 22:47:09 +0200 Subject: [PATCH 03/10] Don't use PagefileUsage, only use WorkingSetSize --- Modules/_testcapi/mem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_testcapi/mem.c b/Modules/_testcapi/mem.c index b23169f095a0fe1..3a01827585b44d1 100644 --- a/Modules/_testcapi/mem.c +++ b/Modules/_testcapi/mem.c @@ -698,6 +698,7 @@ get_process_memory_usage(PyObject *self, PyObject *args) return NULL; } + // Get WorkingSetSize from GetProcessMemoryInfo() HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid); if (handle == NULL) { return PyErr_SetFromWindowsErr(0); @@ -710,8 +711,7 @@ get_process_memory_usage(PyObject *self, PyObject *args) } CloseHandle(handle); - size_t size = (pmc.WorkingSetSize + pmc.PagefileUsage); - return PyLong_FromSize_t(size); + return PyLong_FromSize_t(pmc.WorkingSetSize); } #endif From e6ac054389baa1e4e954374f064baf507ab8bcd3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 23:21:08 +0200 Subject: [PATCH 04/10] Move the function to _winapi Co-Authored-by: Cody Maloney --- Lib/test/libregrtest/utils.py | 34 ++++++++++++++------ Modules/_testcapi/mem.c | 35 -------------------- Modules/_winapi.c | 60 +++++++++++++++++++++++++++++++++++ Modules/clinic/_winapi.c.h | 29 ++++++++++++++++- 4 files changed, 113 insertions(+), 45 deletions(-) diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 0ff9dbf6027277c..02165e12306b2ed 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -14,9 +14,9 @@ import textwrap from collections.abc import Callable try: - from _testcapi import get_process_memory_usage as _get_process_memory_usage + import _winapi except ImportError: - _get_process_memory_usage = None + _winapi = None from test import support from test.support import os_helper @@ -758,13 +758,7 @@ def display_title(title): print(flush=True) -def get_process_memory_usage(pid: int) -> int | None: - """ - Get process memory usage in bytes. - """ - if _get_process_memory_usage is not None: - return _get_process_memory_usage(pid) - +def _get_process_memory_usage_linux(pid: int) -> int | None: # Linux implementation: read the private memory in bytes from # /proc/pid/smaps. try: @@ -784,3 +778,25 @@ def get_process_memory_usage(pid: int) -> int | None: return total except ProcessLookupError: return None + + +def _get_process_memory_usage_windows(pid: int) -> int | None: + handle = _winapi.OpenProcess(_winapi.PROCESS_QUERY_LIMITED_INFORMATION, + False, pid) + try: + mem_info = _winapi.GetProcessMemoryInfo(handle) + finally: + _winapi.CloseHandle(handle) + return mem_info['WorkingSetSize'] + + +if _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 diff --git a/Modules/_testcapi/mem.c b/Modules/_testcapi/mem.c index 3a01827585b44d1..b4896f984510bd6 100644 --- a/Modules/_testcapi/mem.c +++ b/Modules/_testcapi/mem.c @@ -1,9 +1,5 @@ #include "parts.h" -#ifdef MS_WINDOWS -# include -# include // GetProcessMemoryInfo() -#endif #include @@ -688,34 +684,6 @@ tracemalloc_track_race(PyObject *self, PyObject *args) } -#ifdef MS_WINDOWS -// Get process memory usage in bytes. -static PyObject * -get_process_memory_usage(PyObject *self, PyObject *args) -{ - int pid; - if (!PyArg_ParseTuple(args, "i", &pid)) { - return NULL; - } - - // Get WorkingSetSize from GetProcessMemoryInfo() - HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid); - if (handle == NULL) { - return PyErr_SetFromWindowsErr(0); - } - - PROCESS_MEMORY_COUNTERS pmc; - if (!GetProcessMemoryInfo(handle, &pmc, sizeof(pmc))) { - CloseHandle(handle); - return PyErr_SetFromWindowsErr(0); - } - CloseHandle(handle); - - return PyLong_FromSize_t(pmc.WorkingSetSize); -} -#endif - - static PyMethodDef test_methods[] = { {"pymem_api_misuse", pymem_api_misuse, METH_NOARGS}, {"pymem_buffer_overflow", pymem_buffer_overflow, METH_NOARGS}, @@ -730,9 +698,6 @@ 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 MS_WINDOWS - {"get_process_memory_usage", get_process_memory_usage, METH_VARARGS}, -#endif // Tracemalloc tests {"tracemalloc_track", tracemalloc_track, METH_VARARGS}, diff --git a/Modules/_winapi.c b/Modules/_winapi.c index ffa407b2f21f733..8ee304c08b0fd0a 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -49,6 +49,13 @@ #include #include "winreparse.h" +// PSAPI_VERSION=2 redirects GetProcessMemoryInfo() to +// K32GetProcessMemoryInfo() in kernel32.dll, so we don't need to link +// psapi.lib. See: +// https://learn.microsoft.com/windows/win32/api/psapi/nf-psapi-getprocessmemoryinfo +#define PSAPI_VERSION 2 +#include // GetProcessMemoryInfo() + #if defined(MS_WIN32) && !defined(MS_WIN64) #define HANDLE_TO_PYNUM(handle) \ PyLong_FromUnsignedLong((unsigned long) handle) @@ -3080,6 +3087,57 @@ _winapi_ReportEvent_impl(PyObject *module, HANDLE handle, } + +/*[clinic input] +_winapi.GetProcessMemoryInfo + handle: HANDLE + / + +Return the memory usage of the given process handle. +[clinic start generated code]*/ + +static PyObject * +_winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle) +/*[clinic end generated code: output=00a5d09732e84120 input=cfa669907cfbcecb]*/ +{ + PROCESS_MEMORY_COUNTERS pmc; + if (!GetProcessMemoryInfo(handle, &pmc, sizeof(pmc))) { + return PyErr_SetFromWindowsErr(0); + } + + PyObject *result = PyDict_New(); + if (result == NULL) { + return NULL; + } + +#define ADD(ATTR) \ + do { \ + PyObject *obj = PyLong_FromSize_t(pmc.ATTR); \ + if (obj == NULL) { \ + Py_DECREF(result); \ + return NULL; \ + } \ + if (PyDict_SetItemString(result, #ATTR, obj) < 0) { \ + Py_DECREF(obj); \ + return NULL; \ + } \ + Py_DECREF(obj); \ + } while (0) + + ADD(PageFaultCount); + ADD(PeakWorkingSetSize); + ADD(WorkingSetSize); + ADD(QuotaPeakPagedPoolUsage); + ADD(QuotaPagedPoolUsage); + ADD(QuotaPeakNonPagedPoolUsage); + ADD(QuotaNonPagedPoolUsage); + ADD(PagefileUsage); + ADD(PeakPagefileUsage); + + return result; +} + + static PyMethodDef winapi_functions[] = { _WINAPI_CLOSEHANDLE_METHODDEF _WINAPI_CONNECTNAMEDPIPE_METHODDEF @@ -3130,6 +3188,7 @@ static PyMethodDef winapi_functions[] = { _WINAPI__MIMETYPES_READ_WINDOWS_REGISTRY_METHODDEF _WINAPI_NEEDCURRENTDIRECTORYFOREXEPATH_METHODDEF _WINAPI_COPYFILE2_METHODDEF + _WINAPI_GETPROCESSMEMORYINFO_METHODDEF {NULL, NULL} }; @@ -3226,6 +3285,7 @@ static int winapi_exec(PyObject *m) WINAPI_CONSTANT(F_DWORD, PROCESS_ALL_ACCESS); WINAPI_CONSTANT(F_DWORD, SYNCHRONIZE); WINAPI_CONSTANT(F_DWORD, PROCESS_DUP_HANDLE); + WINAPI_CONSTANT(F_DWORD, PROCESS_QUERY_LIMITED_INFORMATION); WINAPI_CONSTANT(F_DWORD, SEC_COMMIT); WINAPI_CONSTANT(F_DWORD, SEC_IMAGE); WINAPI_CONSTANT(F_DWORD, SEC_LARGE_PAGES); diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index 00cce91dca43b1c..0d1d9c2208a769b 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -2331,7 +2331,34 @@ _winapi_ReportEvent(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(_winapi_GetProcessMemoryInfo__doc__, +"GetProcessMemoryInfo($module, handle, /)\n" +"--\n" +"\n" +"Return the memory usage of the given process handle."); + +#define _WINAPI_GETPROCESSMEMORYINFO_METHODDEF \ + {"GetProcessMemoryInfo", (PyCFunction)_winapi_GetProcessMemoryInfo, METH_O, _winapi_GetProcessMemoryInfo__doc__}, + +static PyObject * +_winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle); + +static PyObject * +_winapi_GetProcessMemoryInfo(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + HANDLE handle; + + if (!PyArg_Parse(arg, "" F_HANDLE ":GetProcessMemoryInfo", &handle)) { + goto exit; + } + return_value = _winapi_GetProcessMemoryInfo_impl(module, handle); + +exit: + return return_value; +} + #ifndef _WINAPI_GETSHORTPATHNAME_METHODDEF #define _WINAPI_GETSHORTPATHNAME_METHODDEF #endif /* !defined(_WINAPI_GETSHORTPATHNAME_METHODDEF) */ -/*[clinic end generated code: output=4ab94eaee93a0a90 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1cd5bfa79a04faff input=a9049054013a1b77]*/ From bc287f0f2fa2b0bed14e441cb819d9f729de18eb Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 23:29:53 +0200 Subject: [PATCH 05/10] Fix mypy --- Lib/test/libregrtest/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 02165e12306b2ed..316099a1d8c3756 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -12,7 +12,9 @@ import sysconfig import tempfile import textwrap +import types from collections.abc import Callable +_winapi: types.ModuleType | None try: import _winapi except ImportError: @@ -781,6 +783,7 @@ def _get_process_memory_usage_linux(pid: int) -> int | None: def _get_process_memory_usage_windows(pid: int) -> int | None: + assert _winapi is not None # to make mypy happy handle = _winapi.OpenProcess(_winapi.PROCESS_QUERY_LIMITED_INFORMATION, False, pid) try: From 78f8087e16e60de49a6063a9bd0108044226c138 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 23:31:38 +0200 Subject: [PATCH 06/10] Fix indentation --- Lib/test/libregrtest/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 316099a1d8c3756..21b84f7555b7713 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -787,7 +787,7 @@ def _get_process_memory_usage_windows(pid: int) -> int | None: handle = _winapi.OpenProcess(_winapi.PROCESS_QUERY_LIMITED_INFORMATION, False, pid) try: - mem_info = _winapi.GetProcessMemoryInfo(handle) + mem_info = _winapi.GetProcessMemoryInfo(handle) finally: _winapi.CloseHandle(handle) return mem_info['WorkingSetSize'] From b1c9d56329d6380841f91100f647792d6dab21ee Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 23:33:09 +0200 Subject: [PATCH 07/10] Fix refleak in error path --- Modules/_winapi.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 8ee304c08b0fd0a..e27175280d78a9c 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -3114,12 +3114,11 @@ _winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle) do { \ PyObject *obj = PyLong_FromSize_t(pmc.ATTR); \ if (obj == NULL) { \ - Py_DECREF(result); \ - return NULL; \ + goto error; \ } \ if (PyDict_SetItemString(result, #ATTR, obj) < 0) { \ Py_DECREF(obj); \ - return NULL; \ + goto error; \ } \ Py_DECREF(obj); \ } while (0) @@ -3135,6 +3134,10 @@ _winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle) ADD(PeakPagefileUsage); return result; + +error: + Py_DECREF(result); + return NULL; } From d2ad223ebb313d1e23a61e7795b70646d81141de Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 23:34:43 +0200 Subject: [PATCH 08/10] Cleanup --- Modules/_winapi.c | 5 ++--- Modules/clinic/_winapi.c.h | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index e27175280d78a9c..476b6dd6c30615b 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -3087,18 +3087,17 @@ _winapi_ReportEvent_impl(PyObject *module, HANDLE handle, } - /*[clinic input] _winapi.GetProcessMemoryInfo handle: HANDLE / -Return the memory usage of the given process handle. +Return the memory usage of the given process handle as a dict. [clinic start generated code]*/ static PyObject * _winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle) -/*[clinic end generated code: output=00a5d09732e84120 input=cfa669907cfbcecb]*/ +/*[clinic end generated code: output=00a5d09732e84120 input=5b90ad61cdc68d2a]*/ { PROCESS_MEMORY_COUNTERS pmc; if (!GetProcessMemoryInfo(handle, &pmc, sizeof(pmc))) { diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index 0d1d9c2208a769b..dd9dbffaa9ac23c 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -2335,7 +2335,7 @@ PyDoc_STRVAR(_winapi_GetProcessMemoryInfo__doc__, "GetProcessMemoryInfo($module, handle, /)\n" "--\n" "\n" -"Return the memory usage of the given process handle."); +"Return the memory usage of the given process handle as a dict."); #define _WINAPI_GETPROCESSMEMORYINFO_METHODDEF \ {"GetProcessMemoryInfo", (PyCFunction)_winapi_GetProcessMemoryInfo, METH_O, _winapi_GetProcessMemoryInfo__doc__}, @@ -2361,4 +2361,4 @@ _winapi_GetProcessMemoryInfo(PyObject *module, PyObject *arg) #ifndef _WINAPI_GETSHORTPATHNAME_METHODDEF #define _WINAPI_GETSHORTPATHNAME_METHODDEF #endif /* !defined(_WINAPI_GETSHORTPATHNAME_METHODDEF) */ -/*[clinic end generated code: output=1cd5bfa79a04faff input=a9049054013a1b77]*/ +/*[clinic end generated code: output=07dfd4bbacaed4a8 input=a9049054013a1b77]*/ From 140c121e5566b26eeccb86a812b1deaba63f4e8f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 23:36:14 +0200 Subject: [PATCH 09/10] undef macro --- Modules/_winapi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 476b6dd6c30615b..3c2a618baa11d2e 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -3132,6 +3132,8 @@ _winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle) ADD(PagefileUsage); ADD(PeakPagefileUsage); +#undef ATTR + return result; error: From fd2e63e3d0e74cf2dcc1ee33102668dc4399014a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 22 May 2026 23:36:49 +0200 Subject: [PATCH 10/10] Fix typo --- Modules/_winapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 3c2a618baa11d2e..fc2c0890468a6b9 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -3132,7 +3132,7 @@ _winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle) ADD(PagefileUsage); ADD(PeakPagefileUsage); -#undef ATTR +#undef ADD return result;