Skip to content

Commit a7d5a6c

Browse files
vstinnercmaloney
andauthored
gh-150114: Log the memory usage in regrtest on Windows (#150267)
Add _winapi.GetProcessMemoryInfo() function. Co-authored-by: Cody Maloney <cmaloney@users.noreply.github.com>
1 parent 9df2b6c commit a7d5a6c

3 files changed

Lines changed: 124 additions & 5 deletions

File tree

Lib/test/libregrtest/utils.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@
1212
import sysconfig
1313
import tempfile
1414
import textwrap
15+
import types
1516
from collections.abc import Callable
17+
_winapi: types.ModuleType | None
18+
try:
19+
import _winapi
20+
except ImportError:
21+
_winapi = None
1622

1723
from test import support
1824
from test.support import os_helper
@@ -754,10 +760,9 @@ def display_title(title):
754760
print(flush=True)
755761

756762

757-
def get_process_memory_usage(pid: int) -> int | None:
758-
"""
759-
Read the private memory in bytes from /proc/pid/smaps.
760-
"""
763+
def _get_process_memory_usage_linux(pid: int) -> int | None:
764+
# Linux implementation: read the private memory in bytes from
765+
# /proc/pid/smaps.
761766
try:
762767
fp = open(f"/proc/{pid}/smaps", "rb")
763768
except OSError:
@@ -775,3 +780,26 @@ def get_process_memory_usage(pid: int) -> int | None:
775780
return total
776781
except ProcessLookupError:
777782
return None
783+
784+
785+
def _get_process_memory_usage_windows(pid: int) -> int | None:
786+
assert _winapi is not None # to make mypy happy
787+
handle = _winapi.OpenProcess(_winapi.PROCESS_QUERY_LIMITED_INFORMATION,
788+
False, pid)
789+
try:
790+
mem_info = _winapi.GetProcessMemoryInfo(handle)
791+
finally:
792+
_winapi.CloseHandle(handle)
793+
return mem_info['WorkingSetSize']
794+
795+
796+
if _winapi is not None:
797+
get_process_memory_usage = _get_process_memory_usage_windows
798+
elif sys.platform == 'linux':
799+
get_process_memory_usage = _get_process_memory_usage_linux
800+
else:
801+
def get_process_memory_usage(pid: int) -> int | None:
802+
"""
803+
Get process memory usage in bytes.
804+
"""
805+
return None

Modules/_winapi.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@
4949
#include <crtdbg.h>
5050
#include "winreparse.h"
5151

52+
// PSAPI_VERSION=2 redirects GetProcessMemoryInfo() to
53+
// K32GetProcessMemoryInfo() in kernel32.dll, so we don't need to link
54+
// psapi.lib. See:
55+
// https://learn.microsoft.com/windows/win32/api/psapi/nf-psapi-getprocessmemoryinfo
56+
#define PSAPI_VERSION 2
57+
#include <psapi.h> // GetProcessMemoryInfo()
58+
5259
#if defined(MS_WIN32) && !defined(MS_WIN64)
5360
#define HANDLE_TO_PYNUM(handle) \
5461
PyLong_FromUnsignedLong((unsigned long) handle)
@@ -3080,6 +3087,61 @@ _winapi_ReportEvent_impl(PyObject *module, HANDLE handle,
30803087
}
30813088

30823089

3090+
/*[clinic input]
3091+
_winapi.GetProcessMemoryInfo
3092+
handle: HANDLE
3093+
/
3094+
3095+
Return the memory usage of the given process handle as a dict.
3096+
[clinic start generated code]*/
3097+
3098+
static PyObject *
3099+
_winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle)
3100+
/*[clinic end generated code: output=00a5d09732e84120 input=5b90ad61cdc68d2a]*/
3101+
{
3102+
PROCESS_MEMORY_COUNTERS pmc;
3103+
if (!GetProcessMemoryInfo(handle, &pmc, sizeof(pmc))) {
3104+
return PyErr_SetFromWindowsErr(0);
3105+
}
3106+
3107+
PyObject *result = PyDict_New();
3108+
if (result == NULL) {
3109+
return NULL;
3110+
}
3111+
3112+
#define ADD(ATTR) \
3113+
do { \
3114+
PyObject *obj = PyLong_FromSize_t(pmc.ATTR); \
3115+
if (obj == NULL) { \
3116+
goto error; \
3117+
} \
3118+
if (PyDict_SetItemString(result, #ATTR, obj) < 0) { \
3119+
Py_DECREF(obj); \
3120+
goto error; \
3121+
} \
3122+
Py_DECREF(obj); \
3123+
} while (0)
3124+
3125+
ADD(PageFaultCount);
3126+
ADD(PeakWorkingSetSize);
3127+
ADD(WorkingSetSize);
3128+
ADD(QuotaPeakPagedPoolUsage);
3129+
ADD(QuotaPagedPoolUsage);
3130+
ADD(QuotaPeakNonPagedPoolUsage);
3131+
ADD(QuotaNonPagedPoolUsage);
3132+
ADD(PagefileUsage);
3133+
ADD(PeakPagefileUsage);
3134+
3135+
#undef ADD
3136+
3137+
return result;
3138+
3139+
error:
3140+
Py_DECREF(result);
3141+
return NULL;
3142+
}
3143+
3144+
30833145
static PyMethodDef winapi_functions[] = {
30843146
_WINAPI_CLOSEHANDLE_METHODDEF
30853147
_WINAPI_CONNECTNAMEDPIPE_METHODDEF
@@ -3130,6 +3192,7 @@ static PyMethodDef winapi_functions[] = {
31303192
_WINAPI__MIMETYPES_READ_WINDOWS_REGISTRY_METHODDEF
31313193
_WINAPI_NEEDCURRENTDIRECTORYFOREXEPATH_METHODDEF
31323194
_WINAPI_COPYFILE2_METHODDEF
3195+
_WINAPI_GETPROCESSMEMORYINFO_METHODDEF
31333196
{NULL, NULL}
31343197
};
31353198

@@ -3226,6 +3289,7 @@ static int winapi_exec(PyObject *m)
32263289
WINAPI_CONSTANT(F_DWORD, PROCESS_ALL_ACCESS);
32273290
WINAPI_CONSTANT(F_DWORD, SYNCHRONIZE);
32283291
WINAPI_CONSTANT(F_DWORD, PROCESS_DUP_HANDLE);
3292+
WINAPI_CONSTANT(F_DWORD, PROCESS_QUERY_LIMITED_INFORMATION);
32293293
WINAPI_CONSTANT(F_DWORD, SEC_COMMIT);
32303294
WINAPI_CONSTANT(F_DWORD, SEC_IMAGE);
32313295
WINAPI_CONSTANT(F_DWORD, SEC_LARGE_PAGES);

Modules/clinic/_winapi.c.h

Lines changed: 28 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)