Skip to content

Commit

Permalink
win / C: refactor memory_info_2 code() and return it along side other…
Browse files Browse the repository at this point in the history
… proc_info() metrics
  • Loading branch information
giampaolo committed Oct 27, 2016
1 parent be54625 commit c10a7aa
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 90 deletions.
93 changes: 31 additions & 62 deletions psutil/_psutil_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -778,57 +778,6 @@ psutil_proc_memory_info(PyObject *self, PyObject *args) {
}


/*
* Alternative implementation of the one above but bypasses ACCESS DENIED.
*/
static PyObject *
psutil_proc_memory_info_2(PyObject *self, PyObject *args) {
DWORD pid;
PSYSTEM_PROCESS_INFORMATION process;
PVOID buffer;
SIZE_T private;
unsigned long pfault_count;

#if defined(_WIN64)
unsigned long long m1, m2, m3, m4, m5, m6, m7, m8;
#else
unsigned int m1, m2, m3, m4, m5, m6, m7, m8;
#endif

if (! PyArg_ParseTuple(args, "l", &pid))
return NULL;
if (! psutil_get_proc_info(pid, &process, &buffer))
return NULL;

#if (_WIN32_WINNT >= 0x0501) // Windows XP with SP2
private = process->PrivatePageCount;
#else
private = 0;
#endif
pfault_count = process->PageFaultCount;

m1 = process->PeakWorkingSetSize;
m2 = process->WorkingSetSize;
m3 = process->QuotaPeakPagedPoolUsage;
m4 = process->QuotaPagedPoolUsage;
m5 = process->QuotaPeakNonPagedPoolUsage;
m6 = process->QuotaNonPagedPoolUsage;
m7 = process->PagefileUsage;
m8 = process->PeakPagefileUsage;

free(buffer);

// SYSTEM_PROCESS_INFORMATION values are defined as SIZE_T which on 64
// bits is an (unsigned long long) and on 32bits is an (unsigned int).
// "_WIN64" is defined if we're running a 64bit Python interpreter not
// exclusively if the *system* is 64bit.
#if defined(_WIN64)
return Py_BuildValue("(kKKKKKKKKK)",
#else
return Py_BuildValue("(kIIIIIIIII)",
#endif
pfault_count, m1, m2, m3, m4, m5, m6, m7, m8, private);
}

/**
* Returns the USS of the process.
Expand Down Expand Up @@ -2778,24 +2727,25 @@ psutil_proc_num_handles(PyObject *self, PyObject *args) {
* denied. This is slower because it iterates over all processes.
* Returned tuple includes the following process info:
*
* - num_threads
* - ctx_switches
* - num_handles (fallback)
* - user/kernel times (fallback)
* - create time (fallback)
* - io counters (fallback)
* - num_threads()
* - ctx_switches()
* - num_handles() (fallback)
* - cpu_times() (fallback)
* - create_time() (fallback)
* - io_counters() (fallback)
* - memory_info() (fallback)
*/
static PyObject *
psutil_proc_info(PyObject *self, PyObject *args) {
DWORD pid;
PSYSTEM_PROCESS_INFORMATION process;
PVOID buffer;
ULONG num_handles;
ULONG i;
ULONG ctx_switches = 0;
double user_time;
double kernel_time;
long long create_time;
SIZE_T mem_private;
PyObject *py_retlist;

if (! PyArg_ParseTuple(args, "l", &pid))
Expand All @@ -2822,8 +2772,18 @@ psutil_proc_info(PyObject *self, PyObject *args) {
create_time /= 10000000;
}

#if (_WIN32_WINNT >= 0x0501) // Windows XP with SP2
mem_private = process->PrivatePageCount;
#else
mem_private = 0;
#endif

py_retlist = Py_BuildValue(
"kkdddiKKKK",
#if defined(_WIN64)
"kkdddiKKKK" "kKKKKKKKKK",
#else
"kkdddiKKKK" "kIIIIIIIII",
#endif
process->HandleCount, // num handles
ctx_switches, // num ctx switches
user_time, // cpu user time
Expand All @@ -2833,7 +2793,18 @@ psutil_proc_info(PyObject *self, PyObject *args) {
process->ReadOperationCount.QuadPart, // io rcount
process->WriteOperationCount.QuadPart, // io wcount
process->ReadTransferCount.QuadPart, // io rbytes
process->WriteTransferCount.QuadPart // io wbytes
process->WriteTransferCount.QuadPart, // io wbytes
// memory
process->PageFaultCount, // num page faults
process->PeakWorkingSetSize, // peak wset
process->WorkingSetSize, // wset
process->QuotaPeakPagedPoolUsage, // peak paged pool
process->QuotaPagedPoolUsage, // paged pool
process->QuotaPeakNonPagedPoolUsage, // peak non paged pool
process->QuotaNonPagedPoolUsage, // non paged pool
process->PagefileUsage, // pagefile
process->PeakPagefileUsage, // peak pagefile
mem_private // private
);

free(buffer);
Expand Down Expand Up @@ -3450,8 +3421,6 @@ PsutilMethods[] = {
"seconds since the epoch"},
{"proc_memory_info", psutil_proc_memory_info, METH_VARARGS,
"Return a tuple of process memory information"},
{"proc_memory_info_2", psutil_proc_memory_info_2, METH_VARARGS,
"Alternate implementation"},
{"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS,
"Return the USS of the process"},
{"proc_cwd", psutil_proc_cwd, METH_VARARGS,
Expand Down
67 changes: 58 additions & 9 deletions psutil/_pswindows.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,29 @@ class Priority(enum.IntEnum):

globals().update(Priority.__members__)

pinfo_map = dict(
num_handles=0,
ctx_switches=1,
user_time=2,
kernel_time=3,
create_time=4,
num_threads=5,
io_rcount=6,
io_wcount=7,
io_rbytes=8,
io_wbytes=9,
num_page_faults=10,
peak_wset=11,
wset=12,
peak_paged_pool=13,
paged_pool=14,
peak_non_paged_pool=15,
non_paged_pool=16,
pagefile=17,
peak_pagefile=18,
mem_private=19,
)


# =====================================================================
# --- named tuples
Expand Down Expand Up @@ -553,6 +576,14 @@ def __init__(self, pid):
self._name = None
self._ppid = None

def oneshot_info(self):
"""Return multiple information about this process as a
raw tuple.
"""
ret = cext.proc_info(self.pid)
assert len(ret) == len(pinfo_map)
return ret

@wrap_exceptions
def name(self):
"""Return process name, which on Windows is always the final
Expand Down Expand Up @@ -609,7 +640,19 @@ def _get_raw_meminfo(self):
if err.errno in ACCESS_DENIED_SET:
# TODO: the C ext can probably be refactored in order
# to get this from cext.proc_info()
return cext.proc_memory_info_2(self.pid)
info = self.oneshot_info()
return (
info[pinfo_map['num_page_faults']],
info[pinfo_map['peak_wset']],
info[pinfo_map['wset']],
info[pinfo_map['peak_paged_pool']],
info[pinfo_map['paged_pool']],
info[pinfo_map['peak_non_paged_pool']],
info[pinfo_map['non_paged_pool']],
info[pinfo_map['pagefile']],
info[pinfo_map['peak_pagefile']],
info[pinfo_map['mem_private']],
)
raise

@wrap_exceptions
Expand Down Expand Up @@ -680,12 +723,12 @@ def create_time(self):
return cext.proc_create_time(self.pid)
except OSError as err:
if err.errno in ACCESS_DENIED_SET:
return ntpinfo(*cext.proc_info(self.pid)).create_time
return self.oneshot_info()[pinfo_map['create_time']]
raise

@wrap_exceptions
def num_threads(self):
return ntpinfo(*cext.proc_info(self.pid)).num_threads
return self.oneshot_info()[pinfo_map['num_threads']]

@wrap_exceptions
def threads(self):
Expand All @@ -702,8 +745,9 @@ def cpu_times(self):
user, system = cext.proc_cpu_times(self.pid)
except OSError as err:
if err.errno in ACCESS_DENIED_SET:
nt = ntpinfo(*cext.proc_info(self.pid))
user, system = (nt.user_time, nt.kernel_time)
info = self.oneshot_info()
user = info[pinfo_map['user_time']]
system = info[pinfo_map['kernel_time']]
else:
raise
# Children user/system times are not retrievable (set to 0).
Expand Down Expand Up @@ -782,8 +826,13 @@ def io_counters(self):
ret = cext.proc_io_counters(self.pid)
except OSError as err:
if err.errno in ACCESS_DENIED_SET:
nt = ntpinfo(*cext.proc_info(self.pid))
ret = (nt.io_rcount, nt.io_wcount, nt.io_rbytes, nt.io_wbytes)
info = self.oneshot_info()
ret = (
info[pinfo_map['io_rcount']],
info[pinfo_map['io_wcount']],
info[pinfo_map['io_rbytes']],
info[pinfo_map['io_wbytes']],
)
else:
raise
return _common.pio(*ret)
Expand Down Expand Up @@ -834,11 +883,11 @@ def num_handles(self):
return cext.proc_num_handles(self.pid)
except OSError as err:
if err.errno in ACCESS_DENIED_SET:
return ntpinfo(*cext.proc_info(self.pid)).num_handles
return self.oneshot_info()[pinfo_map['num_handles']]
raise

@wrap_exceptions
def num_ctx_switches(self):
ctx_switches = ntpinfo(*cext.proc_info(self.pid)).ctx_switches
ctx_switches = self.oneshot_info()[pinfo_map['ctx_switches']]
# only voluntary ctx switches are supported
return _common.pctxsw(ctx_switches, 0)
77 changes: 58 additions & 19 deletions psutil/tests/test_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,10 @@ class TestDualProcessImplementation(unittest.TestCase):
]

def test_compare_values(self):
from psutil._pswindows import pinfo_map

def assert_ge_0(obj):
if isinstance(obj, tuple):
if isinstance(obj, (tuple, list)):
for value in obj:
self.assertGreaterEqual(value, 0, msg=obj)
elif isinstance(obj, (int, long, float)):
Expand All @@ -356,14 +358,13 @@ def compare_with_tolerance(ret1, ret2, tolerance):
diff = abs(a - b)
self.assertLessEqual(diff, tolerance)

from psutil._pswindows import ntpinfo
failures = []
for p in psutil.process_iter():
try:
nt = ntpinfo(*cext.proc_info(p.pid))
raw_info = cext.proc_info(p.pid)
except psutil.NoSuchProcess:
continue
assert_ge_0(nt)
assert_ge_0(raw_info)

for name, tolerance in self.fun_names:
if name == 'proc_memory_info' and p.pid == os.getpid():
Expand All @@ -378,28 +379,66 @@ def compare_with_tolerance(ret1, ret2, tolerance):
# compare values
try:
if name == 'proc_cpu_times':
compare_with_tolerance(ret[0], nt.user_time, tolerance)
compare_with_tolerance(ret[1],
nt.kernel_time, tolerance)
compare_with_tolerance(
ret[0], raw_info[pinfo_map['user_time']],
tolerance)
compare_with_tolerance(
ret[1], raw_info[pinfo_map['kernel_time']],
tolerance)
elif name == 'proc_create_time':
compare_with_tolerance(ret, nt.create_time, tolerance)
compare_with_tolerance(
ret, raw_info[pinfo_map['create_time']], tolerance)
elif name == 'proc_num_handles':
compare_with_tolerance(ret, nt.num_handles, tolerance)
compare_with_tolerance(
ret, raw_info[pinfo_map['num_handles']], tolerance)
elif name == 'proc_io_counters':
compare_with_tolerance(ret[0], nt.io_rcount, tolerance)
compare_with_tolerance(ret[1], nt.io_wcount, tolerance)
compare_with_tolerance(ret[2], nt.io_rbytes, tolerance)
compare_with_tolerance(ret[3], nt.io_wbytes, tolerance)
compare_with_tolerance(
ret[0], raw_info[pinfo_map['io_rcount']],
tolerance)
compare_with_tolerance(
ret[1], raw_info[pinfo_map['io_wcount']],
tolerance)
compare_with_tolerance(
ret[2], raw_info[pinfo_map['io_rbytes']],
tolerance)
compare_with_tolerance(
ret[3], raw_info[pinfo_map['io_wbytes']],
tolerance)
elif name == 'proc_memory_info':
try:
rawtupl = cext.proc_memory_info_2(p.pid)
except psutil.NoSuchProcess:
continue
compare_with_tolerance(ret, rawtupl, tolerance)
compare_with_tolerance(
ret[0], raw_info[pinfo_map['num_page_faults']],
tolerance)
compare_with_tolerance(
ret[1], raw_info[pinfo_map['peak_wset']],
tolerance)
compare_with_tolerance(
ret[2], raw_info[pinfo_map['wset']],
tolerance)
compare_with_tolerance(
ret[3], raw_info[pinfo_map['peak_paged_pool']],
tolerance)
compare_with_tolerance(
ret[4], raw_info[pinfo_map['paged_pool']],
tolerance)
compare_with_tolerance(
ret[5], raw_info[pinfo_map['peak_non_paged_pool']],
tolerance)
compare_with_tolerance(
ret[6], raw_info[pinfo_map['non_paged_pool']],
tolerance)
compare_with_tolerance(
ret[7], raw_info[pinfo_map['pagefile']],
tolerance)
compare_with_tolerance(
ret[8], raw_info[pinfo_map['peak_pagefile']],
tolerance)
compare_with_tolerance(
ret[9], raw_info[pinfo_map['mem_private']],
tolerance)
except AssertionError:
trace = traceback.format_exc()
msg = '%s\npid=%s, method=%r, ret_1=%r, ret_2=%r' % (
trace, p.pid, name, ret, nt)
trace, p.pid, name, ret, raw_info)
failures.append(msg)
break

Expand Down

0 comments on commit c10a7aa

Please sign in to comment.