Repository: giampaolo/psutil
Pull request #937 (2016-11-05 01:16:48)

Oneshot ctx manager
See #799 


Patch:
diff --git a/Makefile b/Makefile
index 13e20867..c91d3b96 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@ DEPS = argparse \
 	ipaddress \
 	mock==1.0.1 \
 	pep8 \
+	perf \
 	pyflakes \
 	requests \
 	setuptools \
@@ -240,3 +241,11 @@ print-announce:
 
 grep-todos:
 	git grep -EIn "TODO|FIXME|XXX"
+
+# run script which benchmarks oneshot() ctx manager (see #799)
+bench-oneshot: install
+	$(PYTHON) scripts/internal/bench_oneshot.py
+
+# same as above but using perf module (supposed to be more precise)
+bench-oneshot-2: install
+	$(PYTHON) scripts/internal/bench_oneshot_2.py
diff --git a/docs/index.rst b/docs/index.rst
index be6663e5..7e32bc6b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -736,6 +736,12 @@ Process class
   and creation time). As such it can also be used with
   `set()s <http://docs.python.org/2/library/stdtypes.html#types-set>`__.
 
+  .. note::
+
+    In order to efficiently fetch more than one information about the process
+    at the same time, make sure to use either :meth:`as_dict` or
+    :meth:`oneshot` context manager.
+
   .. warning::
 
     the way this class is bound to a process is via its **PID**.
@@ -760,9 +766,84 @@ Process class
     :meth:`is_running()` before querying the process or use
     :func:`process_iter()` in case you're iterating over all processes.
 
+  .. method:: oneshot()
+
+    Utility context manager which considerably speeds up the retrieval of
+    multiple process information at the same time.
+    Internally different process info (e.g. :meth:`name`, :meth:`ppid`,
+    :meth:`uids`, :meth:`create_time`, ...) may be fetched by using the same
+    routine, but only one data is returned and the others are discarded.
+    When using this context manager the internal routine is executed once (in
+    the example below on :meth:`name()`) and the other info are cached.
+    The subsequent calls sharing the same internal routine will return the
+    cached value.
+    The cache is cleared when exiting the context manager block.
+    The advice is to use this every time you retrieve more than one information
+    about the process. If you're lucky, you'll get a hell of a speedup.
+    Example:
+
+    >>> import psutil
+    >>> p = psutil.Process()
+    >>> with p.oneshot():
+    ...     p.name()  # execute internal routine once collecting multiple info
+    ...     p.cpu_times()  # return cached value
+    ...     p.cpu_percent()  # return cached value
+    ...     p.create_time()  # return cached value
+    ...     p.ppid()  # return cached value
+    ...     p.status()  # return cached value
+    ...
+    >>>
+
+    Here's a list of methods which can take advantage of the speedup depending
+    on what platform you're on.
+    In the table below horizontal emtpy rows indicate what process methods can
+    be efficiently grouped together internally.
+    The last column (speedup) shows an approximation of the speedup you can get
+    if you call all the methods together (best case scenario).
+
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | Linux                        | Windows                       | OSX                          | BSD                          | SunOS                    |
+    +==============================+===============================+==============================+==============================+==========================+
+    | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent`  | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`name`             |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`~Process.cpu_times`   | :meth:`~Process.cpu_times`    | :meth:`~Process.cpu_times`   | :meth:`~Process.cpu_times`   | :meth:`cmdline`          |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`create_time`          | :meth:`io_counters()`         | :meth:`memory_info`          | :meth:`create_time`          | :meth:`create_time`      |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`name`                 | :meth:`ionice`                | :meth:`memory_percent`       | :meth:`gids`                 |                          |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`ppid`                 | :meth:`memory_info`           | :meth:`num_ctx_switches`     | :meth:`io_counters`          | :meth:`memory_info`      |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`status`               | :meth:`nice`                  | :meth:`num_threads`          | :meth:`name`                 | :meth:`memory_percent`   |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`terminal`             | :meth:`memory_maps`           |                              | :meth:`memory_info`          | :meth:`nice`             |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    |                              | :meth:`num_ctx_switches`      | :meth:`create_time`          | :meth:`memory_percent`       | :meth:`num_threads`      |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`gids`                 | :meth:`num_handles`           | :meth:`gids`                 | :meth:`num_ctx_switches`     | :meth:`ppid`             |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`num_ctx_switches`     | :meth:`num_threads`           | :meth:`name`                 | :meth:`ppid`                 | :meth:`status`           |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`num_threads`          | :meth:`username`              | :meth:`ppid`                 | :meth:`status`               | :meth:`terminal`         |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`uids`                 |                               | :meth:`status`               | :meth:`terminal`             |                          |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`username`             |                               | :meth:`terminal`             | :meth:`uids`                 | :meth:`gids`             |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    |                              |                               | :meth:`uids`                 | :meth:`username`             | :meth:`uids`             |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`memory_full_info`     |                               | :meth:`username`             |                              | :meth:`username`         |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | :meth:`memory_maps`          |                               |                              |                              |                          |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+    | *speedup: +2.5x*             | *speedup: +1.8x / +6.5x*      | *speedup: +1.9x*             | *speedup: +2.0x*             | *speedup: +1.3x*         |
+    +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+
+     .. versionadded:: 5.0.0
+
   .. attribute:: pid
 
-    The process PID.
+     The process PID. This is the only (read-only) attribute of the class.
 
   .. method:: ppid()
 
@@ -815,6 +896,8 @@ Process class
     value which gets assigned to a dict key in case :class:`AccessDenied`
     or :class:`ZombieProcess` exception is raised when retrieving that
     particular process information.
+    Internally, :meth:`as_dict` uses :meth:`oneshot` context manager so
+    there's no need you use it also.
 
       >>> import psutil
       >>> p = psutil.Process()
@@ -825,6 +908,9 @@ Process class
       3.0.0 *ad_value* is used also when incurring into
       :class:`ZombieProcess` exception, not only :class:`AccessDenied`
 
+     .. versionchanged:: 4.5.0 :meth:`as_dict` is considerably faster thanks
+        to :meth:`oneshot` context manager.
+
   .. method:: parent()
 
     Utility method which returns the parent process as a :class:`Process`
diff --git a/docs/make.bat b/docs/make.bat
index 9bc67515..185fc951 100644
--- a/docs/make.bat
+++ b/docs/make.bat
@@ -1,242 +1,32 @@
-@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
-)
-set BUILDDIR=_build
-set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
-set I18NSPHINXOPTS=%SPHINXOPTS% .
-if NOT "%PAPER%" == "" (
-	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
-	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
-	:help
-	echo.Please use `make ^<target^>` where ^<target^> is one of
-	echo.  html       to make standalone HTML files
-	echo.  dirhtml    to make HTML files named index.html in directories
-	echo.  singlehtml to make a single large HTML file
-	echo.  pickle     to make pickle files
-	echo.  json       to make JSON files
-	echo.  htmlhelp   to make HTML files and a HTML help project
-	echo.  qthelp     to make HTML files and a qthelp project
-	echo.  devhelp    to make HTML files and a Devhelp project
-	echo.  epub       to make an epub
-	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
-	echo.  text       to make text files
-	echo.  man        to make manual pages
-	echo.  texinfo    to make Texinfo files
-	echo.  gettext    to make PO message catalogs
-	echo.  changes    to make an overview over all changed/added/deprecated items
-	echo.  xml        to make Docutils-native XML files
-	echo.  pseudoxml  to make pseudoxml-XML files for display purposes
-	echo.  linkcheck  to check all external links for integrity
-	echo.  doctest    to run all doctests embedded in the documentation if enabled
-	goto end
-)
-
-if "%1" == "clean" (
-	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
-	del /q /s %BUILDDIR%\*
-	goto end
-)
-
-
-%SPHINXBUILD% 2> nul
-if errorlevel 9009 (
-	echo.
-	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
-	echo.installed, then set the SPHINXBUILD environment variable to point
-	echo.to the full path of the 'sphinx-build' executable. Alternatively you
-	echo.may add the Sphinx directory to PATH.
-	echo.
-	echo.If you don't have Sphinx installed, grab it from
-	echo.http://sphinx-doc.org/
-	exit /b 1
-)
-
-if "%1" == "html" (
-	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
-	goto end
-)
-
-if "%1" == "dirhtml" (
-	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
-	goto end
-)
-
-if "%1" == "singlehtml" (
-	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
-	goto end
-)
-
-if "%1" == "pickle" (
-	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; now you can process the pickle files.
-	goto end
-)
-
-if "%1" == "json" (
-	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; now you can process the JSON files.
-	goto end
-)
-
-if "%1" == "htmlhelp" (
-	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
-	goto end
-)
-
-if "%1" == "qthelp" (
-	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
-	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\psutil.qhcp
-	echo.To view the help file:
-	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\psutil.ghc
-	goto end
-)
-
-if "%1" == "devhelp" (
-	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished.
-	goto end
-)
-
-if "%1" == "epub" (
-	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The epub file is in %BUILDDIR%/epub.
-	goto end
-)
-
-if "%1" == "latex" (
-	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
-	goto end
-)
-
-if "%1" == "latexpdf" (
-	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
-	cd %BUILDDIR%/latex
-	make all-pdf
-	cd %BUILDDIR%/..
-	echo.
-	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
-	goto end
-)
-
-if "%1" == "latexpdfja" (
-	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
-	cd %BUILDDIR%/latex
-	make all-pdf-ja
-	cd %BUILDDIR%/..
-	echo.
-	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
-	goto end
-)
-
-if "%1" == "text" (
-	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The text files are in %BUILDDIR%/text.
-	goto end
-)
-
-if "%1" == "man" (
-	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The manual pages are in %BUILDDIR%/man.
-	goto end
-)
-
-if "%1" == "texinfo" (
-	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
-	goto end
-)
-
-if "%1" == "gettext" (
-	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
-	goto end
-)
-
-if "%1" == "changes" (
-	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.The overview file is in %BUILDDIR%/changes.
-	goto end
-)
-
-if "%1" == "linkcheck" (
-	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
-	goto end
-)
-
-if "%1" == "doctest" (
-	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
-	goto end
-)
-
-if "%1" == "xml" (
-	%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The XML files are in %BUILDDIR%/xml.
-	goto end
-)
-
-if "%1" == "pseudoxml" (
-	%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
-	goto end
-)
-
-:end
+@echo off
+
+rem ==========================================================================
+rem Shortcuts for various tasks, emulating UNIX "make" on Windows.
+rem It is primarly intended as a shortcut for compiling / installing
+rem psutil ("make.bat build", "make.bat install") and running tests
+rem ("make.bat test").
+rem
+rem This script is modeled after my Windows installation which uses:
+rem - Visual studio 2008 for Python 2.6, 2.7, 3.2
+rem - Visual studio 2010 for Python 3.3+
+rem ...therefore it might not work on your Windows installation.
+rem
+rem By default C:\Python27\python.exe is used.
+rem To compile for a specific Python version run:
+rem     set PYTHON=C:\Python34\python.exe & make.bat build
+rem
+rem To use a different test script:
+rem      set PYTHON=C:\Python34\python.exe & set TSCRIPT=foo.py & make.bat test
+rem ==========================================================================
+
+if "%PYTHON%" == "" (
+    set PYTHON=C:\Python27\python.exe
+)
+if "%TSCRIPT%" == "" (
+    set TSCRIPT=psutil\tests\runner.py
+)
+
+rem Needed to locate the .pypirc file and upload exes on PYPI.
+set HOME=%USERPROFILE%
+
+%PYTHON% scripts\internal\winmake.py %1 %2
diff --git a/psutil/__init__.py b/psutil/__init__.py
index 2ba99fed..2c0954c9 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -12,6 +12,7 @@ in Python.
 from __future__ import division
 
 import collections
+import contextlib
 import errno
 import functools
 import os
@@ -28,6 +29,7 @@ except ImportError:
 from . import _common
 from ._common import deprecated_method
 from ._common import memoize
+from ._common import memoize_when_activated
 from ._compat import callable
 from ._compat import long
 from ._compat import PY3 as _PY3
@@ -386,6 +388,7 @@ class Process(object):
         self._create_time = None
         self._gone = False
         self._hash = None
+        self._oneshot_inctx = False
         # used for caching on Windows only (on POSIX ppid may change)
         self._ppid = None
         # platform-specific modules define an _psplatform.Process
@@ -452,15 +455,75 @@ class Process(object):
 
     # --- utility methods
 
+    @contextlib.contextmanager
+    def oneshot(self):
+        """Utility context manager which considerably speeds up the
+        retrieval of multiple process information at the same time.
+
+        Internally different process info (e.g. name, ppid, uids,
+        gids, ...) may be fetched by using the same routine, but
+        only one information is returned and the others are discarded.
+        When using this context manager the internal routine is
+        executed once (in the example below on name()) and the
+        other info are cached.
+
+        The cache is cleared when exiting the context manager block.
+        The advice is to use this every time you retrieve more than
+        one information about the process. If you're lucky, you'll
+        get a hell of a speedup.
+
+        >>> import psutil
+        >>> p = psutil.Process()
+        >>> with p.oneshot():
+        ...     p.name()  # collect multiple info
+        ...     p.cpu_times()  # return cached value
+        ...     p.cpu_percent()  # return cached value
+        ...     p.create_time()  # return cached value
+        ...
+        >>>
+        """
+        if self._oneshot_inctx:
+            # NOOP: this covers the use case where the user enters the
+            # context twice. Since as_dict() internally uses oneshot()
+            # I expect that the code below will be a pretty common
+            # "mistake" that the user will make, so let's guard
+            # against that:
+            #
+            # >>> with p.oneshot():
+            # ...    p.as_dict()
+            # ...
+            yield
+        else:
+            self._oneshot_inctx = True
+            try:
+                # cached in case cpu_percent() is used
+                self.cpu_times.cache_activate()
+                # cached in case memory_percent() is used
+                self.memory_info.cache_activate()
+                # cached in case parent() is used
+                self.ppid.cache_activate()
+                # cached in case username() is used
+                if POSIX:
+                    self.uids.cache_activate()
+                # specific implementation cache
+                self._proc.oneshot_enter()
+                yield
+            finally:
+                self.cpu_times.cache_deactivate()
+                self.memory_info.cache_deactivate()
+                self.ppid.cache_deactivate()
+                if POSIX:
+                    self.uids.cache_deactivate()
+                self._proc.oneshot_exit()
+                self._oneshot_inctx = False
+
     def as_dict(self, attrs=None, ad_value=None):
         """Utility method returning process information as a
         hashable dictionary.
-
         If 'attrs' is specified it must be a list of strings
         reflecting available Process class' attribute names
         (e.g. ['cpu_times', 'name']) else all public (read
         only) attributes are assumed.
-
         'ad_value' is the value which gets assigned in case
         AccessDenied or ZombieProcess exception is raised when
         retrieving that particular process information.
@@ -478,23 +541,24 @@ class Process(object):
 
         retdict = dict()
         ls = attrs or valid_names
-        for name in ls:
-            try:
-                if name == 'pid':
-                    ret = self.pid
-                else:
-                    meth = getattr(self, name)
-                    ret = meth()
-            except (AccessDenied, ZombieProcess):
-                ret = ad_value
-            except NotImplementedError:
-                # in case of not implemented functionality (may happen
-                # on old or exotic systems) we want to crash only if
-                # the user explicitly asked for that particular attr
-                if attrs:
-                    raise
-                continue
-            retdict[name] = ret
+        with self.oneshot():
+            for name in ls:
+                try:
+                    if name == 'pid':
+                        ret = self.pid
+                    else:
+                        meth = getattr(self, name)
+                        ret = meth()
+                except (AccessDenied, ZombieProcess):
+                    ret = ad_value
+                except NotImplementedError:
+                    # in case of not implemented functionality (may happen
+                    # on old or exotic systems) we want to crash only if
+                    # the user explicitly asked for that particular attr
+                    if attrs:
+                        raise
+                    continue
+                retdict[name] = ret
         return retdict
 
     def parent(self):
@@ -542,6 +606,7 @@ class Process(object):
         """The process PID."""
         return self._pid
 
+    @memoize_when_activated
     def ppid(self):
         """The process parent PID.
         On Windows the return value is cached after first call.
@@ -677,6 +742,7 @@ class Process(object):
 
     if POSIX:
 
+        @memoize_when_activated
         def uids(self):
             """Return process UIDs as a (real, effective, saved)
             namedtuple.
@@ -988,6 +1054,7 @@ class Process(object):
             single_cpu_percent = overall_cpus_percent * num_cpus
             return round(single_cpu_percent, 1)
 
+    @memoize_when_activated
     def cpu_times(self):
         """Return a (user, system, children_user, children_system)
         namedtuple representing the accumulated process time, in
@@ -998,6 +1065,7 @@ class Process(object):
         """
         return self._proc.cpu_times()
 
+    @memoize_when_activated
     def memory_info(self):
         """Return a namedtuple with variable fields depending on the
         platform, representing memory information about the process.
@@ -1316,7 +1384,7 @@ _as_dict_attrnames = set(
     [x for x in dir(Process) if not x.startswith('_') and x not in
      ['send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait',
       'is_running', 'as_dict', 'parent', 'children', 'rlimit',
-      'memory_info_ex']])
+      'memory_info_ex', 'oneshot']])
 
 
 # =====================================================================
@@ -2111,7 +2179,7 @@ def test():  # pragma: no cover
                 pinfo['name'].strip() or '?'))
 
 
-del memoize, division, deprecated_method
+del memoize, memoize_when_activated, division, deprecated_method
 if sys.version_info[0] < 3:
     del num, x
 
diff --git a/psutil/_common.py b/psutil/_common.py
index c5957241..3879a1d7 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -261,6 +261,59 @@ def memoize(fun):
     return wrapper
 
 
+def memoize_when_activated(fun):
+    """A memoize decorator which is disabled by default. It can be
+    activated and deactivated on request.
+    For efficiency reasons it can be used only against class methods
+    accepting no arguments.
+
+    >>> class Foo:
+    ...     @memoize
+    ...     def foo()
+    ...         print(1)
+    ...
+    >>> f = Foo()
+    >>> # deactivated (default)
+    >>> foo()
+    1
+    >>> foo()
+    1
+    >>>
+    >>> # activated
+    >>> foo.cache_activate()
+    >>> foo()
+    1
+    >>> foo()
+    >>> foo()
+    >>>
+    """
+    @functools.wraps(fun)
+    def wrapper(self):
+        if not wrapper.cache_activated:
+            return fun(self)
+        else:
+            try:
+                ret = cache[fun]
+            except KeyError:
+                ret = cache[fun] = fun(self)
+            return ret
+
+    def cache_activate():
+        """Activate cache."""
+        wrapper.cache_activated = True
+
+    def cache_deactivate():
+        """Deactivate and clear cache."""
+        wrapper.cache_activated = False
+        cache.clear()
+
+    cache = {}
+    wrapper.cache_activated = False
+    wrapper.cache_activate = cache_activate
+    wrapper.cache_deactivate = cache_deactivate
+    return wrapper
+
+
 def isfile_strict(path):
     """Same as os.path.isfile() but does not swallow EACCES / EPERM
     exceptions, see:
diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py
index caace16b..a3301504 100644
--- a/psutil/_psbsd.py
+++ b/psutil/_psbsd.py
@@ -17,6 +17,7 @@ from . import _psutil_bsd as cext
 from . import _psutil_posix as cext_posix
 from ._common import conn_tmap
 from ._common import FREEBSD
+from ._common import memoize_when_activated
 from ._common import NETBSD
 from ._common import OPENBSD
 from ._common import sockfam_to_enum
@@ -96,6 +97,33 @@ else:
     PAGESIZE = os.sysconf("SC_PAGE_SIZE")
 AF_LINK = cext_posix.AF_LINK
 
+kinfo_proc_map = dict(
+    ppid=0,
+    status=1,
+    real_uid=2,
+    effective_uid=3,
+    saved_uid=4,
+    real_gid=5,
+    effective_gid=6,
+    saved_gid=7,
+    ttynr=8,
+    create_time=9,
+    ctx_switches_vol=10,
+    ctx_switches_unvol=11,
+    read_io_count=12,
+    write_io_count=13,
+    user_time=14,
+    sys_time=15,
+    ch_user_time=16,
+    ch_sys_time=17,
+    rss=18,
+    vms=19,
+    memtext=20,
+    memdata=21,
+    memstack=22,
+    name=23,
+)
+
 
 # =====================================================================
 # --- named tuples
@@ -460,9 +488,23 @@ class Process(object):
         self._name = None
         self._ppid = None
 
+    @memoize_when_activated
+    def oneshot(self):
+        """Retrieves multiple process info in one shot as a raw tuple."""
+        ret = cext.proc_oneshot_info(self.pid)
+        assert len(ret) == len(kinfo_proc_map)
+        return ret
+
+    def oneshot_enter(self):
+        self.oneshot.cache_activate()
+
+    def oneshot_exit(self):
+        self.oneshot.cache_deactivate()
+
     @wrap_exceptions
     def name(self):
-        return cext.proc_name(self.pid)
+        name = self.oneshot()[kinfo_proc_map['name']]
+        return name if name is not None else cext.proc_name(self.pid)
 
     @wrap_exceptions
     def exe(self):
@@ -510,7 +552,7 @@ class Process(object):
 
     @wrap_exceptions
     def terminal(self):
-        tty_nr = cext.proc_tty_nr(self.pid)
+        tty_nr = self.oneshot()[kinfo_proc_map['ttynr']]
         tmap = _psposix.get_terminal_map()
         try:
             return tmap[tty_nr]
@@ -519,32 +561,49 @@ class Process(object):
 
     @wrap_exceptions
     def ppid(self):
-        self._ppid = cext.proc_ppid(self.pid)
+        self._ppid = self.oneshot()[kinfo_proc_map['ppid']]
         return self._ppid
 
     @wrap_exceptions
     def uids(self):
-        real, effective, saved = cext.proc_uids(self.pid)
-        return _common.puids(real, effective, saved)
+        rawtuple = self.oneshot()
+        return _common.puids(
+            rawtuple[kinfo_proc_map['real_uid']],
+            rawtuple[kinfo_proc_map['effective_uid']],
+            rawtuple[kinfo_proc_map['saved_uid']])
 
     @wrap_exceptions
     def gids(self):
-        real, effective, saved = cext.proc_gids(self.pid)
-        return _common.pgids(real, effective, saved)
+        rawtuple = self.oneshot()
+        return _common.pgids(
+            rawtuple[kinfo_proc_map['real_gid']],
+            rawtuple[kinfo_proc_map['effective_gid']],
+            rawtuple[kinfo_proc_map['saved_gid']])
 
     @wrap_exceptions
     def cpu_times(self):
-        return _common.pcputimes(*cext.proc_cpu_times(self.pid))
+        rawtuple = self.oneshot()
+        return _common.pcputimes(
+            rawtuple[kinfo_proc_map['user_time']],
+            rawtuple[kinfo_proc_map['sys_time']],
+            rawtuple[kinfo_proc_map['ch_user_time']],
+            rawtuple[kinfo_proc_map['ch_sys_time']])
 
     @wrap_exceptions
     def memory_info(self):
-        return pmem(*cext.proc_memory_info(self.pid))
+        rawtuple = self.oneshot()
+        return pmem(
+            rawtuple[kinfo_proc_map['rss']],
+            rawtuple[kinfo_proc_map['vms']],
+            rawtuple[kinfo_proc_map['memtext']],
+            rawtuple[kinfo_proc_map['memdata']],
+            rawtuple[kinfo_proc_map['memstack']])
 
     memory_full_info = memory_info
 
     @wrap_exceptions
     def create_time(self):
-        return cext.proc_create_time(self.pid)
+        return self.oneshot()[kinfo_proc_map['create_time']]
 
     @wrap_exceptions
     def num_threads(self):
@@ -556,7 +615,10 @@ class Process(object):
 
     @wrap_exceptions
     def num_ctx_switches(self):
-        return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid))
+        rawtuple = self.oneshot()
+        return _common.pctxsw(
+            rawtuple[kinfo_proc_map['ctx_switches_vol']],
+            rawtuple[kinfo_proc_map['ctx_switches_unvol']])
 
     @wrap_exceptions
     def threads(self):
@@ -635,14 +697,18 @@ class Process(object):
 
     @wrap_exceptions
     def status(self):
-        code = cext.proc_status(self.pid)
+        code = self.oneshot()[kinfo_proc_map['status']]
         # XXX is '?' legit? (we're not supposed to return it anyway)
         return PROC_STATUSES.get(code, '?')
 
     @wrap_exceptions
     def io_counters(self):
-        rc, wc, rb, wb = cext.proc_io_counters(self.pid)
-        return _common.pio(rc, wc, rb, wb)
+        rawtuple = self.oneshot()
+        return _common.pio(
+            rawtuple[kinfo_proc_map['read_io_count']],
+            rawtuple[kinfo_proc_map['write_io_count']],
+            -1,
+            -1)
 
     @wrap_exceptions
     def cwd(self):
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 3732beaf..88141adb 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -25,6 +25,7 @@ from . import _psutil_linux as cext
 from . import _psutil_posix as cext_posix
 from ._common import isfile_strict
 from ._common import memoize
+from ._common import memoize_when_activated
 from ._common import parse_environ_block
 from ._common import NIC_DUPLEX_FULL
 from ._common import NIC_DUPLEX_HALF
@@ -1104,12 +1105,16 @@ class Process(object):
         self._ppid = None
         self._procfs_path = get_procfs_path()
 
+    @memoize_when_activated
     def _parse_stat_file(self):
         """Parse /proc/{pid}/stat file. Return a list of fields where
         process name is in position 0.
         Using "man proc" as a reference: where "man proc" refers to
-        position N, always subscract 2 (e.g starttime pos 22 in
+        position N, always substract 2 (e.g starttime pos 22 in
         'man proc' == pos 20 in the list returned here).
+
+        The return value is cached in case oneshot() ctx manager is
+        in use.
         """
         with open_binary("%s/%s/stat" % (self._procfs_path, self.pid)) as f:
             data = f.read()
@@ -1121,15 +1126,32 @@ class Process(object):
         fields_after_name = data[rpar + 2:].split()
         return [name] + fields_after_name
 
+    @memoize_when_activated
     def _read_status_file(self):
+        """Read /proc/{pid}/stat file and return its content.
+
+        The return value is cached in case oneshot() ctx manager is
+        in use.
+        """
         with open_binary("%s/%s/status" % (self._procfs_path, self.pid)) as f:
             return f.read()
 
+    @memoize_when_activated
     def _read_smaps_file(self):
         with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid),
                          buffering=BIGGER_FILE_BUFFERING) as f:
             return f.read().strip()
 
+    def oneshot_enter(self):
+        self._parse_stat_file.cache_activate()
+        self._read_status_file.cache_activate()
+        self._read_smaps_file.cache_activate()
+
+    def oneshot_exit(self):
+        self._parse_stat_file.cache_deactivate()
+        self._read_status_file.cache_deactivate()
+        self._read_smaps_file.cache_deactivate()
+
     @wrap_exceptions
     def name(self):
         name = self._parse_stat_file()[0]
diff --git a/psutil/_psosx.py b/psutil/_psosx.py
index dab5f8d7..2665080e 100644
--- a/psutil/_psosx.py
+++ b/psutil/_psosx.py
@@ -15,6 +15,7 @@ from . import _psutil_osx as cext
 from . import _psutil_posix as cext_posix
 from ._common import conn_tmap
 from ._common import isfile_strict
+from ._common import memoize_when_activated
 from ._common import parse_environ_block
 from ._common import sockfam_to_enum
 from ._common import socktype_to_enum
@@ -56,6 +57,31 @@ PROC_STATUSES = {
     cext.SZOMB: _common.STATUS_ZOMBIE,
 }
 
+kinfo_proc_map = dict(
+    ppid=0,
+    ruid=1,
+    euid=2,
+    suid=3,
+    rgid=4,
+    egid=5,
+    sgid=6,
+    ttynr=7,
+    ctime=8,
+    status=9,
+    name=10,
+)
+
+pidtaskinfo_map = dict(
+    cpuutime=0,
+    cpustime=1,
+    rss=2,
+    vms=3,
+    pfaults=4,
+    pageins=5,
+    numthreads=6,
+    volctxsw=7,
+)
+
 scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle'])
 
 svmem = namedtuple(
@@ -271,9 +297,32 @@ class Process(object):
         self._name = None
         self._ppid = None
 
+    @memoize_when_activated
+    def _get_kinfo_proc(self):
+        # Note: should work with all PIDs without permission issues.
+        ret = cext.proc_kinfo_oneshot(self.pid)
+        assert len(ret) == len(kinfo_proc_map)
+        return ret
+
+    @memoize_when_activated
+    def _get_pidtaskinfo(self):
+        # Note: should work for PIDs owned by user only.
+        ret = cext.proc_pidtaskinfo_oneshot(self.pid)
+        assert len(ret) == len(pidtaskinfo_map)
+        return ret
+
+    def oneshot_enter(self):
+        self._get_kinfo_proc.cache_activate()
+        self._get_pidtaskinfo.cache_activate()
+
+    def oneshot_exit(self):
+        self._get_kinfo_proc.cache_deactivate()
+        self._get_pidtaskinfo.cache_deactivate()
+
     @wrap_exceptions
     def name(self):
-        return cext.proc_name(self.pid)
+        name = self._get_kinfo_proc()[kinfo_proc_map['name']]
+        return name if name is not None else cext.proc_name(self.pid)
 
     @wrap_exceptions
     def exe(self):
@@ -293,7 +342,7 @@ class Process(object):
 
     @wrap_exceptions
     def ppid(self):
-        self._ppid = cext.proc_ppid(self.pid)
+        self._ppid = self._get_kinfo_proc()[kinfo_proc_map['ppid']]
         return self._ppid
 
     @wrap_exceptions
@@ -302,17 +351,23 @@ class Process(object):
 
     @wrap_exceptions
     def uids(self):
-        real, effective, saved = cext.proc_uids(self.pid)
-        return _common.puids(real, effective, saved)
+        rawtuple = self._get_kinfo_proc()
+        return _common.puids(
+            rawtuple[kinfo_proc_map['ruid']],
+            rawtuple[kinfo_proc_map['euid']],
+            rawtuple[kinfo_proc_map['suid']])
 
     @wrap_exceptions
     def gids(self):
-        real, effective, saved = cext.proc_gids(self.pid)
-        return _common.pgids(real, effective, saved)
+        rawtuple = self._get_kinfo_proc()
+        return _common.puids(
+            rawtuple[kinfo_proc_map['rgid']],
+            rawtuple[kinfo_proc_map['egid']],
+            rawtuple[kinfo_proc_map['sgid']])
 
     @wrap_exceptions
     def terminal(self):
-        tty_nr = cext.proc_tty_nr(self.pid)
+        tty_nr = self._get_kinfo_proc()[kinfo_proc_map['ttynr']]
         tmap = _psposix.get_terminal_map()
         try:
             return tmap[tty_nr]
@@ -321,8 +376,13 @@ class Process(object):
 
     @wrap_exceptions
     def memory_info(self):
-        rss, vms, pfaults, pageins = cext.proc_memory_info(self.pid)
-        return pmem(rss, vms, pfaults, pageins)
+        rawtuple = self._get_pidtaskinfo()
+        return pmem(
+            rawtuple[pidtaskinfo_map['rss']],
+            rawtuple[pidtaskinfo_map['vms']],
+            rawtuple[pidtaskinfo_map['pfaults']],
+            rawtuple[pidtaskinfo_map['pageins']],
+        )
 
     @wrap_exceptions
     def memory_full_info(self):
@@ -332,21 +392,28 @@ class Process(object):
 
     @wrap_exceptions
     def cpu_times(self):
-        user, system = cext.proc_cpu_times(self.pid)
-        # Children user/system times are not retrievable (set to 0).
-        return _common.pcputimes(user, system, 0, 0)
+        rawtuple = self._get_pidtaskinfo()
+        return _common.pcputimes(
+            rawtuple[pidtaskinfo_map['cpuutime']],
+            rawtuple[pidtaskinfo_map['cpustime']],
+            # children user / system times are not retrievable (set to 0)
+            0, 0)
 
     @wrap_exceptions
     def create_time(self):
-        return cext.proc_create_time(self.pid)
+        return self._get_kinfo_proc()[kinfo_proc_map['ctime']]
 
     @wrap_exceptions
     def num_ctx_switches(self):
-        return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid))
+        # Unvoluntary value seems not to be available;
+        # getrusage() numbers seems to confirm this theory.
+        # We set it to 0.
+        vol = self._get_pidtaskinfo()[pidtaskinfo_map['volctxsw']]
+        return _common.pctxsw(vol, 0)
 
     @wrap_exceptions
     def num_threads(self):
-        return cext.proc_num_threads(self.pid)
+        return self._get_pidtaskinfo()[pidtaskinfo_map['numthreads']]
 
     @wrap_exceptions
     def open_files(self):
@@ -400,7 +467,7 @@ class Process(object):
 
     @wrap_exceptions
     def status(self):
-        code = cext.proc_status(self.pid)
+        code = self._get_kinfo_proc()[kinfo_proc_map['status']]
         # XXX is '?' legit? (we're not supposed to return it anyway)
         return PROC_STATUSES.get(code, '?')
 
diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py
index d24a76a1..a62e0bf5 100644
--- a/psutil/_pssunos.py
+++ b/psutil/_pssunos.py
@@ -16,6 +16,7 @@ from . import _psposix
 from . import _psutil_posix as cext_posix
 from . import _psutil_sunos as cext
 from ._common import isfile_strict
+from ._common import memoize_when_activated
 from ._common import sockfam_to_enum
 from ._common import socktype_to_enum
 from ._common import usage_percent
@@ -353,10 +354,32 @@ class Process(object):
         self._ppid = None
         self._procfs_path = get_procfs_path()
 
+    def oneshot_enter(self):
+        self._proc_name_and_args.cache_activate()
+        self._proc_basic_info.cache_activate()
+        self._proc_cred.cache_activate()
+
+    def oneshot_exit(self):
+        self._proc_name_and_args.cache_deactivate()
+        self._proc_basic_info.cache_deactivate()
+        self._proc_cred.cache_deactivate()
+
+    @memoize_when_activated
+    def _proc_name_and_args(self):
+        return cext.proc_name_and_args(self.pid, self._procfs_path)
+
+    @memoize_when_activated
+    def _proc_basic_info(self):
+        return cext.proc_basic_info(self.pid, self._procfs_path)
+
+    @memoize_when_activated
+    def _proc_cred(self):
+        return cext.proc_cred(self.pid, self._procfs_path)
+
     @wrap_exceptions
     def name(self):
         # note: max len == 15
-        return cext.proc_name_and_args(self.pid, self._procfs_path)[0]
+        return self._proc_name_and_args()[0]
 
     @wrap_exceptions
     def exe(self):
@@ -373,16 +396,15 @@ class Process(object):
 
     @wrap_exceptions
     def cmdline(self):
-        return cext.proc_name_and_args(
-            self.pid, self._procfs_path)[1].split(' ')
+        return self._proc_name_and_args()[1].split(' ')
 
     @wrap_exceptions
     def create_time(self):
-        return cext.proc_basic_info(self.pid, self._procfs_path)[3]
+        return self._proc_basic_info()[3]
 
     @wrap_exceptions
     def num_threads(self):
-        return cext.proc_basic_info(self.pid, self._procfs_path)[5]
+        return self._proc_basic_info()[5]
 
     @wrap_exceptions
     def nice_get(self):
@@ -415,19 +437,17 @@ class Process(object):
 
     @wrap_exceptions
     def ppid(self):
-        self._ppid = cext.proc_basic_info(self.pid, self._procfs_path)[0]
+        self._ppid = self._proc_basic_info()[0]
         return self._ppid
 
     @wrap_exceptions
     def uids(self):
-        real, effective, saved, _, _, _ = \
-            cext.proc_cred(self.pid, self._procfs_path)
+        real, effective, saved, _, _, _ = self._proc_cred()
         return _common.puids(real, effective, saved)
 
     @wrap_exceptions
     def gids(self):
-        _, _, _, real, effective, saved = \
-            cext.proc_cred(self.pid, self._procfs_path)
+        _, _, _, real, effective, saved = self._proc_cred()
         return _common.puids(real, effective, saved)
 
     @wrap_exceptions
@@ -453,7 +473,7 @@ class Process(object):
         procfs_path = self._procfs_path
         hit_enoent = False
         tty = wrap_exceptions(
-            cext.proc_basic_info(self.pid, self._procfs_path)[0])
+            self._proc_basic_info()[0])
         if tty != cext.PRNODEV:
             for x in (0, 1, 2, 255):
                 try:
@@ -485,7 +505,7 @@ class Process(object):
 
     @wrap_exceptions
     def memory_info(self):
-        ret = cext.proc_basic_info(self.pid, self._procfs_path)
+        ret = self._proc_basic_info()
         rss, vms = ret[1] * 1024, ret[2] * 1024
         return pmem(rss, vms)
 
@@ -493,7 +513,7 @@ class Process(object):
 
     @wrap_exceptions
     def status(self):
-        code = cext.proc_basic_info(self.pid, self._procfs_path)[6]
+        code = self._proc_basic_info()[6]
         # XXX is '?' legit? (we're not supposed to return it anyway)
         return PROC_STATUSES.get(code, '?')
 
diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c
index 4b50d01b..33448ccf 100644
--- a/psutil/_psutil_bsd.c
+++ b/psutil/_psutil_bsd.c
@@ -192,211 +192,197 @@ psutil_boot_time(PyObject *self, PyObject *args) {
 
 
 /*
- * Return process name from kinfo_proc as a Python string.
+ * Collect different info about a process in one shot and return
+ * them as a big Python tuple.
  */
 static PyObject *
-psutil_proc_name(PyObject *self, PyObject *args) {
+psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
     long pid;
+    long rss;
+    long vms;
+    long memtext;
+    long memdata;
+    long memstack;
     kinfo_proc kp;
+    long pagesize = sysconf(_SC_PAGESIZE);
     char str[1000];
+    PyObject *py_name;
+    PyObject *py_retlist;
 
     if (! PyArg_ParseTuple(args, "l", &pid))
         return NULL;
     if (psutil_kinfo_proc(pid, &kp) == -1)
         return NULL;
 
+    // Process
 #ifdef __FreeBSD__
     sprintf(str, "%s", kp.ki_comm);
 #elif defined(__OpenBSD__) || defined(__NetBSD__)
     sprintf(str, "%s", kp.p_comm);
 #endif
-
 #if PY_MAJOR_VERSION >= 3
-    return PyUnicode_DecodeFSDefault(str);
+    py_name = PyUnicode_DecodeFSDefault(str);
 #else
-    return Py_BuildValue("s", str);
+    py_name = Py_BuildValue("s", str);
 #endif
-}
-
-
-/*
- * Return process cmdline as a Python list of cmdline arguments.
- */
-static PyObject *
-psutil_proc_cmdline(PyObject *self, PyObject *args) {
-    long pid;
-    PyObject *py_retlist = NULL;
-
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-
-    py_retlist = psutil_get_cmdline(pid);
-    // psutil_get_cmdline() returns NULL only if psutil_cmd_args
-    // failed with ESRCH (no process with that PID)
-    if (NULL == py_retlist)
-        return PyErr_SetFromErrno(PyExc_OSError);
-    return Py_BuildValue("N", py_retlist);
-}
-
+    if (! py_name) {
+        // Likely a decoding error. We don't want to fail the whole
+        // operation. The python module may retry with proc_name().
+        PyErr_Clear();
+        py_name = Py_None;
+    }
 
-/*
- * Return process parent pid from kinfo_proc as a Python integer.
- */
-static PyObject *
-psutil_proc_ppid(PyObject *self, PyObject *args) {
-    long pid;
-    kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_kinfo_proc(pid, &kp) == -1)
-        return NULL;
+    // Calculate memory.
 #ifdef __FreeBSD__
-    return Py_BuildValue("l", (long)kp.ki_ppid);
-#elif defined(__OpenBSD__) || defined(__NetBSD__)
-    return Py_BuildValue("l", (long)kp.p_ppid);
+    rss = (long)kp.ki_rssize * pagesize;
+    vms = (long)kp.ki_size;
+    memtext = (long)kp.ki_tsize * pagesize;
+    memdata = (long)kp.ki_dsize * pagesize;
+    memstack = (long)kp.ki_ssize * pagesize;
+#else
+    rss = (long)kp.p_vm_rssize * pagesize;
+    #ifdef __OpenBSD__
+        // VMS, this is how ps determines it on OpenBSD:
+        // http://anoncvs.spacehopper.org/openbsd-src/tree/bin/ps/print.c#n461
+        // vms
+        vms = (long)(kp.p_vm_dsize + kp.p_vm_ssize + kp.p_vm_tsize) * pagesize;
+    #elif __NetBSD__
+        // VMS, this is how top determines it on NetBSD:
+        // ftp://ftp.iij.ad.jp/pub/NetBSD/NetBSD-release-6/src/external/bsd/
+        //     top/dist/machine/m_netbsd.c
+        vms = (long)kp.p_vm_msize * pagesize;
+    #endif
+        memtext = (long)kp.p_vm_tsize * pagesize;
+        memdata = (long)kp.p_vm_dsize * pagesize;
+        memstack = (long)kp.p_vm_ssize * pagesize;
 #endif
-}
-
 
-/*
- * Return process status as a Python integer.
- */
-static PyObject *
-psutil_proc_status(PyObject *self, PyObject *args) {
-    long pid;
-    kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_kinfo_proc(pid, &kp) == -1)
-        return NULL;
+    // Return a single big tuple with all process info.
+    py_retlist = Py_BuildValue(
+        "(lillllllidllllddddlllllO)",
 #ifdef __FreeBSD__
-    return Py_BuildValue("i", (int)kp.ki_stat);
+        //
+        (long)kp.ki_ppid,                // (long) ppid
+        (int)kp.ki_stat,                 // (int) status
+        // UIDs
+        (long)kp.ki_ruid,                // (long) real uid
+        (long)kp.ki_uid,                 // (long) effective uid
+        (long)kp.ki_svuid,               // (long) saved uid
+        // GIDs
+        (long)kp.ki_rgid,                // (long) real gid
+        (long)kp.ki_groups[0],           // (long) effective gid
+        (long)kp.ki_svuid,               // (long) saved gid
+        //
+        kp.ki_tdev,                      // (int) tty nr
+        PSUTIL_TV2DOUBLE(kp.ki_start),   // (double) create time
+        // ctx switches
+        kp.ki_rusage.ru_nvcsw,           // (long) ctx switches (voluntary)
+        kp.ki_rusage.ru_nivcsw,          // (long) ctx switches (unvoluntary)
+        // IO count
+        kp.ki_rusage.ru_inblock,         // (long) read io count
+        kp.ki_rusage.ru_oublock,         // (long) write io count
+        // CPU times: convert from micro seconds to seconds.
+        PSUTIL_TV2DOUBLE(kp.ki_rusage.ru_utime),     // (double) user time
+        PSUTIL_TV2DOUBLE(kp.ki_rusage.ru_stime),     // (double) sys time
+        PSUTIL_TV2DOUBLE(kp.ki_rusage_ch.ru_utime),  // (double) children utime
+        PSUTIL_TV2DOUBLE(kp.ki_rusage_ch.ru_stime),  // (double) children stime
+        // memory
+        rss,                              // (long) rss
+        vms,                              // (long) vms
+        memtext,                          // (long) mem text
+        memdata,                          // (long) mem data
+        memstack,                         // (long) mem stack
 #elif defined(__OpenBSD__) || defined(__NetBSD__)
-    return Py_BuildValue("i", (int)kp.p_stat);
+        //
+        (long)kp.p_ppid,                 // (long) ppid
+        (int)kp.p_stat,                  // (int) status
+        // UIDs
+        (long)kp.p_ruid,                 // (long) real uid
+        (long)kp.p_uid,                  // (long) effective uid
+        (long)kp.p_svuid,                // (long) saved uid
+        // GIDs
+        (long)kp.p_rgid,                 // (long) real gid
+        (long)kp.p_groups[0],            // (long) effective gid
+        (long)kp.p_svuid,                // (long) saved gid
+        //
+        kp.p_tdev,                       // (int) tty nr
+        PSUTIL_KPT2DOUBLE(kp.p_ustart),  // (double) create time
+        // ctx switches
+        kp.p_uru_nvcsw,                  // (long) ctx switches (voluntary)
+        kp.p_uru_nivcsw,                 // (long) ctx switches (unvoluntary)
+        // IO count
+        kp.p_uru_inblock,                // (long) read io count
+        kp.p_uru_oublock,                // (long) write io count
+        // CPU times: convert from micro seconds to seconds.
+        PSUTIL_KPT2DOUBLE(kp.p_uutime),  // (double) user time
+        PSUTIL_KPT2DOUBLE(kp.p_ustime),  // (double) sys time
+        // OpenBSD and NetBSD provide children user + system times summed
+        // together (no distinction).
+        kp.p_uctime_sec + kp.p_uctime_usec / 1000000.0,  // (double) ch utime
+        kp.p_uctime_sec + kp.p_uctime_usec / 1000000.0,  // (double) ch stime
+        // memory
+        rss,                              // (long) rss
+        vms,                              // (long) vms
+        memtext,                          // (long) mem text
+        memdata,                          // (long) mem data
+        memstack,                         // (long) mem stack
 #endif
-}
-
+        py_name                           // (pystr) name
+    );
 
-/*
- * Return process real, effective and saved user ids from kinfo_proc
- * as a Python tuple.
- */
-static PyObject *
-psutil_proc_uids(PyObject *self, PyObject *args) {
-    long pid;
-    kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    return Py_BuildValue("lll",
-#ifdef __FreeBSD__
-                         (long)kp.ki_ruid,
-                         (long)kp.ki_uid,
-                         (long)kp.ki_svuid);
-#elif defined(__OpenBSD__) || defined(__NetBSD__)
-                         (long)kp.p_ruid,
-                         (long)kp.p_uid,
-                         (long)kp.p_svuid);
-#endif
+    if (py_retlist != NULL) {
+        // XXX shall we decref() also in case of Py_BuildValue() error?
+        Py_DECREF(py_name);
+    }
+    return py_retlist;
 }
 
 
 /*
- * Return process real, effective and saved group ids from kinfo_proc
- * as a Python tuple.
+ * Return process name from kinfo_proc as a Python string.
  */
 static PyObject *
-psutil_proc_gids(PyObject *self, PyObject *args) {
+psutil_proc_name(PyObject *self, PyObject *args) {
     long pid;
     kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    return Py_BuildValue("lll",
-#ifdef __FreeBSD__
-                         (long)kp.ki_rgid,
-                         (long)kp.ki_groups[0],
-                         (long)kp.ki_svuid);
-#elif defined(__OpenBSD__) || defined(__NetBSD__)
-                         (long)kp.p_rgid,
-                         (long)kp.p_groups[0],
-                         (long)kp.p_svuid);
-#endif
-}
-
+    char str[1000];
 
-/*
- * Return process real, effective and saved group ids from kinfo_proc
- * as a Python tuple.
- */
-static PyObject *
-psutil_proc_tty_nr(PyObject *self, PyObject *args) {
-    long pid;
-    kinfo_proc kp;
     if (! PyArg_ParseTuple(args, "l", &pid))
         return NULL;
     if (psutil_kinfo_proc(pid, &kp) == -1)
         return NULL;
+
 #ifdef __FreeBSD__
-    return Py_BuildValue("i", kp.ki_tdev);
+    sprintf(str, "%s", kp.ki_comm);
 #elif defined(__OpenBSD__) || defined(__NetBSD__)
-    return Py_BuildValue("i", kp.p_tdev);
+    sprintf(str, "%s", kp.p_comm);
 #endif
-}
-
 
-/*
- * Return the number of context switches performed by process as a tuple.
- */
-static PyObject *
-psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) {
-    long pid;
-    kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    return Py_BuildValue("(ll)",
-#ifdef __FreeBSD__
-                         kp.ki_rusage.ru_nvcsw,
-                         kp.ki_rusage.ru_nivcsw);
-#elif defined(__OpenBSD__) || defined(__NetBSD__)
-                         kp.p_uru_nvcsw,
-                         kp.p_uru_nivcsw);
+#if PY_MAJOR_VERSION >= 3
+    return PyUnicode_DecodeFSDefault(str);
+#else
+    return Py_BuildValue("s", str);
 #endif
 }
 
 
 /*
- * Return a Python tuple (user_time, kernel_time)
+ * Return process cmdline as a Python list of cmdline arguments.
  */
 static PyObject *
-psutil_proc_cpu_times(PyObject *self, PyObject *args) {
+psutil_proc_cmdline(PyObject *self, PyObject *args) {
     long pid;
-    double user_t, sys_t, children_user_t, children_sys_t;
-    kinfo_proc kp;
+    PyObject *py_retlist = NULL;
+
     if (! PyArg_ParseTuple(args, "l", &pid))
         return NULL;
-    if (psutil_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    // convert from microseconds to seconds
-#ifdef __FreeBSD__
-    user_t = PSUTIL_TV2DOUBLE(kp.ki_rusage.ru_utime);
-    sys_t = PSUTIL_TV2DOUBLE(kp.ki_rusage.ru_stime);
-    children_user_t = PSUTIL_TV2DOUBLE(kp.ki_rusage_ch.ru_utime);
-    children_sys_t = PSUTIL_TV2DOUBLE(kp.ki_rusage_ch.ru_stime);
-#elif defined(__OpenBSD__) || defined(__NetBSD__)
-    user_t = PSUTIL_KPT2DOUBLE(kp.p_uutime);
-    sys_t = PSUTIL_KPT2DOUBLE(kp.p_ustime);
-    // OpenBSD and NetBSD provide children user + system times summed
-    // together (no distinction).
-    children_user_t = kp.p_uctime_sec + kp.p_uctime_usec / 1000000.0;
-    children_sys_t = children_user_t;
-#endif
-    return Py_BuildValue("(dddd)",
-                         user_t, sys_t, children_user_t, children_sys_t);
+
+    py_retlist = psutil_get_cmdline(pid);
+    // psutil_get_cmdline() returns NULL only if psutil_cmd_args
+    // failed with ESRCH (no process with that PID)
+    if (NULL == py_retlist)
+        return PyErr_SetFromErrno(PyExc_OSError);
+    return Py_BuildValue("N", py_retlist);
 }
 
 
@@ -421,94 +407,6 @@ psutil_cpu_count_logical(PyObject *self, PyObject *args) {
 }
 
 
-/*
- * Return a Python float indicating the process create time expressed in
- * seconds since the epoch.
- */
-static PyObject *
-psutil_proc_create_time(PyObject *self, PyObject *args) {
-    long pid;
-    kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-#ifdef __FreeBSD__
-    return Py_BuildValue("d", PSUTIL_TV2DOUBLE(kp.ki_start));
-#elif defined(__OpenBSD__) || defined(__NetBSD__)
-    return Py_BuildValue("d", PSUTIL_KPT2DOUBLE(kp.p_ustart));
-#endif
-}
-
-
-/*
- * Return a Python float indicating the process create time expressed in
- * seconds since the epoch.
- */
-static PyObject *
-psutil_proc_io_counters(PyObject *self, PyObject *args) {
-    long pid;
-    kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    // there's apparently no way to determine bytes count, hence return -1.
-    return Py_BuildValue("(llll)",
-#ifdef __FreeBSD__
-                         kp.ki_rusage.ru_inblock,
-                         kp.ki_rusage.ru_oublock,
-#elif defined(__OpenBSD__) || defined(__NetBSD__)
-                         kp.p_uru_inblock,
-                         kp.p_uru_oublock,
-#endif
-                         -1,
-                         -1);
-}
-
-
-/*
- * Return extended memory info for a process as a Python tuple.
- */
-static PyObject *
-psutil_proc_memory_info(PyObject *self, PyObject *args) {
-    long pagesize = sysconf(_SC_PAGESIZE);
-    long pid;
-    kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-
-    return Py_BuildValue(
-        "(lllll)",
-#ifdef __FreeBSD__
-        (long) kp.ki_rssize * pagesize,  // rss
-        (long) kp.ki_size,  // vms
-        (long) kp.ki_tsize * pagesize,  // text
-        (long) kp.ki_dsize * pagesize,  // data
-        (long) kp.ki_ssize * pagesize  // stack
-#else
-        (long) kp.p_vm_rssize * pagesize,    // rss
-    #ifdef __OpenBSD__
-        // VMS, this is how ps determines it on OpenBSD:
-        // http://anoncvs.spacehopper.org/openbsd-src/tree/bin/ps/print.c#n461
-        // vms
-        (long) (kp.p_vm_dsize + kp.p_vm_ssize + kp.p_vm_tsize) * pagesize,
-    #elif __NetBSD__
-        // VMS, this is how top determines it on NetBSD:
-        // ftp://ftp.iij.ad.jp/pub/NetBSD/NetBSD-release-6/src/external/bsd/
-        //     top/dist/machine/m_netbsd.c
-        (long) kp.p_vm_msize * pagesize,  // vms
-    #endif
-        (long) kp.p_vm_tsize * pagesize,  // text
-        (long) kp.p_vm_dsize * pagesize,  // data
-        (long) kp.p_vm_ssize * pagesize  // stack
-#endif
-    );
-}
-
-
 /*
  * Return a Python tuple representing user, kernel and idle CPU times
  */
@@ -944,8 +842,11 @@ error:
  */
 static PyMethodDef
 PsutilMethods[] = {
+
     // --- per-process functions
 
+    {"proc_oneshot_info", psutil_proc_oneshot_info, METH_VARARGS,
+     "Return multiple info about a process"},
     {"proc_name", psutil_proc_name, METH_VARARGS,
      "Return process name"},
 #if !defined(__NetBSD__)
@@ -954,29 +855,8 @@ PsutilMethods[] = {
 #endif
     {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS,
      "Return process cmdline as a list of cmdline arguments"},
-    {"proc_ppid", psutil_proc_ppid, METH_VARARGS,
-     "Return process ppid as an integer"},
-    {"proc_uids", psutil_proc_uids, METH_VARARGS,
-     "Return process real effective and saved user ids as a Python tuple"},
-    {"proc_gids", psutil_proc_gids, METH_VARARGS,
-     "Return process real effective and saved group ids as a Python tuple"},
-    {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS,
-     "Return tuple of user/kern time for the given PID"},
-    {"proc_create_time", psutil_proc_create_time, METH_VARARGS,
-     "Return a float indicating the process create time expressed in "
-     "seconds since the epoch"},
-    {"proc_memory_info", psutil_proc_memory_info, METH_VARARGS,
-     "Return extended memory info for a process as a Python tuple."},
-    {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS,
-     "Return the number of context switches performed by process"},
     {"proc_threads", psutil_proc_threads, METH_VARARGS,
      "Return process threads"},
-    {"proc_status", psutil_proc_status, METH_VARARGS,
-     "Return process status as an integer"},
-    {"proc_io_counters", psutil_proc_io_counters, METH_VARARGS,
-     "Return process IO counters"},
-    {"proc_tty_nr", psutil_proc_tty_nr, METH_VARARGS,
-     "Return process tty (terminal) number"},
 #if defined(__FreeBSD__) || defined(__OpenBSD__)
     {"proc_cwd", psutil_proc_cwd, METH_VARARGS,
      "Return process current working directory."},
diff --git a/psutil/_psutil_osx.c b/psutil/_psutil_osx.c
index 851b8f6a..a1168c29 100644
--- a/psutil/_psutil_osx.c
+++ b/psutil/_psutil_osx.c
@@ -119,6 +119,98 @@ error:
 }
 
 
+/*
+ * Return multiple process info as a Python tuple in one shot by
+ * using sysctl() and filling up a kinfo_proc struct.
+ * It should be possible to do this for all processes without
+ * incurring into permission (EPERM) errors.
+ */
+static PyObject *
+psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args) {
+    long pid;
+    struct kinfo_proc kp;
+    PyObject *py_name;
+    PyObject *py_retlist;
+
+    if (! PyArg_ParseTuple(args, "l", &pid))
+        return NULL;
+    if (psutil_get_kinfo_proc(pid, &kp) == -1)
+        return NULL;
+
+#if PY_MAJOR_VERSION >= 3
+    py_name = PyUnicode_DecodeFSDefault(kp.kp_proc.p_comm);
+#else
+    py_name = Py_BuildValue("s", kp.kp_proc.p_comm);
+#endif
+    if (! py_name) {
+        // Likely a decoding error. We don't want to fail the whole
+        // operation. The python module may retry with proc_name().
+        PyErr_Clear();
+        py_name = Py_None;
+    }
+
+    py_retlist = Py_BuildValue(
+        "lllllllidiO",
+        (long)kp.kp_eproc.e_ppid,                  // (long) ppid
+        (long)kp.kp_eproc.e_pcred.p_ruid,          // (long) real uid
+        (long)kp.kp_eproc.e_ucred.cr_uid,          // (long) effective uid
+        (long)kp.kp_eproc.e_pcred.p_svuid,         // (long) saved uid
+        (long)kp.kp_eproc.e_pcred.p_rgid,          // (long) real gid
+        (long)kp.kp_eproc.e_ucred.cr_groups[0],    // (long) effective gid
+        (long)kp.kp_eproc.e_pcred.p_svgid,         // (long) saved gid
+        kp.kp_eproc.e_tdev,                        // (int) tty nr
+        PSUTIL_TV2DOUBLE(kp.kp_proc.p_starttime),  // (double) create time
+        (int)kp.kp_proc.p_stat,                    // (int) status
+        py_name                                    // (pystr) name
+    );
+
+    if (py_retlist != NULL) {
+        // XXX shall we decref() also in case of Py_BuildValue() error?
+        Py_DECREF(py_name);
+    }
+    return py_retlist;
+}
+
+
+/*
+ * Return multiple process info as a Python tuple in one shot by
+ * using proc_pidinfo(PROC_PIDTASKINFO) and filling a proc_taskinfo
+ * struct.
+ * Contrarily from proc_kinfo above this function will return EACCES
+ * for PIDs owned by another user.
+ */
+static PyObject *
+psutil_proc_pidtaskinfo_oneshot(PyObject *self, PyObject *args) {
+    long pid;
+    struct proc_taskinfo pti;
+
+    if (! PyArg_ParseTuple(args, "l", &pid))
+        return NULL;
+    if (psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0)
+        return NULL;
+
+    return Py_BuildValue(
+        "(ddKKkkkk)",
+        (float)pti.pti_total_user / 1000000000.0,     // (float) cpu user time
+        (float)pti.pti_total_system / 1000000000.0,   // (float) cpu sys time
+        // Note about memory: determining other mem stats on OSX is a mess:
+        // http://www.opensource.apple.com/source/top/top-67/libtop.c?txt
+        // I just give up.
+        // struct proc_regioninfo pri;
+        // psutil_proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, sizeof(pri))
+        pti.pti_resident_size,  // (uns long long) rss
+        pti.pti_virtual_size,   // (uns long long) vms
+        pti.pti_faults,         // (uns long) number of page faults (pages)
+        pti.pti_pageins,        // (uns long) number of actual pageins (pages)
+        pti.pti_threadnum,      // (uns long) num threads
+        // Unvoluntary value seems not to be available;
+        // pti.pti_csw probably refers to the sum of the two;
+        // getrusage() numbers seems to confirm this theory.
+        pti.pti_csw             // (uns long) voluntary ctx switches
+    );
+}
+
+
 /*
  * Return process name from kinfo_proc as a Python string.
  */
@@ -226,72 +318,6 @@ psutil_proc_environ(PyObject *self, PyObject *args) {
 }
 
 
-/*
- * Return process parent pid from kinfo_proc as a Python integer.
- */
-static PyObject *
-psutil_proc_ppid(PyObject *self, PyObject *args) {
-    long pid;
-    struct kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_get_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    return Py_BuildValue("l", (long)kp.kp_eproc.e_ppid);
-}
-
-
-/*
- * Return process real uid from kinfo_proc as a Python integer.
- */
-static PyObject *
-psutil_proc_uids(PyObject *self, PyObject *args) {
-    long pid;
-    struct kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_get_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    return Py_BuildValue("lll",
-                         (long)kp.kp_eproc.e_pcred.p_ruid,
-                         (long)kp.kp_eproc.e_ucred.cr_uid,
-                         (long)kp.kp_eproc.e_pcred.p_svuid);
-}
-
-
-/*
- * Return process real group id from ki_comm as a Python integer.
- */
-static PyObject *
-psutil_proc_gids(PyObject *self, PyObject *args) {
-    long pid;
-    struct kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_get_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    return Py_BuildValue("lll",
-                         (long)kp.kp_eproc.e_pcred.p_rgid,
-                         (long)kp.kp_eproc.e_ucred.cr_groups[0],
-                         (long)kp.kp_eproc.e_pcred.p_svgid);
-}
-
-
-/*
- * Return process controlling terminal number as an integer.
- */
-static PyObject *
-psutil_proc_tty_nr(PyObject *self, PyObject *args) {
-    long pid;
-    struct kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_get_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    return Py_BuildValue("i", kp.kp_eproc.e_tdev);
-}
-
-
 /*
  * Return a list of tuples for every process memory maps.
  * 'procstat' cmdline utility has been used as an example.
@@ -485,67 +511,6 @@ psutil_cpu_count_phys(PyObject *self, PyObject *args) {
 }
 
 
-/*
- * Return a Python tuple (user_time, kernel_time)
- */
-static PyObject *
-psutil_proc_cpu_times(PyObject *self, PyObject *args) {
-    long pid;
-    struct proc_taskinfo pti;
-
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0)
-        return NULL;
-    return Py_BuildValue("(dd)",
-                         (float)pti.pti_total_user / 1000000000.0,
-                         (float)pti.pti_total_system / 1000000000.0);
-}
-
-
-/*
- * Return a Python float indicating the process create time expressed in
- * seconds since the epoch.
- */
-static PyObject *
-psutil_proc_create_time(PyObject *self, PyObject *args) {
-    long pid;
-    struct kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_get_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    return Py_BuildValue("d", PSUTIL_TV2DOUBLE(kp.kp_proc.p_starttime));
-}
-
-
-/*
- * Return extended memory info about a process.
- */
-static PyObject *
-psutil_proc_memory_info(PyObject *self, PyObject *args) {
-    long pid;
-    struct proc_taskinfo pti;
-
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0)
-        return NULL;
-    // Note: determining other memory stats on OSX is a mess:
-    // http://www.opensource.apple.com/source/top/top-67/libtop.c?txt
-    // I just give up...
-    // struct proc_regioninfo pri;
-    // psutil_proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, sizeof(pri))
-    return Py_BuildValue(
-        "(KKkk)",
-        pti.pti_resident_size,  // resident memory size (rss)
-        pti.pti_virtual_size,   // virtual memory size (vms)
-        pti.pti_faults,         // number of page faults (pages)
-        pti.pti_pageins         // number of actual pageins (pages)
-    );
-}
-
-
 /*
  * Indicates if the given virtual address on the given architecture is in the
  * shared VM region.
@@ -669,41 +634,6 @@ psutil_proc_memory_uss(PyObject *self, PyObject *args) {
 }
 
 
-/*
- * Return number of threads used by process as a Python integer.
- */
-static PyObject *
-psutil_proc_num_threads(PyObject *self, PyObject *args) {
-    long pid;
-    struct proc_taskinfo pti;
-
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0)
-        return NULL;
-    return Py_BuildValue("k", pti.pti_threadnum);
-}
-
-
-/*
- * Return the number of context switches performed by process.
- */
-static PyObject *
-psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) {
-    long pid;
-    struct proc_taskinfo pti;
-
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0)
-        return NULL;
-    // unvoluntary value seems not to be available;
-    // pti.pti_csw probably refers to the sum of the two (getrusage()
-    // numbers seems to confirm this theory).
-    return Py_BuildValue("ki", pti.pti_csw, 0);
-}
-
-
 /*
  * Return system virtual memory stats.
  * See:
@@ -1021,21 +951,6 @@ error:
 }
 
 
-/*
- * Return process status as a Python integer.
- */
-static PyObject *
-psutil_proc_status(PyObject *self, PyObject *args) {
-    long pid;
-    struct kinfo_proc kp;
-    if (! PyArg_ParseTuple(args, "l", &pid))
-        return NULL;
-    if (psutil_get_kinfo_proc(pid, &kp) == -1)
-        return NULL;
-    return Py_BuildValue("i", (int)kp.kp_proc.p_stat);
-}
-
-
 /*
  * Return process threads
  */
@@ -1820,6 +1735,10 @@ PsutilMethods[] = {
 
     // --- per-process functions
 
+    {"proc_kinfo_oneshot", psutil_proc_kinfo_oneshot, METH_VARARGS,
+     "Return multiple process info."},
+    {"proc_pidtaskinfo_oneshot", psutil_proc_pidtaskinfo_oneshot, METH_VARARGS,
+     "Return multiple process info."},
     {"proc_name", psutil_proc_name, METH_VARARGS,
      "Return process name"},
     {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS,
@@ -1830,37 +1749,16 @@ PsutilMethods[] = {
      "Return path of the process executable"},
     {"proc_cwd", psutil_proc_cwd, METH_VARARGS,
      "Return process current working directory."},
-    {"proc_ppid", psutil_proc_ppid, METH_VARARGS,
-     "Return process ppid as an integer"},
-    {"proc_uids", psutil_proc_uids, METH_VARARGS,
-     "Return process real user id as an integer"},
-    {"proc_gids", psutil_proc_gids, METH_VARARGS,
-     "Return process real group id as an integer"},
-    {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS,
-     "Return tuple of user/kern time for the given PID"},
-    {"proc_create_time", psutil_proc_create_time, METH_VARARGS,
-     "Return a float indicating the process create time expressed in "
-     "seconds since the epoch"},
-    {"proc_memory_info", psutil_proc_memory_info, METH_VARARGS,
-     "Return memory information about a process"},
     {"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS,
      "Return process USS memory"},
-    {"proc_num_threads", psutil_proc_num_threads, METH_VARARGS,
-     "Return number of threads used by process"},
-    {"proc_status", psutil_proc_status, METH_VARARGS,
-     "Return process status as an integer"},
     {"proc_threads", psutil_proc_threads, METH_VARARGS,
      "Return process threads as a list of tuples"},
     {"proc_open_files", psutil_proc_open_files, METH_VARARGS,
      "Return files opened by process as a list of tuples"},
     {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS,
      "Return the number of fds opened by process."},
-    {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS,
-     "Return the number of context switches performed by process"},
     {"proc_connections", psutil_proc_connections, METH_VARARGS,
      "Get process TCP and UDP connections as a list of tuples"},
-    {"proc_tty_nr", psutil_proc_tty_nr, METH_VARARGS,
-     "Return process tty number as an integer"},
     {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS,
      "Return a list of tuples for every process's memory map"},
 
diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py
index d41eeef3..d956b91f 100644
--- a/psutil/_pswindows.py
+++ b/psutil/_pswindows.py
@@ -19,6 +19,7 @@ from ._common import parse_environ_block
 from ._common import sockfam_to_enum
 from ._common import socktype_to_enum
 from ._common import usage_percent
+from ._common import memoize_when_activated
 from ._compat import long
 from ._compat import lru_cache
 from ._compat import PY3
@@ -576,6 +577,15 @@ class Process(object):
         self._name = None
         self._ppid = None
 
+    # --- oneshot() stuff
+
+    def oneshot_enter(self):
+        self.oneshot_info.cache_activate()
+
+    def oneshot_exit(self):
+        self.oneshot_info.cache_deactivate()
+
+    @memoize_when_activated
     def oneshot_info(self):
         """Return multiple information about this process as a
         raw tuple.
diff --git a/psutil/arch/windows/process_info.c b/psutil/arch/windows/process_info.c
index 5b0b7726..e29f2161 100644
--- a/psutil/arch/windows/process_info.c
+++ b/psutil/arch/windows/process_info.c
@@ -67,6 +67,21 @@ psutil_handle_from_pid(DWORD pid) {
 }
 
 
+/*
+ * Given a Python int referencing a process handle close the process handle.
+ */
+PyObject *
+psutil_win32_CloseHandle(PyObject *self, PyObject *args) {
+    unsigned long handle;
+
+    if (! PyArg_ParseTuple(args, "k", &handle))
+        return NULL;
+    // TODO: may want to check return value;
+    CloseHandle((HANDLE)handle);
+    Py_RETURN_NONE;
+}
+
+
 DWORD *
 psutil_get_pids(DWORD *numberOfReturnedPIDs) {
     // Win32 SDK says the only way to know if our process array
diff --git a/psutil/arch/windows/process_info.h b/psutil/arch/windows/process_info.h
index f9af7765..7c2c9c2b 100644
--- a/psutil/arch/windows/process_info.h
+++ b/psutil/arch/windows/process_info.h
@@ -12,9 +12,16 @@
 #include "security.h"
 #include "ntextapi.h"
 
+#define HANDLE_TO_PYNUM(handle) PyLong_FromUnsignedLong((unsigned long) handle)
+#define PYNUM_TO_HANDLE(obj) ((HANDLE)PyLong_AsUnsignedLong(obj))
+
+
 DWORD* psutil_get_pids(DWORD *numberOfReturnedPIDs);
 HANDLE psutil_handle_from_pid(DWORD pid);
 HANDLE psutil_handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess);
+PyObject* psutil_win32_OpenProcess(PyObject *self, PyObject *args);
+PyObject* psutil_win32_CloseHandle(PyObject *self, PyObject *args);
+
 int psutil_handlep_is_running(HANDLE hProcess);
 int psutil_pid_in_proclist(DWORD pid);
 int psutil_pid_is_running(DWORD pid);
diff --git a/scripts/internal/bench_oneshot.py b/scripts/internal/bench_oneshot.py
new file mode 100755
index 00000000..cf8497f8
--- /dev/null
+++ b/scripts/internal/bench_oneshot.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+A simple micro benchmark script which prints the speedup when using
+Process.oneshot() ctx manager.
+See: https://github.com/giampaolo/psutil/issues/799
+"""
+
+from __future__ import print_function, division
+import sys
+import timeit
+import textwrap
+
+import psutil
+
+
+ITERATIONS = 1000
+
+# The list of Process methods which gets collected in one shot and
+# as such get advantage of the speedup.
+names = [
+    'cpu_times',
+    'cpu_percent',
+    'memory_info',
+    'memory_percent',
+    'ppid',
+    'parent',
+]
+
+if psutil.POSIX:
+    names.append('uids')
+    names.append('username')
+
+if psutil.LINUX:
+    names += [
+        'cpu_times',
+        'gids',
+        # 'memory_full_info',
+        # 'memory_maps',
+        'name',
+        'num_ctx_switches',
+        'num_threads',
+        'ppid',
+        'status',
+        'terminal',
+        'uids',
+    ]
+elif psutil.BSD:
+    names = [
+        'cpu_times',
+        'gids',
+        'io_counters',
+        'memory_full_info',
+        'memory_info',
+        'name',
+        'num_ctx_switches',
+        'ppid',
+        'status',
+        'terminal',
+        'uids',
+    ]
+elif psutil.SUNOS:
+    names += [
+        'cmdline',
+        'gids',
+        'memory_full_info',
+        'memory_info',
+        'name',
+        'num_threads',
+        'ppid',
+        'status',
+        'terminal',
+        'uids',
+    ]
+elif psutil.OSX:
+    names += [
+        'cpu_times',
+        'create_time',
+        'gids',
+        'memory_info',
+        'name',
+        'num_ctx_switches',
+        'num_threads',
+        'ppid',
+        'terminal',
+        'uids',
+    ]
+elif psutil.WINDOWS:
+    names += [
+        'num_ctx_switches',
+        'num_threads',
+        # dual implementation, called in case of AccessDenied
+        'num_handles',
+        'cpu_times',
+        'create_time',
+        'num_threads',
+        'io_counters',
+        'memory_info',
+    ]
+
+names = sorted(set(names))
+
+setup = textwrap.dedent("""
+    from __main__ import names
+    import psutil
+
+    def call_normal(funs):
+        for fun in funs:
+            fun()
+
+    def call_oneshot(funs):
+        with p.oneshot():
+            for fun in funs:
+                fun()
+
+    p = psutil.Process()
+    funs = [getattr(p, n) for n in names]
+    """)
+
+
+def main():
+    print("%s methods involved on platform %r (%s iterations, psutil %s):" % (
+        len(names), sys.platform, ITERATIONS, psutil.__version__))
+    for name in sorted(names):
+        print("    " + name)
+
+    # "normal" run
+    elapsed1 = timeit.timeit(
+        "call_normal(funs)", setup=setup, number=ITERATIONS)
+    print("normal:  %.3f secs" % elapsed1)
+
+    # "one shot" run
+    elapsed2 = timeit.timeit(
+        "call_oneshot(funs)", setup=setup, number=ITERATIONS)
+    print("onshot:  %.3f secs" % elapsed2)
+
+    # done
+    if elapsed2 < elapsed1:
+        print("speedup: +%.2fx" % (elapsed1 / elapsed2))
+    elif elapsed2 > elapsed1:
+        print("slowdown: -%.2fx" % (elapsed2 / elapsed1))
+    else:
+        print("same speed")
+
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/internal/bench_oneshot_2.py b/scripts/internal/bench_oneshot_2.py
new file mode 100644
index 00000000..a25d1806
--- /dev/null
+++ b/scripts/internal/bench_oneshot_2.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Same as bench_oneshot.py but uses perf module instead, which is
+supposed to be more precise.
+"""
+
+import sys
+
+import perf  # requires "pip install perf"
+
+import psutil
+from bench_oneshot import names
+
+
+p = psutil.Process()
+funs = [getattr(p, n) for n in names]
+
+
+def call_normal():
+    for fun in funs:
+        fun()
+
+
+def call_oneshot():
+    with p.oneshot():
+        for fun in funs:
+            fun()
+
+
+def add_cmdline_args(cmd, args):
+    cmd.append(args.benchmark)
+
+
+def main():
+    runner = perf.Runner()
+
+    args = runner.parse_args()
+    if not args.worker:
+        print("%s methods involved on platform %r (psutil %s):" % (
+            len(names), sys.platform, psutil.__version__))
+        for name in sorted(names):
+            print("    " + name)
+
+    runner.bench_func("normal", call_normal)
+    runner.bench_func("oneshot", call_oneshot)
+
+
+main()
diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py
index 8a8e763e..ec459025 100755
--- a/scripts/internal/winmake.py
+++ b/scripts/internal/winmake.py
@@ -33,6 +33,7 @@ DEPS = [
     "mock",
     "nose",
     "pdbpp",
+    "perf",
     "pip",
     "pypiwin32",
     "setuptools",
@@ -351,6 +352,18 @@ def install_git_hooks():
     shutil.copy(".git-pre-commit", ".git/hooks/pre-commit")
 
 
+@cmd
+def bench_oneshot():
+    install()
+    sh("%s scripts\\internal\\bench_oneshot.py" % PYTHON)
+
+
+@cmd
+def bench_oneshot_2():
+    install()
+    sh("%s scripts\\internal\\bench_oneshot_2.py" % PYTHON)
+
+
 def main():
     try:
         cmd = sys.argv[1].replace('-', '_')
diff --git a/scripts/iotop.py b/scripts/iotop.py
index a153f0f4..9f76eb1c 100755
--- a/scripts/iotop.py
+++ b/scripts/iotop.py
@@ -112,14 +112,15 @@ def poll(interval):
 
     # then retrieve the same info again
     for p in procs[:]:
-        try:
-            p._after = p.io_counters()
-            p._cmdline = ' '.join(p.cmdline())
-            if not p._cmdline:
-                p._cmdline = p.name()
-            p._username = p.username()
-        except (psutil.NoSuchProcess, psutil.ZombieProcess):
-            procs.remove(p)
+        with p.oneshot():
+            try:
+                p._after = p.io_counters()
+                p._cmdline = ' '.join(p.cmdline())
+                if not p._cmdline:
+                    p._cmdline = p.name()
+                p._username = p.username()
+            except (psutil.NoSuchProcess, psutil.ZombieProcess):
+                procs.remove(p)
     disks_after = psutil.disk_io_counters()
 
     # finally calculate results by comparing data before and
diff --git a/scripts/pidof.py b/scripts/pidof.py
index 51e66a45..1c23900f 100755
--- a/scripts/pidof.py
+++ b/scripts/pidof.py
@@ -19,23 +19,24 @@ import sys
 def pidof(pgname):
     pids = []
     for proc in psutil.process_iter():
-        # search for matches in the process name and cmdline
-        try:
-            name = proc.name()
-        except psutil.Error:
-            pass
-        else:
-            if name == pgname:
-                pids.append(str(proc.pid))
-                continue
-
-        try:
-            cmdline = proc.cmdline()
-        except psutil.Error:
-            pass
-        else:
-            if cmdline and cmdline[0] == pgname:
-                pids.append(str(proc.pid))
+        with proc.oneshot():
+            # search for matches in the process name and cmdline
+            try:
+                name = proc.name()
+            except psutil.Error:
+                pass
+            else:
+                if name == pgname:
+                    pids.append(str(proc.pid))
+                    continue
+
+            try:
+                cmdline = proc.cmdline()
+            except psutil.Error:
+                pass
+            else:
+                if cmdline and cmdline[0] == pgname:
+                    pids.append(str(proc.pid))
 
     return pids
 
diff --git a/scripts/procinfo.py b/scripts/procinfo.py
index 52dcf7dc..8dc34c45 100755
--- a/scripts/procinfo.py
+++ b/scripts/procinfo.py
@@ -153,23 +153,24 @@ def run(pid, verbose=False):
         sys.exit(str(err))
 
     # collect other proc info
-    try:
-        parent = proc.parent()
-        if parent:
-            parent = '(%s)' % parent.name()
-        else:
+    with proc.oneshot():
+        try:
+            parent = proc.parent()
+            if parent:
+                parent = '(%s)' % parent.name()
+            else:
+                parent = ''
+        except psutil.Error:
             parent = ''
-    except psutil.Error:
-        parent = ''
-    try:
-        pinfo['children'] = proc.children()
-    except psutil.Error:
-        pinfo['children'] = []
-    if pinfo['create_time']:
-        started = datetime.datetime.fromtimestamp(
-            pinfo['create_time']).strftime('%Y-%m-%d %H:%M')
-    else:
-        started = ACCESS_DENIED
+        try:
+            pinfo['children'] = proc.children()
+        except psutil.Error:
+            pinfo['children'] = []
+        if pinfo['create_time']:
+            started = datetime.datetime.fromtimestamp(
+                pinfo['create_time']).strftime('%Y-%m-%d %H:%M')
+        else:
+            started = ACCESS_DENIED
 
     # here we go
     print_('pid', pinfo['pid'])
diff --git a/scripts/procsmem.py b/scripts/procsmem.py
index c0414aef..d7b53f62 100755
--- a/scripts/procsmem.py
+++ b/scripts/procsmem.py
@@ -61,22 +61,23 @@ def main():
     ad_pids = []
     procs = []
     for p in psutil.process_iter():
-        try:
-            mem = p.memory_full_info()
-            info = p.as_dict(attrs=["cmdline", "username"])
-        except psutil.AccessDenied:
-            ad_pids.append(p.pid)
-        except psutil.NoSuchProcess:
-            pass
-        else:
-            p._uss = mem.uss
-            p._rss = mem.rss
-            if not p._uss:
-                continue
-            p._pss = getattr(mem, "pss", "")
-            p._swap = getattr(mem, "swap", "")
-            p._info = info
-            procs.append(p)
+        with p.oneshot():
+            try:
+                mem = p.memory_full_info()
+                info = p.as_dict(attrs=["cmdline", "username"])
+            except psutil.AccessDenied:
+                ad_pids.append(p.pid)
+            except psutil.NoSuchProcess:
+                pass
+            else:
+                p._uss = mem.uss
+                p._rss = mem.rss
+                if not p._uss:
+                    continue
+                p._pss = getattr(mem, "pss", "")
+                p._swap = getattr(mem, "swap", "")
+                p._info = info
+                procs.append(p)
 
     procs.sort(key=lambda p: p._uss)
     templ = "%-7s %-7s %-30s %7s %7s %7s %7s"


Test patch:
diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py
index a9f86a32..025bdba4 100755
--- a/psutil/tests/test_misc.py
+++ b/psutil/tests/test_misc.py
@@ -25,6 +25,8 @@ from psutil import OPENBSD
 from psutil import OSX
 from psutil import POSIX
 from psutil import WINDOWS
+from psutil._common import memoize
+from psutil._common import memoize_when_activated
 from psutil._common import supports_ipv6
 from psutil.tests import APPVEYOR
 from psutil.tests import chdir
@@ -181,8 +183,6 @@ class TestMisc(unittest.TestCase):
         self.assertNotIn('foo', p.as_dict())
 
     def test_memoize(self):
-        from psutil._common import memoize
-
         @memoize
         def foo(*args, **kwargs):
             "foo docstring"
@@ -217,6 +217,32 @@ class TestMisc(unittest.TestCase):
         # docstring
         self.assertEqual(foo.__doc__, "foo docstring")
 
+    def test_memoize_when_activated(self):
+        class Foo:
+            @memoize_when_activated
+            def foo(self):
+                calls.append(None)
+
+        f = Foo()
+        calls = []
+        f.foo()
+        f.foo()
+        self.assertEqual(len(calls), 2)
+
+        # activate
+        calls = []
+        f.foo.cache_activate()
+        f.foo()
+        f.foo()
+        self.assertEqual(len(calls), 1)
+
+        # deactivate
+        calls = []
+        f.foo.cache_deactivate()
+        f.foo()
+        f.foo()
+        self.assertEqual(len(calls), 2)
+
     def test_parse_environ_block(self):
         from psutil._common import parse_environ_block
 
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index 6e4073d1..52651b44 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -1236,6 +1236,39 @@ class TestProcess(unittest.TestCase):
         with self.assertRaises(ValueError):
             p.as_dict(['foo', 'bar'])
 
+    def test_oneshot(self):
+        with mock.patch("psutil._psplatform.Process.cpu_times") as m:
+            p = psutil.Process()
+            with p.oneshot():
+                p.cpu_times()
+                p.cpu_times()
+            self.assertEqual(m.call_count, 1)
+
+        with mock.patch("psutil._psplatform.Process.cpu_times") as m:
+            p.cpu_times()
+            p.cpu_times()
+        self.assertEqual(m.call_count, 2)
+
+    def test_oneshot_twice(self):
+        # Test the case where the ctx manager is __enter__ed twice.
+        # The second __enter__ is supposed to resut in a NOOP.
+        with mock.patch("psutil._psplatform.Process.cpu_times") as m1:
+            with mock.patch("psutil._psplatform.Process.oneshot_enter") as m2:
+                p = psutil.Process()
+                with p.oneshot():
+                    p.cpu_times()
+                    p.cpu_times()
+                    with p.oneshot():
+                        p.cpu_times()
+                        p.cpu_times()
+                self.assertEqual(m1.call_count, 1)
+                self.assertEqual(m2.call_count, 1)
+
+        with mock.patch("psutil._psplatform.Process.cpu_times") as m:
+            p.cpu_times()
+            p.cpu_times()
+        self.assertEqual(m.call_count, 2)
+
     def test_halfway_terminated_process(self):
         # Test that NoSuchProcess exception gets raised in case the
         # process dies after we create the Process object.
@@ -1255,7 +1288,7 @@ class TestProcess(unittest.TestCase):
         #   retcode)
 
         excluded_names = ['pid', 'is_running', 'wait', 'create_time',
-                          'memory_info_ex']
+                          'oneshot', 'memory_info_ex']
         if LINUX and not RLIMIT_SUPPORT:
             excluded_names.append('rlimit')
         for name in dir(p):
@@ -1543,7 +1576,7 @@ class TestFetchAllProcesses(unittest.TestCase):
         excluded_names = set([
             'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait',
             'as_dict', 'cpu_percent', 'parent', 'children', 'pid',
-            'memory_info_ex',
+            'memory_info_ex', 'oneshot',
         ])
         if LINUX and not RLIMIT_SUPPORT:
             excluded_names.add('rlimit')
@@ -1557,10 +1590,10 @@ class TestFetchAllProcesses(unittest.TestCase):
 
         default = object()
         failures = []
-        for name in attrs:
-            for p in psutil.process_iter():
-                ret = default
-                try:
+        for p in psutil.process_iter():
+            with p.oneshot():
+                for name in attrs:
+                    ret = default
                     try:
                         args = ()
                         attr = getattr(p, name, None)
@@ -1583,23 +1616,23 @@ class TestFetchAllProcesses(unittest.TestCase):
                             self.assertEqual(err.name, p.name())
                         self.assertTrue(str(err))
                         self.assertTrue(err.msg)
+                    except Exception as err:
+                        s = '\n' + '=' * 70 + '\n'
+                        s += "FAIL: test_%s (proc=%s" % (name, p)
+                        if ret != default:
+                            s += ", ret=%s)" % repr(ret)
+                        s += ')\n'
+                        s += '-' * 70
+                        s += "\n%s" % traceback.format_exc()
+                        s = "\n".join((" " * 4) + i for i in s.splitlines())
+                        s += '\n'
+                        failures.append(s)
+                        break
                     else:
                         if ret not in (0, 0.0, [], None, '', {}):
                             assert ret, ret
                         meth = getattr(self, name)
                         meth(ret, p)
-                except Exception as err:
-                    s = '\n' + '=' * 70 + '\n'
-                    s += "FAIL: test_%s (proc=%s" % (name, p)
-                    if ret != default:
-                        s += ", ret=%s)" % repr(ret)
-                    s += ')\n'
-                    s += '-' * 70
-                    s += "\n%s" % traceback.format_exc()
-                    s = "\n".join((" " * 4) + i for i in s.splitlines())
-                    s += '\n'
-                    failures.append(s)
-                    break
 
         if failures:
             self.fail(''.join(failures))
diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py
index 40a84086..802242b5 100755
--- a/psutil/tests/test_windows.py
+++ b/psutil/tests/test_windows.py
@@ -15,7 +15,6 @@ import signal
 import subprocess
 import sys
 import time
-import traceback
 
 try:
     import win32api  # requires "pip install pypiwin32" / "make setup-dev-env"
@@ -29,7 +28,6 @@ import psutil
 from psutil import WINDOWS
 from psutil._compat import basestring
 from psutil._compat import callable
-from psutil._compat import long
 from psutil._compat import PY3
 from psutil.tests import APPVEYOR
 from psutil.tests import get_test_subprocess
@@ -359,15 +357,6 @@ class TestDualProcessImplementation(unittest.TestCase):
     https://github.com/giampaolo/psutil/issues/304
     """
 
-    fun_names = [
-        # function name, tolerance
-        ('proc_cpu_times', 0.2),
-        ('proc_create_time', 0.5),
-        ('proc_num_handles', 1),  # 1 because impl #1 opens a handle
-        ('proc_memory_info', 1024),  # KB
-        ('proc_io_counters', 0),
-    ]
-
     @classmethod
     def setUpClass(cls):
         cls.pid = get_test_subprocess().pid
@@ -375,118 +364,6 @@ class TestDualProcessImplementation(unittest.TestCase):
     @classmethod
     def tearDownClass(cls):
         reap_children()
-
-    def test_all_procs(self):
-        from psutil._pswindows import pinfo_map
-
-        def assert_ge_0(obj):
-            if isinstance(obj, (tuple, list)):
-                for value in obj:
-                    self.assertGreaterEqual(value, 0, msg=obj)
-            elif isinstance(obj, (int, long, float)):
-                self.assertGreaterEqual(obj, 0)
-            else:
-                assert 0  # case not handled which needs to be fixed
-
-        def compare_with_tolerance(ret1, ret2, tolerance):
-            if ret1 == ret2:
-                return
-            else:
-                if isinstance(ret2, (int, long, float)):
-                    diff = abs(ret1 - ret2)
-                    self.assertLessEqual(diff, tolerance)
-                elif isinstance(ret2, tuple):
-                    for a, b in zip(ret1, ret2):
-                        diff = abs(a - b)
-                        self.assertLessEqual(diff, tolerance)
-
-        failures = []
-        for p in psutil.process_iter():
-            try:
-                raw_info = cext.proc_info(p.pid)
-            except psutil.NoSuchProcess:
-                continue
-            assert_ge_0(raw_info)
-
-            for name, tolerance in self.fun_names:
-                if name == 'proc_memory_info' and p.pid == os.getpid():
-                    continue
-                if name == 'proc_create_time' and p.pid in (0, 4):
-                    continue
-                meth = wrap_exceptions(getattr(cext, name))
-                try:
-                    ret = meth(p.pid)
-                except (psutil.NoSuchProcess, psutil.AccessDenied):
-                    continue
-                # compare values
-                try:
-                    if name == 'proc_cpu_times':
-                        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, raw_info[pinfo_map['create_time']], tolerance)
-                    elif name == 'proc_num_handles':
-                        compare_with_tolerance(
-                            ret, raw_info[pinfo_map['num_handles']], tolerance)
-                    elif name == 'proc_io_counters':
-                        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':
-                        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, raw_info)
-                    failures.append(msg)
-                    break
-
-        if failures:
-            self.fail('\n\n'.join(failures))
-
     # ---
     # same tests as above but mimicks the AccessDenied failure of
     # the first (fast) method failing with AD.
@@ -549,14 +426,6 @@ class TestDualProcessImplementation(unittest.TestCase):
             psutil.Process(self.pid).num_handles() == num_handles
             assert fun.called
 
-    def test_zombies(self):
-        # test that NPS is raised by the 2nd implementation in case a
-        # process no longer exists
-        ZOMBIE_PID = max(psutil.pids()) + 5000
-        for name, _ in self.fun_names:
-            meth = wrap_exceptions(getattr(cext, name))
-            self.assertRaises(psutil.NoSuchProcess, meth, ZOMBIE_PID)
-
 
 @unittest.skipUnless(WINDOWS, "WINDOWS only")
 class RemoteProcessTestCase(unittest.TestCase):