Skip to content

Commit

Permalink
Issue 260: Implement mapped memory regions used by process for OS X.
Browse files Browse the repository at this point in the history
* svn:ignore: Updated to ignore *.pyc files.

* HISTORY: Added my name to the Windows support for Issues 150 and 206.  Also
   added my name to the OSX support for Issue 260.

* psutil/_psosx.py (get_memory_maps, nt_mmap_grouped, nt_mmap_ext): Added.

* psutil/_psutil_osx.h,
  psutil/_psutil_osx.c (get_process_memory_maps): Added.

* test/test_psutil.py
   (test_get_memory_pams): Removed.
   (test_get_memory_maps): Added.

* Note: As comment #25 says, to really finish this work we will need to write a
  parser for the dyld_shared_cache_*.map files to get the real dylib path for
  dylib files loaded from the dyld shared cache but this will end up as an
  enhancement request and not done as part of Issue 260.
  • Loading branch information
whitlockjc committed Jun 25, 2012
1 parent e235363 commit cf18ae5
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 9 deletions.
7 changes: 4 additions & 3 deletions HISTORY
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ NEW FEATURES
* Issue #245: (POSIX) Process.wait() incrementally consumes less CPU cycles.
* Issue #257: (Windows) removed Windows 2000 support.
* Issue #258: (Linux) Process.get_memory_info() is now 0.5x faster.
* Issue #260: process's mapped memory regions. (Windows patch by wj32.64)
* Issue #260: process's mapped memory regions. (Windows patch by wj32.64, OSX
patch by Jeremy Whitlock)
* Issue #262: (Windows) psutil.disk_partitions() was slow due to inspecting
the floppy disk drive also when "all" argument was False.
* Issue #273: psutil.get_process_list() is deprecated.
Expand Down Expand Up @@ -81,12 +82,12 @@ BUGFIXES

NEW FEATURES

* Issue 150: network I/O counters. (OSX patch by Jeremy Whitlock)
* Issue 150: network I/O counters. (OSX and Windows patch by Jeremy Whitlock)
* Issue 154: (FreeBSD) add support for process getcwd()
* Issue 157: (Windows) provide installer for Python 3.2 64-bit.
* Issue 198: Process.wait(timeout=0) can now be used to make wait() return
immediately.
* Issue 206: disk I/O counters. (OSX patch by Jeremy Whitlock)
* Issue 206: disk I/O counters. (OSX and Windows patch by Jeremy Whitlock)
* Issue 213: examples/iotop.py script.
* Issue 217: Process.get_connections() now has a "kind" argument to filter
for connections with different criteria.
Expand Down
9 changes: 9 additions & 0 deletions psutil/_psosx.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,12 @@ def get_process_threads(self):
ntuple = nt_thread(thread_id, utime, stime)
retlist.append(ntuple)
return retlist

nt_mmap_grouped = namedtuple('mmap',
'path rss private swapped dirtied ref_count shadow_depth')
nt_mmap_ext = namedtuple('mmap',
'addr perms path rss private swapped dirtied ref_count shadow_depth')

@wrap_exceptions
def get_memory_maps(self):
return _psutil_osx.get_process_memory_maps(self.pid)
130 changes: 130 additions & 0 deletions psutil/_psutil_osx.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@
#include <mach/host_info.h>
#include <mach/mach_host.h>
#include <mach/mach_traps.h>
#include <mach/mach_vm.h>
#include <mach/shared_memory_server.h>

#include <mach-o/loader.h>

#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOBlockStorageDriver.h>
Expand Down Expand Up @@ -190,6 +193,131 @@ get_process_tty_nr(PyObject* self, PyObject* args)
}


/*
* Return a list of tuples for every process memory maps.
* 'procstat' cmdline utility has been used as an example.
*/
static PyObject*
get_process_memory_maps(PyObject* self, PyObject* args)
{
char buf[PATH_MAX];
char addr_str[34];
char perms[8];
int pagesize = getpagesize();
long pid;
kern_return_t err = KERN_SUCCESS;
mach_port_t task;
uint32_t depth = 1;
vm_address_t address = 0;
vm_size_t size = 0;

PyObject* py_tuple = NULL;
PyObject* py_list = PyList_New(0);

if (! PyArg_ParseTuple(args, "l", &pid)) {
return NULL;
}

err = task_for_pid(mach_task_self(), pid, &task);

if (err != KERN_SUCCESS) {
if (! pid_exists(pid)) {
return NoSuchProcess();
}
// pid exists, so return AccessDenied error since task_for_pid() failed
return AccessDenied();
}

while (1) {
struct vm_region_submap_info_64 info;
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;

err = vm_region_recurse_64(task, &address, &size, &depth,
(vm_region_info_64_t)&info, &count);

if (err == KERN_INVALID_ADDRESS) {
break;
}

if (info.is_submap) {
depth++;
}
else {
// Free/Reset the char[]s to avoid weird paths
memset(buf, 0, sizeof(buf));
memset(addr_str, 0, sizeof(addr_str));
memset(perms, 0, sizeof(perms));

sprintf(addr_str, "%016x-%016x", address, address + size);
sprintf(perms, "%c%c%c/%c%c%c",
(info.protection & VM_PROT_READ) ? 'r' : '-',
(info.protection & VM_PROT_WRITE) ? 'w' : '-',
(info.protection & VM_PROT_EXECUTE) ? 'x' : '-',
(info.max_protection & VM_PROT_READ) ? 'r' : '-',
(info.max_protection & VM_PROT_WRITE) ? 'w' : '-',
(info.max_protection & VM_PROT_EXECUTE) ? 'x' : '-');

address += size;

err = proc_regionfilename(pid, address, buf, sizeof(buf));

if (info.share_mode == SM_COW && info.ref_count == 1) {
// Treat single reference SM_COW as SM_PRIVATE
info.share_mode = SM_PRIVATE;
}

if (strlen(buf) == 0) {
switch(info.share_mode) {
/*
case SM_LARGE_PAGE:
// Treat SM_LARGE_PAGE the same as SM_PRIVATE
// since they are not shareable and are wired.
*/
case SM_COW:
strcpy(buf, "[cow]");
break;
case SM_PRIVATE:
strcpy(buf, "[prv]");
break;
case SM_EMPTY:
strcpy(buf, "[nul]");
break;
case SM_SHARED:
case SM_TRUESHARED:
strcpy(buf, "[shm]");
break;
case SM_PRIVATE_ALIASED:
strcpy(buf, "[ali]");
break;
case SM_SHARED_ALIASED:
strcpy(buf, "[s/a]");
break;
default:
strcpy(buf, "[???]");
}
}

py_tuple = Py_BuildValue("sssIIIIIH",
addr_str, // "start-end" address
perms, // "rwx" permissions
buf, // path
info.pages_resident * pagesize, // rss
info.pages_shared_now_private * pagesize, // private
info.pages_swapped_out * pagesize, // swapped
info.pages_dirtied * pagesize, // dirtied
info.ref_count, // ref count
info.shadow_depth // shadow depth
);

PyList_Append(py_list, py_tuple);
Py_XDECREF(py_tuple);
}
}

return py_list;
}


/*
* Return a Python integer indicating the number of CPUs on the system.
*/
Expand Down Expand Up @@ -1512,6 +1640,8 @@ PsutilMethods[] =
"Get process TCP and UDP connections as a list of tuples"},
{"get_process_tty_nr", get_process_tty_nr, METH_VARARGS,
"Return process tty number as an integer"},
{"get_process_memory_maps", get_process_memory_maps, METH_VARARGS,
"Return a list of tuples for every process's memory map"},

// --- system-related functions

Expand Down
1 change: 1 addition & 0 deletions psutil/_psutil_osx.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ static PyObject* get_process_open_files(PyObject* self, PyObject* args);
static PyObject* get_process_connections(PyObject* self, PyObject* args);
static PyObject* get_process_num_fds(PyObject* self, PyObject* args);
static PyObject* get_process_tty_nr(PyObject* self, PyObject* args);
static PyObject* get_process_memory_maps(PyObject* self, PyObject* args);

// --- system-related functions
static PyObject* get_pid_list(PyObject* self, PyObject* args);
Expand Down
8 changes: 2 additions & 6 deletions test/test_psutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,10 +809,8 @@ def test_get_memory_info(self):
self.assertTrue(percent2 > percent1)
del memarr

def test_get_memory_pams(self):
sproc = get_test_subprocess()
p = psutil.Process(sproc.pid)
time.sleep(.1)
def test_get_memory_maps(self):
p = psutil.Process(os.getpid())
maps = p.get_memory_maps()
paths = [x for x in maps]
self.assertEqual(len(paths), len(set(paths)))
Expand All @@ -825,8 +823,6 @@ def test_get_memory_pams(self):
self.assertTrue(value >= 0, value)

ext_maps = p.get_memory_maps(grouped=False)
self.assertEqual(sum([x.rss for x in maps]),
sum([x.rss for x in ext_maps]))
for nt in ext_maps:
self.assertTrue(nt.addr)
for f in nt._fields:
Expand Down

0 comments on commit cf18ae5

Please sign in to comment.