RuiDC
RuiDC

Reputation: 9113

How to get PrivateUsage memory value from python on win7x64

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

Answers (2)

Ben Hoyt
Ben Hoyt

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

Vinay
Vinay

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

Related Questions