Reputation: 9113
I use the following code which works well on WinXPx32, but returns 0 on Win7x64. I know the psutil library will also return it, but I need something that can run without extra dependencies, ctypes and win32api is fine. I've also tried Kernel32.K32GetProcessMemoryInfo with the same result.
import ctypes
psapi = ctypes.windll.psapi
Kernel32 = ctypes.windll.Kernel32
class PROCESS_MEMORY_COUNTERS_EX(ctypes.Structure):
_fields_ = [("cb", ctypes.c_ulong),
("PageFaultCount", ctypes.c_ulong),
("PeakWorkingSetSize", ctypes.c_size_t),
("WorkingSetSize", ctypes.c_size_t),
("QuotaPeakPagedPoolUsage", ctypes.c_size_t),
("QuotaPagedPoolUsage", ctypes.c_size_t),
("QuotaPeakNonPagedPoolUsage", ctypes.c_size_t),
("QuotaNonPagedPoolUsage", ctypes.c_size_t),
("PagefileUsage", ctypes.c_size_t),
("PeakPagefileUsage", ctypes.c_size_t),
("PrivateUsage", ctypes.c_size_t),
]
def GetProcessPrivateUsage():
mem_struct = PROCESS_MEMORY_COUNTERS_EX()
p_handle = Kernel32.GetCurrentProcess()
b = psapi.GetProcessMemoryInfo(p_handle, ctypes.byref(mem_struct), ctypes.sizeof(mem_struct))
print(b)
return mem_struct.PrivateUsage
print(GetProcessPrivateUsage())
Upvotes: 3
Views: 1457
Reputation: 11044
I suspect the problem is due to HANDLE
being 64 bits on 64-bit Windows, but 32 bits on 32-bit Windows. The default ctypes return type is int
, which is 32 bits on both systems. Sometimes it happens to work because the high 64 bits happen to be correct, but there are no guarantees.
This is the same issue we had with CherryPy calling SetHandleInformation
via ctypes without explicit argument and return types, which I described here.
What you need to do is explicitly specify the argtypes
and restype
attributes of the Win32 functions you're calling. Here's a processutil.py
module I made to do this that works on both 32-bit and 64-bit Windows (I also uploaded this as an ActiveState recipe):
"""Functions for getting memory usage of Windows processes."""
__all__ = ['get_current_process', 'get_memory_info', 'get_memory_usage']
import ctypes
from ctypes import wintypes
GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
GetCurrentProcess.argtypes = []
GetCurrentProcess.restype = wintypes.HANDLE
SIZE_T = ctypes.c_size_t
class PROCESS_MEMORY_COUNTERS_EX(ctypes.Structure):
_fields_ = [
('cb', wintypes.DWORD),
('PageFaultCount', wintypes.DWORD),
('PeakWorkingSetSize', SIZE_T),
('WorkingSetSize', SIZE_T),
('QuotaPeakPagedPoolUsage', SIZE_T),
('QuotaPagedPoolUsage', SIZE_T),
('QuotaPeakNonPagedPoolUsage', SIZE_T),
('QuotaNonPagedPoolUsage', SIZE_T),
('PagefileUsage', SIZE_T),
('PeakPagefileUsage', SIZE_T),
('PrivateUsage', SIZE_T),
]
GetProcessMemoryInfo = ctypes.windll.psapi.GetProcessMemoryInfo
GetProcessMemoryInfo.argtypes = [
wintypes.HANDLE,
ctypes.POINTER(PROCESS_MEMORY_COUNTERS_EX),
wintypes.DWORD,
]
GetProcessMemoryInfo.restype = wintypes.BOOL
def get_current_process():
"""Return handle to current process."""
return GetCurrentProcess()
def get_memory_info(process=None):
"""Return Win32 process memory counters structure as a dict."""
if process is None:
process = get_current_process()
counters = PROCESS_MEMORY_COUNTERS_EX()
ret = GetProcessMemoryInfo(process, ctypes.byref(counters),
ctypes.sizeof(counters))
if not ret:
raise ctypes.WinError()
info = dict((name, getattr(counters, name))
for name, _ in counters._fields_)
return info
def get_memory_usage(process=None):
"""Return this process's memory usage in bytes."""
info = get_memory_info(process=process)
return info['PrivateUsage']
if __name__ == '__main__':
import pprint
pprint.pprint(get_memory_info())
Upvotes: 3
Reputation: 469
I modified your code based on the following sample from MSDN:
from __future__ import print_function
import ctypes
psapi = ctypes.windll.psapi
Kernel32 = ctypes.windll.Kernel32
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_READ = 0x0010
class PROCESS_MEMORY_COUNTERS_EX(ctypes.Structure):
_fields_ = [("cb", ctypes.c_ulong),
("PageFaultCount", ctypes.c_ulong),
("PeakWorkingSetSize", ctypes.c_size_t),
("WorkingSetSize", ctypes.c_size_t),
("QuotaPeakPagedPoolUsage", ctypes.c_size_t),
("QuotaPagedPoolUsage", ctypes.c_size_t),
("QuotaPeakNonPagedPoolUsage", ctypes.c_size_t),
("QuotaNonPagedPoolUsage", ctypes.c_size_t),
("PagefileUsage", ctypes.c_size_t),
("PeakPagefileUsage", ctypes.c_size_t),
("PrivateUsage", ctypes.c_size_t),
]
def GetProcessPrivateUsage():
mem_struct = PROCESS_MEMORY_COUNTERS_EX()
id = Kernel32.GetCurrentProcessId()
print_output('GetCurrentProcessId: {}'.format(id))
handle = Kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, id)
print_output('GetCurrentProcess: {}'.format(handle))
b = psapi.GetProcessMemoryInfo(handle, ctypes.byref(mem_struct), ctypes.sizeof(mem_struct))
print_output('GetProcessMemoryInfo: {}'.format(b))
return mem_struct.PrivateUsage
def print_output(text):
print('{}. {}'.format(text, ctypes.FormatError(Kernel32.GetLastError())))
usage = GetProcessPrivateUsage()
print_output('private usage: {}'.format(usage))
My best guess is that the handle returned by GetCurrentProcess()
no longer has the access rights to the memory counters in Windows 7 (GetLastError()
would return "invalid handle").
I couldn't find PROCESS_QUERY_INFORMATION
and PROCESS_VM_READ
in ctypes, so I looked up the actual values here and inserted them as constants.
I also added the print_output()
function so I could see which steps were failing.
Upvotes: 0