Repository: giampaolo/psutil
Pull request #757 (2016-02-04 21:04:49)

Mem refactoring


Patch:
diff --git a/README.rst b/README.rst
index cebf841c..017a0d66 100644
--- a/README.rst
+++ b/README.rst
@@ -237,9 +237,11 @@ Process management
     0.63423
     >>>
     >>> p.memory_info()
-    pmem(rss=7471104, vms=68513792)
-    >>> p.memory_info_ex()
-    pextmem(rss=10915840, vms=67608576, shared=3313664, text=2310144, lib=0, data=7262208, dirty=0, uss=9830400, pss=1216512)
+    pmem(rss=10915840, vms=67608576, shared=3313664, text=2310144, lib=0, data=7262208, dirty=0)
+    >>>
+    >>> p.memory_addrspace_info()  # "real" memory usage (Linux, OSX, Win only)
+    paddrspmem(uss=9830400, pss=1216512, swap=0)
+    >>>
     >>> p.memory_maps()
     [pmmap_grouped(path='/lib/x8664-linux-gnu/libutil-2.15.so', rss=32768, size=2125824, pss=32768, shared_clean=0, shared_dirty=0, private_clean=20480, private_dirty=12288, referenced=32768, anonymous=12288, swap=0),
      pmmap_grouped(path='/lib/x8664-linux-gnu/libc-2.15.so', rss=3821568, size=3842048, pss=3821568, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=3821568, referenced=3575808, anonymous=3821568, swap=0),
diff --git a/docs/index.rst b/docs/index.rst
index 444c1849..2dfb1108 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -977,121 +977,134 @@ Process class
 
   .. method:: memory_info()
 
-     Return a tuple representing RSS (Resident Set Size) and VMS (Virtual
-     Memory Size) in bytes. On UNIX *rss* and *vms* are the same values shown
-     by `ps` or by RES and VIRT column of `top`.
-     On Windows *rss* and *vms* refer to "Mem Usage" and "VM Size"
-     columns of taskmgr.exe. For more detailed memory stats use
-     :meth:`memory_info_ex`.
-
-  .. method:: memory_info_ex()
-
      Return a namedtuple with variable fields depending on the platform
-     representing extended memory information about the process.
+     representing memory information about the process.
+     The "portable" fields available on all plaforms are `rss` and `vms`.
      All numbers are expressed in bytes.
 
-     +---------+---------+-------+---------+--------------------+
-     | Linux   | OSX     | BSD   | Solaris | Windows            |
-     +=========+=========+=======+=========+====================+
-     | rss     | rss     | rss   | rss     | num_page_faults    |
-     +---------+---------+-------+---------+--------------------+
-     | vms     | vms     | vms   | vms     | peak_wset          |
-     +---------+---------+-------+---------+--------------------+
-     | shared  | pfaults | text  |         | wset               |
-     +---------+---------+-------+---------+--------------------+
-     | text    | pageins | data  |         | peak_paged_pool    |
-     +---------+---------+-------+---------+--------------------+
-     | lib     | **uss** | stack |         | paged_pool         |
-     +---------+---------+-------+---------+--------------------+
-     | data    |         |       |         | peak_nonpaged_pool |
-     +---------+---------+-------+---------+--------------------+
-     | dirty   |         |       |         | nonpaged_pool      |
-     +---------+---------+-------+---------+--------------------+
-     | **uss** |         |       |         | pagefile           |
-     +---------+---------+-------+---------+--------------------+
-     | **pss** |         |       |         | peak_pagefile      |
-     +---------+---------+-------+---------+--------------------+
-     |         |         |       |         | private            |
-     +---------+---------+-------+---------+--------------------+
-     |         |         |       |         | **uss**            |
-     +---------+---------+-------+---------+--------------------+
-
-     .. note::
-       the most representative value for determining how much memory is
-       used by a process on Linux, OSX and Windows is probably *uss*, which
-       is the amount of memory that would be freed if the process was
-       terminated right now.
-       Also *pss* on Linux is useful (read later).
-       :meth:`memory_info_ex` method calculates these two values separately, by
-       passing through process address space (which is quite expensive BTW).
-       If this cannot be done due to lack of permissions `uss` and `pss` will
-       be set to `0` (instead of raising :class:`psutil.AccessDenied`).
-
-     **Linux, OSX, Windows**
-
-     - **uss**: aka "Unique Set Size" this is the set of
-       pages that are unique to a process. This is the amount of memory that
-       would be freed if the process was terminated right now.
-       It will be set to `0` if it cannot be determined due to permission
-       issues.
-
-     **Linux**
-
-     - **pss**: aka "Proportional Set Size", is the amount of memory shared
-       with other processes, accounted in a way that the amount is divided
-       evenly between the processes that share it.
-       I.e. if a process has 10 MBs all to itself, and 10 MBs shared with
-       another process, its PSS will be 15 MBs.
-       "pss" value can be set to `0` if it cannot be determined due to
-       permission issues.
-
-     **UNIX**
+     +---------+---------+-------+---------+------------------------------+
+     | Linux   | OSX     | BSD   | Solaris | Windows                      |
+     +=========+=========+=======+=========+==============================+
+     | rss     | rss     | rss   | rss     | rss (alias for ``wset``)     |
+     +---------+---------+-------+---------+------------------------------+
+     | vms     | vms     | vms   | vms     | vms (alias for ``pagefile``) |
+     +---------+---------+-------+---------+------------------------------+
+     | shared  | pfaults | text  |         | num_page_faults              |
+     +---------+---------+-------+---------+------------------------------+
+     | text    | pageins | data  |         | peak_wset                    |
+     +---------+---------+-------+---------+------------------------------+
+     | lib     |         | stack |         | wset                         |
+     +---------+---------+-------+---------+------------------------------+
+     | data    |         |       |         | peak_paged_pool              |
+     +---------+---------+-------+---------+------------------------------+
+     | dirty   |         |       |         | paged_pool                   |
+     +---------+---------+-------+---------+------------------------------+
+     |         |         |       |         | peak_nonpaged_pool           |
+     +---------+---------+-------+---------+------------------------------+
+     |         |         |       |         | nonpaged_pool                |
+     +---------+---------+-------+---------+------------------------------+
+     |         |         |       |         | pagefile                     |
+     +---------+---------+-------+---------+------------------------------+
+     |         |         |       |         | peak_pagefile                |
+     +---------+---------+-------+---------+------------------------------+
+     |         |         |       |         | private                      |
+     +---------+---------+-------+---------+------------------------------+
 
      - **rss**: aka "Resident Set Size", this is the non-swapped physical
        memory a process has used.
-       It matches "top"'s RES column
+       On UNIX it matches "top"'s RES column
        (see `doc <http://linux.die.net/man/1/top>`__).
+       On Windows this is an alias for `wset` field and it matches "Mem Usage"
+       column of taskmgr.exe.
+
      - **vms**: aka "Virtual Memory Size", this is the total amount of virtual
-       memory used by the process. This matches "top"'s VIRT column
+       memory used by the process.
+       On UNIX it matches "top"'s VIRT column
        (see `doc <http://linux.die.net/man/1/top>`__).
+       On Windows this is an alias for `pagefile` field and it matches
+       "Mem Usage" "VM Size" column of taskmgr.exe.
+
      - **shared**: (Linux)
        memory that could be potentially shared with other processes.
        This matches "top"'s SHR column
        (see `doc <http://linux.die.net/man/1/top>`__).
+
      - **text**: (Linux, BSD)
        aka TRS (text resident set) the amount of memory devoted to
        executable code. This matches "top"'s CODE column
        (see `doc <http://linux.die.net/man/1/top>`__).
+
      - **data**: (Linux, BSD)
        aka DRS (data resident set) the amount of physical memory devoted to
        other than executable code. It matches "top"'s DATA column
        (see `doc <http://linux.die.net/man/1/top>`__).
-     - **lib**: (Linux) the memory used by shared libraries.
-     - **dirty**: (Linux) the number of dirty pages.
 
-     **Windows**
+     - **lib**: (Linux) the memory used by shared libraries.
 
-       For Windows fields rely on
-       `PROCESS_MEMORY_COUNTERS_EX <http://msdn.microsoft.com/en-us/library/windows/desktop/ms684874(v=vs.85).aspx>`__ structure doc.
+     - **dirty**: (Linux) the number of dirty pages.
 
+     For Windows fields rely on
+     `PROCESS_MEMORY_COUNTERS_EX <http://msdn.microsoft.com/en-us/library/windows/desktop/ms684874(v=vs.85).aspx>`__ structure doc.
      Example on Linux:
 
      >>> import psutil
      >>> p = psutil.Process()
-     >>> p.memory_info_ex()
-     pextmem(rss=15491072, vms=84025344, shared=5206016, text=2555904, lib=0, data=9891840, dirty=0, uss=7168000, pss=3653632)
+     >>> p.memory_info()
+     pmem(rss=15491072, vms=84025344, shared=5206016, text=2555904, lib=0, data=9891840, dirty=0)
+
+     .. versionchanged:: 3.5.0 mutiple fields are returned, not only `rss` and
+        `vms`.
+
+  .. method:: memory_info_ex()
+
+     Same as :meth:`memory_info` (deprecated).
+
+     .. warning:: depcreated in version 3.5.0; use :meth:`memory_info` instead.
+
+  .. method:: memory_addrspace_info()
+
+     This method passes through the whole process address space in order to
+     calculate highly reliable metrics about "real" process memory consumption.
+     It usually requires higher privileges and is considerably slower than
+     :meth:`memory_info`.
+
+     - **uss**: (Linux, Windows, OSX) aka "Unique Set Size", this is the memory
+       which is unique to a process and which would be freed if the process was terminated right now.
+
+     - **pss**: (Linux) aka "Proportional Set Size", is the amount of memory
+       shared with other processes, accounted in a way that the amount is
+       divided evenly between the processes that share it.
+       I.e. if a process has 10 MBs all to itself, and 10 MBs shared with
+       another process, its PSS will be 15 MBs.
 
-     .. versionchanged:: 3.5.0 added `uss` field on Linux, OSX and Windows.
-     .. versionchanged:: 3.5.0 added `pss` field on Linux.
+      - **swap**: (Linux) amount memory that has been swapped out to disk.
+
+     .. note::
+       `uss` is probably the most representative metric for determining how
+       much memory is being used by a process.
+       It represents the amount of memory that would be freed if the process
+       was terminated right now.
+
+     Example on Linux:
+
+       >>> import psutil
+       >>> p = psutil.Process()
+       >>> p.memory_addrspace_info()
+       paddrspmem(uss=7421952, pss=7681024, swap=0)
+       >>>
+
+     .. versionadded:: 3.5.0
+
+     Availability: Linux, OSX, Windows
 
   .. method:: memory_percent(memtype="rss")
 
      Compare process memory to total physical system memory and calculate
      process memory utilization as a percentage.
      *memtype* argument is a string that dictates what type of process memory
-     you want to compare against (defaults to *"rss"*).
-     The list of available strings can be obtained like this:
-     ``psutil.Process().memory_info_ex()._fields``.
+     you want to compare against. You can choose between the namedtuple field
+     names returned by :meth:`memory_info` and :meth:`memory_addrspace_info`
+     (defaults to *"rss"*).
 
      .. versionchanged:: 3.5.0 added `memtype` parameter.
 
@@ -1105,7 +1118,7 @@ Process class
     (the most important value is "private" memory).
     If *grouped* is ``True`` the mapped regions with the same *path* are
     grouped together and the different memory fields are summed.  If *grouped*
-    is ``False`` every mapped region is shown as a single entity and the
+    is ``False`` each mapped region is shown as a single entity and the
     namedtuple will also include the mapped region's address space (*addr*)
     and permission set (*perms*).
     See `examples/pmap.py <https://github.com/giampaolo/psutil/blob/master/examples/pmap.py>`__
diff --git a/psutil/__init__.py b/psutil/__init__.py
index d2ded8e4..5b7ee603 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -26,6 +26,7 @@ except ImportError:
     pwd = None
 
 from . import _common
+from ._common import deprecated_method
 from ._common import memoize
 from ._compat import callable
 from ._compat import long
@@ -955,22 +956,30 @@ class Process(object):
         return self._proc.cpu_times()
 
     def memory_info(self):
-        """Return a tuple representing RSS (Resident Set Size) and VMS
-        (Virtual Memory Size) in bytes.
+        """Return a namedtuple with variable fields depending on the
+        platform, representing memory information about the process.
 
-        On UNIX RSS and VMS are the same values shown by 'ps'.
+        The "portable" fields available on all plaforms are `rss` and `vms`.
 
-        On Windows RSS and VMS refer to "Mem Usage" and "VM Size"
-        columns of taskmgr.exe.
+        All numbers are expressed in bytes.
         """
         return self._proc.memory_info()
 
+    @deprecated_method(replacement="memory_info")
     def memory_info_ex(self):
-        """Return a namedtuple with variable fields depending on the
-        platform representing extended memory information about
-        this process. All numbers are expressed in bytes.
-        """
-        return self._proc.memory_info_ex()
+        return self.memory_info()
+
+    if hasattr(_psplatform.Process, "memory_addrspace_info"):
+
+        def memory_addrspace_info(self):
+            """This method passes through the whole process address space
+            in order to calculate highly reliable metrics about "real"
+            process memory consumption (USS and PSS).
+
+            It usually requires higher privileges and is considerably
+            slower than memory_info().
+            """
+            return self._proc.memory_addrspace_info()
 
     def memory_percent(self, memtype="rss"):
         """Compare process memory to total physical system memory and
@@ -979,20 +988,26 @@ class Process(object):
         process memory you want to compare against (defaults to "rss").
         The list of available strings can be obtained like this:
 
-        >>> psutil.Process().memory_info_ex()._fields
+        >>> psutil.Process().memory_info()._fields
         ('rss', 'vms', 'shared', 'text', 'lib', 'data', 'dirty', 'uss', 'pss')
         """
-        if memtype in ("rss", "vsz"):
-            value = getattr(self.memory_info(), memtype)
+        if memtype in ('uss', 'pss', 'swap'):
+            if not hasattr(self, "memory_addrspace_info"):
+                fields = _psplatform.pmem._fields
+                raise ValueError(
+                    "invalid memtype %r; valid types are %r" % (
+                        memtype, fields))
+            fun = self.memory_addrspace_info
+            fields = _psplatform.paddrspmem._fields
         else:
-            memex = self.memory_info_ex()
-            if memtype not in memex._fields:
-                raise ValueError("invalid memtype %r; valid types are %r" % (
-                    memtype, memex._fields))
-            value = getattr(memex, memtype)
-            if value == 0 and memtype in ('uss', 'pss'):
-                raise AccessDenied(self.pid, self._name,
-                                   msg="can't retrieve %s memory" % memtype)
+            fields = _psplatform.pmem._fields
+            fun = self.memory_info
+
+        if memtype not in fields:
+            raise ValueError("invalid memtype %r; valid types are %r" % (
+                memtype, fields))
+        metrics = fun()
+        value = getattr(metrics, memtype)
 
         # use cached value if available
         total_phymem = _TOTAL_PHYMEM or virtual_memory().total
@@ -1984,7 +1999,7 @@ def test():  # pragma: no cover
                 pinfo['name'].strip() or '?'))
 
 
-del memoize, division
+del memoize, division, deprecated_method
 if sys.version_info < (3, 0):
     del num
 
diff --git a/psutil/_common.py b/psutil/_common.py
index ef209343..92b3b5e1 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -13,6 +13,7 @@ import os
 import socket
 import stat
 import sys
+import warnings
 from collections import namedtuple
 from socket import AF_INET
 from socket import SOCK_DGRAM
@@ -229,6 +230,24 @@ def socktype_to_enum(num):
         return num
 
 
+def deprecated_method(replacement):
+    """A decorator which can be used to mark a method as deprecated
+    'replcement' is the method name which will be called instead.
+    """
+    def outer(fun):
+        msg = "%s() is deprecated; use %s() instead" % (
+            fun.__name__, replacement)
+        if fun.__doc__ is None:
+            fun.__doc__ = msg
+
+        @functools.wraps(fun)
+        def inner(self, *args, **kwargs):
+            warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
+            return getattr(self, replacement)(*args, **kwargs)
+        return inner
+    return outer
+
+
 # --- Process.connections() 'kind' parameter mapping
 
 conn_tmap = {
@@ -287,8 +306,6 @@ snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu'])
 
 # --- namedtuples for psutil.Process methods
 
-# psutil.Process.memory_info()
-pmem = namedtuple('pmem', ['rss', 'vms'])
 # psutil.Process.cpu_times()
 pcputimes = namedtuple('pcputimes', ['user', 'system'])
 # psutil.Process.open_files()
diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py
index 5397026f..4806161b 100644
--- a/psutil/_psbsd.py
+++ b/psutil/_psbsd.py
@@ -98,7 +98,7 @@ svmem = namedtuple(
               'active', 'inactive', 'buffers', 'cached', 'shared', 'wired'])
 scputimes = namedtuple(
     'scputimes', ['user', 'nice', 'system', 'idle', 'irq'])
-pextmem = namedtuple('pextmem', ['rss', 'vms', 'text', 'data', 'stack'])
+pmem = namedtuple('pmem', ['rss', 'vms', 'text', 'data', 'stack'])
 pmmap_grouped = namedtuple(
     'pmmap_grouped', 'path rss, private, ref_count, shadow_count')
 pmmap_ext = namedtuple(
@@ -432,12 +432,7 @@ class Process(object):
 
     @wrap_exceptions
     def memory_info(self):
-        rss, vms = cext.proc_memory_info(self.pid)[:2]
-        return _common.pmem(rss, vms)
-
-    @wrap_exceptions
-    def memory_info_ex(self):
-        return pextmem(*cext.proc_memory_info(self.pid))
+        return pmem(*cext.proc_memory_info(self.pid))
 
     @wrap_exceptions
     def create_time(self):
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 6865efb1..11e21fd7 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -218,7 +218,8 @@ svmem = namedtuple(
     'svmem', ['total', 'available', 'percent', 'used', 'free',
               'active', 'inactive', 'buffers', 'cached'])
 
-pextmem = namedtuple('pextmem', 'rss vms shared text lib data dirty uss pss')
+pmem = namedtuple('pmem', 'rss vms shared text lib data dirty')
+paddrspmem = namedtuple('paddrspmem', ['uss', 'pss', 'swap'])
 
 pmmap_grouped = namedtuple(
     'pmmap_grouped', ['path', 'rss', 'size', 'pss', 'shared_clean',
@@ -953,15 +954,6 @@ class Process(object):
 
     @wrap_exceptions
     def memory_info(self):
-        with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f:
-            vms, rss = f.readline().split()[:2]
-            return _common.pmem(int(rss) * PAGESIZE,
-                                int(vms) * PAGESIZE)
-
-    @wrap_exceptions
-    def memory_info_ex(self,
-                       _private_re=re.compile(b"Private.*:\s+(\d+)"),
-                       _pss_re=re.compile(b"Pss.*:\s+(\d+)")):
         #  ============================================================
         # | FIELD  | DESCRIPTION                         | AKA  | TOP  |
         #  ============================================================
@@ -972,38 +964,31 @@ class Process(object):
         # | lib    | library (unused in Linux 2.6)       | lrs  |      |
         # | data   | data + stack                        | drs  | DATA |
         # | dirty  | dirty pages (unused in Linux 2.6)   | dt   |      |
-        # | -----------------------------------------------------------
-        # | uss    | unique set size ("real memory")     |      |      |
-        # | pss    | proportional set size               |      |      |
         #  ============================================================
         with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f:
             vms, rss, shared, text, lib, data, dirty = \
                 [int(x) * PAGESIZE for x in f.readline().split()[:7]]
+            return pmem(rss, vms, shared, text, lib, data, dirty)
 
-        uss = pss = 0
-        if HAS_SMAPS:
+    if HAS_SMAPS:
+
+        @wrap_exceptions
+        def memory_addrspace_info(
+                self,
+                _private_re=re.compile(b"Private.*:\s+(\d+)"),
+                _pss_re=re.compile(b"Pss.*:\s+(\d+)"),
+                _swap_re=re.compile(b"Swap.*:\s+(\d+)")):
             # Note: using two regexes is faster than reading the file
             # line by line.
             # XXX: on Python 3 the 2 regexes are 30% slower than on
             # Python 2 though. Figure out why.
-            try:
-                with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid),
-                                 buffering=BIGGER_FILE_BUFFERING) as f:
-                    smaps_data = f.read()
-            except EnvironmentError as err:
-                if err.errno not in (errno.EPERM, errno.EACCES):
-                    raise
-            else:
-                uss = sum(map(int, _private_re.findall(smaps_data))) * 1024
-                pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024
-        else:
-            # usually means we're on kernel < 2.6.14 or CONFIG_MMU kernel
-            # configuration option is not enabled.
-            pass
-
-        return pextmem(rss, vms, shared, text, lib, data, dirty, uss, pss)
-
-    if HAS_SMAPS:
+            with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid),
+                             buffering=BIGGER_FILE_BUFFERING) as f:
+                smaps_data = f.read()
+            uss = sum(map(int, _private_re.findall(smaps_data))) * 1024
+            pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024
+            swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024
+            return paddrspmem(uss, pss, swap)
 
         @wrap_exceptions
         def memory_maps(self):
diff --git a/psutil/_psosx.py b/psutil/_psosx.py
index 420b5109..113d5f32 100644
--- a/psutil/_psosx.py
+++ b/psutil/_psosx.py
@@ -58,7 +58,8 @@ svmem = namedtuple(
     'svmem', ['total', 'available', 'percent', 'used', 'free',
               'active', 'inactive', 'wired'])
 
-pextmem = namedtuple('pextmem', ['rss', 'vms', 'pfaults', 'pageins', 'uss'])
+pmem = namedtuple('pmem', ['rss', 'vms', 'pfaults', 'pageins'])
+paddrspmem = namedtuple('paddrspmem', 'uss')
 
 pmmap_grouped = namedtuple(
     'pmmap_grouped',
@@ -269,19 +270,13 @@ class Process(object):
 
     @wrap_exceptions
     def memory_info(self):
-        rss, vms = cext.proc_memory_info(self.pid)[:2]
-        return _common.pmem(rss, vms)
+        rss, vms, pfaults, pageins = cext.proc_memory_info(self.pid)
+        return pmem(rss, vms, pfaults, pageins)
 
     @wrap_exceptions
-    def memory_info_ex(self):
-        rss, vms, pfaults, pageins = cext.proc_memory_info(self.pid)
-        uss = 0
-        try:
-            uss = cext.proc_memory_uss(self.pid)
-        except OSError as err:
-            if err.errno not in (errno.EPERM, errno.EACCES):
-                raise
-        return pextmem(rss, vms, pfaults * PAGESIZE, pageins * PAGESIZE, uss)
+    def memory_addrspace_info(self):
+        uss = cext.proc_memory_uss(self.pid)
+        return paddrspmem(uss)
 
     @wrap_exceptions
     def cpu_times(self):
diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py
index 74040d9a..41ebdbb8 100644
--- a/psutil/_pssunos.py
+++ b/psutil/_pssunos.py
@@ -60,7 +60,7 @@ TCP_STATUSES = {
 
 scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
 svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
-pextmem = namedtuple('pextmem', ['rss', 'vms'])
+pmem = namedtuple('pmem', ['rss', 'vms'])
 pmmap_grouped = namedtuple('pmmap_grouped',
                            ['path', 'rss', 'anonymous', 'locked'])
 pmmap_ext = namedtuple(
@@ -407,9 +407,6 @@ class Process(object):
         rss, vms = ret[1] * 1024, ret[2] * 1024
         return _common.pmem(rss, vms)
 
-    # it seems Solaris uses rss and vms only
-    memory_info_ex = memory_info
-
     @wrap_exceptions
     def status(self):
         code = cext.proc_basic_info(self.pid, self._procfs_path)[6]
diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py
index 9635a3d4..3f1a3cc2 100644
--- a/psutil/_pswindows.py
+++ b/psutil/_pswindows.py
@@ -84,10 +84,11 @@ if enum is not None:
 
 scputimes = namedtuple('scputimes', ['user', 'system', 'idle'])
 svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
-pextmem = namedtuple(
-    'pextmem', ['num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool',
-                'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool',
-                'pagefile', 'peak_pagefile', 'private', 'uss'])
+pmem = namedtuple(
+    'pmem', ['num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool',
+             'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool',
+             'pagefile', 'peak_pagefile', 'private'])
+paddrspmem = namedtuple('paddrspmem', 'uss')
 pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss'])
 pmmap_ext = namedtuple(
     'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
@@ -359,14 +360,6 @@ class Process(object):
                 return cext.proc_memory_info_2(self.pid)
             raise
 
-    def _get_uss_mem(self):
-        try:
-            return cext.proc_memory_uss(self.pid)
-        except OSError as err:
-            if err.errno in ACCESS_DENIED_SET:
-                return 0
-            raise
-
     @wrap_exceptions
     def memory_info(self):
         # on Windows RSS == WorkingSetSize and VSM == PagefileUsage
@@ -377,10 +370,9 @@ class Process(object):
         return _common.pmem(t[2], t[7])
 
     @wrap_exceptions
-    def memory_info_ex(self):
-        info = self._get_raw_meminfo()
-        uss = self._get_uss_mem()
-        return pextmem(*info + (uss, ))
+    def memory_addrspace_info(self):
+        uss = cext.proc_memory_uss(self.pid)
+        return paddrspmem(uss)
 
     def memory_maps(self):
         try:
diff --git a/setup.py b/setup.py
index d5c66e81..35582c19 100644
--- a/setup.py
+++ b/setup.py
@@ -222,7 +222,7 @@ def main():
             'ps', 'top', 'kill', 'free', 'lsof', 'netstat', 'nice', 'tty',
             'ionice', 'uptime', 'taskmgr', 'process', 'df', 'iotop', 'iostat',
             'ifconfig', 'taskset', 'who', 'pidof', 'pmap', 'smem', 'pstree',
-            'monitoring', 'ulimit', 'prlimit',
+            'monitoring', 'ulimit', 'prlimit', 'smem',
         ],
         author='Giampaolo Rodola',
         author_email='g.rodola <at> gmail <dot> com',


Test patch:
diff --git a/test/_linux.py b/test/_linux.py
index 637ad1f7..7baa7d00 100644
--- a/test/_linux.py
+++ b/test/_linux.py
@@ -599,7 +599,7 @@ class LinuxSpecificTestCase(unittest.TestCase):
             self.assertEqual(psutil.Process().exe(), "/home/foo")
             self.assertEqual(psutil.Process().cwd(), "/home/foo")
 
-    def test_uss_pss_mem_against_mem_maps(self):
+    def test_memory_addrspace_info(self):
         src = textwrap.dedent("""
             import time
             with open("%s", "w") as f:
@@ -610,12 +610,14 @@ class LinuxSpecificTestCase(unittest.TestCase):
         call_until(lambda: os.listdir('.'), "'%s' not in ret" % TESTFN)
         p = psutil.Process(sproc.pid)
         time.sleep(.1)
-        memex = p.memory_info_ex()
+        mem = p.memory_addrspace_info()
         maps = p.memory_maps(grouped=False)
         self.assertEqual(
-            memex.uss, sum([x.private_dirty + x.private_clean for x in maps]))
+            mem.uss, sum([x.private_dirty + x.private_clean for x in maps]))
         self.assertEqual(
-            memex.pss, sum([x.shared_dirty + x.shared_clean for x in maps]))
+            mem.pss, sum([x.pss for x in maps]))
+        self.assertEqual(
+            mem.swap, sum([x.swap for x in maps]))
 
 
 def main():
diff --git a/test/test_memory_leaks.py b/test/test_memory_leaks.py
index d79d5900..20078373 100644
--- a/test/test_memory_leaks.py
+++ b/test/test_memory_leaks.py
@@ -229,9 +229,11 @@ class TestProcessObjectLeaks(Base):
     def test_memory_info(self):
         self.execute('memory_info')
 
-    @skip_if_linux()
-    def test_memory_info_ex(self):
-        self.execute('memory_info_ex')
+    # also available on Linux but it's pure python
+    @unittest.skipUnless(OSX or WINDOWS,
+                         "not available on this platform")
+    def test_memory_addrspace_info(self):
+        self.execute('memory_addrspace_info')
 
     @unittest.skipUnless(POSIX, "POSIX only")
     @skip_if_linux()
diff --git a/test/test_psutil.py b/test/test_psutil.py
index 158f3458..e8017cf3 100644
--- a/test/test_psutil.py
+++ b/test/test_psutil.py
@@ -1675,7 +1675,7 @@ class TestProcess(unittest.TestCase):
         p = psutil.Process()
 
         # step 1 - get a base value to compare our results
-        rss1, vms1 = p.memory_info()
+        rss1, vms1 = p.memory_info()[:2]
         percent1 = p.memory_percent()
         self.assertGreater(rss1, 0)
         self.assertGreater(vms1, 0)
@@ -1683,18 +1683,23 @@ class TestProcess(unittest.TestCase):
         # step 2 - allocate some memory
         memarr = [None] * 1500000
 
-        rss2, vms2 = p.memory_info()
+        rss2, vms2 = p.memory_info()[:2]
         percent2 = p.memory_percent()
-        # make sure that the memory usage bumped up
+
+        # step 3 - make sure that the memory usage bumped up
         self.assertGreater(rss2, rss1)
         self.assertGreaterEqual(vms2, vms1)  # vms might be equal
         self.assertGreater(percent2, percent1)
         del memarr
 
-    def test_memory_info_ex(self):
-        memex = psutil.Process().memory_info_ex()
-        if LINUX or OSX or WINDOWS:
-            self.assertGreater(memex.uss, 0)
+    @unittest.skipUnless(LINUX or OSX or WINDOWS,
+                         "not available on this platform")
+    def test_memory_addrspace_info(self):
+        mem = psutil.Process().memory_addrspace_info()
+        self.assertGreater(mem.uss, 0)
+        if LINUX:
+            self.assertGreater(mem.pss, 0)
+            self.assertGreater(mem.pss, mem.uss)
 
     @unittest.skipIf(OPENBSD or NETBSD, "not available on this platform")
     def test_memory_maps(self):
@@ -1733,20 +1738,12 @@ class TestProcess(unittest.TestCase):
         assert 0 <= ret <= 100, ret
         ret = p.memory_percent(memtype='vms')
         assert 0 <= ret <= 100, ret
-        memtype = psutil._psplatform.pextmem._fields[-1]
-        ret = p.memory_percent(memtype=memtype)
         assert 0 <= ret <= 100, ret
         self.assertRaises(ValueError, p.memory_percent, memtype="?!?")
-
-    @unittest.skipUnless(LINUX or OSX or WINDOWS,
-                         "uss not available on this plaftorm")
-    def test_memory_percent_uss_ad(self):
-        ret = collections.namedtuple("pextm", "rss vms uss")(1, 1, 0)
-        with mock.patch("psutil._psplatform.Process.memory_info_ex",
-                        return_value=ret):
-            p = psutil.Process()
-            self.assertRaises(
-                psutil.AccessDenied, p.memory_percent, memtype="uss")
+        if LINUX or OSX or WINDOWS:
+            ret = p.memory_percent(memtype='uss')
+            assert 0 <= ret <= 100, ret
+            assert 0 <= ret <= 100, ret
 
     def test_is_running(self):
         sproc = get_test_subprocess(wait=True)
@@ -2597,7 +2594,9 @@ class TestFetchAllProcesses(unittest.TestCase):
         valid_procs = 0
         excluded_names = set([
             'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait',
-            'as_dict', 'cpu_percent', 'parent', 'children', 'pid'])
+            'as_dict', 'cpu_percent', 'parent', 'children', 'pid',
+            'memory_info_ex',
+        ])
         if LINUX and not RLIMIT_SUPPORT:
             excluded_names.add('rlimit')
         attrs = []
@@ -2747,12 +2746,8 @@ class TestFetchAllProcesses(unittest.TestCase):
         self.assertTrue(ret.system >= 0)
 
     def memory_info(self, ret, proc):
-        self.assertTrue(ret.rss >= 0)
-        self.assertTrue(ret.vms >= 0)
-
-    def memory_info_ex(self, ret, proc):
         for name in ret._fields:
-            self.assertTrue(getattr(ret, name) >= 0)
+            self.assertGreaterEqual(getattr(ret, name), 0)
         if POSIX and ret.vms != 0:
             # VMS is always supposed to be the highest
             for name in ret._fields:
@@ -2765,6 +2760,12 @@ class TestFetchAllProcesses(unittest.TestCase):
             assert ret.peak_nonpaged_pool >= ret.nonpaged_pool, ret
             assert ret.peak_pagefile >= ret.pagefile, ret
 
+    def memory_addrspace_info(self, ret, proc):
+        for name in ret._fields:
+            self.assertGreaterEqual(getattr(ret, name), 0)
+        if LINUX:
+            self.assertGreaterEqual(ret.pss, ret.uss)
+
     def open_files(self, ret, proc):
         for f in ret:
             if WINDOWS: