bug 1481612 - vendor an unpacked win64 wheel of psutil 5.4.3. r=gps
authorTed Mielczarek <ted@mielczarek.org>
Wed, 10 Oct 2018 19:54:25 +0000
changeset 488962 7243432584616e49d01a0b8910518b359d5aab99
parent 488961 60407ad1392236779a537033965c59e3170416f3
child 488963 ec133330b1ec04135b786d0d3dc9a2fe4460d874
push id246
push userfmarier@mozilla.com
push dateSat, 13 Oct 2018 00:15:40 +0000
reviewersgps
bugs1481612
milestone64.0a1
bug 1481612 - vendor an unpacked win64 wheel of psutil 5.4.3. r=gps This change uses the previously added `--with-windows-wheel` option to vendor a win64 wheel of psutil. This patch was produced by running: `mach vendor python --with-windows-wheel psutil==5.4.3`. We vendor this wheel unpacked so that we can insert it into sys.path in mach without needing a virtualenv in which to install a wheel with pip, since we have a chicken-and-egg problem there where configure creates the virtualenv but we'd like to be able to use psutil before we run configure. Differential Revision: https://phabricator.services.mozilla.com/D3436
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/DESCRIPTION.rst
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/METADATA
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/RECORD
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/WHEEL
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/metadata.json
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/top_level.txt
third_party/python/psutil-cp27-none-win_amd64/psutil/__init__.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_common.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_compat.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_exceptions.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psaix.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psbsd.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_pslinux.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psosx.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psposix.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_pssunos.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psutil_windows.pyd
third_party/python/psutil-cp27-none-win_amd64/psutil/_pswindows.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/__init__.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/__main__.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_aix.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_bsd.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_connections.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_contracts.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_linux.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_memory_leaks.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_misc.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_osx.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_posix.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_process.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_sunos.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_system.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_unicode.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_windows.py
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/DESCRIPTION.rst
@@ -0,0 +1,472 @@
+.. image:: https://img.shields.io/travis/giampaolo/psutil/master.svg?maxAge=3600&label=Linux%20/%20OSX
+    :target: https://travis-ci.org/giampaolo/psutil
+    :alt: Linux tests (Travis)
+
+.. image:: https://img.shields.io/appveyor/ci/giampaolo/psutil/master.svg?maxAge=3600&label=Windows
+    :target: https://ci.appveyor.com/project/giampaolo/psutil
+    :alt: Windows tests (Appveyor)
+
+.. image:: https://coveralls.io/repos/github/giampaolo/psutil/badge.svg?branch=master
+    :target: https://coveralls.io/github/giampaolo/psutil?branch=master
+    :alt: Test coverage (coverall.io)
+
+.. image:: https://readthedocs.org/projects/psutil/badge/?version=latest
+    :target: http://psutil.readthedocs.io/en/latest/?badge=latest
+    :alt: Documentation Status
+
+.. image:: https://img.shields.io/pypi/v/psutil.svg?label=pypi
+    :target: https://pypi.python.org/pypi/psutil/
+    :alt: Latest version
+
+.. image:: https://img.shields.io/github/stars/giampaolo/psutil.svg
+    :target: https://github.com/giampaolo/psutil/
+    :alt: Github stars
+
+.. image:: https://img.shields.io/pypi/l/psutil.svg
+    :target: https://pypi.python.org/pypi/psutil/
+    :alt: License
+
+===========
+Quick links
+===========
+
+- `Home page <https://github.com/giampaolo/psutil>`_
+- `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_
+- `Documentation <http://psutil.readthedocs.io>`_
+- `Download <https://pypi.python.org/pypi?:action=display&name=psutil#downloads>`_
+- `Forum <http://groups.google.com/group/psutil/topics>`_
+- `Blog <http://grodola.blogspot.com/search/label/psutil>`_
+- `Development guide <https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst>`_
+- `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`_
+
+=======
+Summary
+=======
+
+psutil (process and system utilities) is a cross-platform library for
+retrieving information on **running processes** and **system utilization**
+(CPU, memory, disks, network, sensors) in Python.
+It is useful mainly for **system monitoring**, **profiling and limiting process
+resources** and **management of running processes**.
+It implements many functionalities offered by UNIX command line tools such as:
+ps, top, lsof, netstat, ifconfig, who, df, kill, free, nice, ionice, iostat,
+iotop, uptime, pidof, tty, taskset, pmap.
+psutil currently supports the following platforms:
+
+- **Linux**
+- **Windows**
+- **OSX**,
+- **FreeBSD, OpenBSD**, **NetBSD**
+- **Sun Solaris**
+- **AIX**
+
+...both **32-bit** and **64-bit** architectures, with Python
+versions from **2.6 to 3.6**.
+`PyPy <http://pypy.org/>`__ is also known to work.
+
+====================
+Example applications
+====================
+
++------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+
+| .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/procinfo-small.png     | .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/top-small.png      |
+|    :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/procinfo.png          |     :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/top.png          |
++------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+
+| .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/procsmem-small.png     | .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/pmap-small.png     |
+|     :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/procsmem.png         |     :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/pmap.png         |
++------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+
+
+Also see `scripts directory <https://github.com/giampaolo/psutil/tree/master/scripts>`__
+and `doc recipes <http://psutil.readthedocs.io/#recipes/>`__.
+
+=====================
+Projects using psutil
+=====================
+
+At the time of writing psutil has roughly
+`2.9 milion downloads <https://github.com/giampaolo/psutil/issues/1053#issuecomment-340166262>`__
+per month and there are over
+`6000 open source projects <https://libraries.io/pypi/psutil/dependent_repositories?page=1>`__
+on github which depend from psutil.
+Here's some I find particularly interesting:
+
+- https://github.com/facebook/osquery/
+- https://github.com/nicolargo/glances
+- https://github.com/google/grr
+- https://github.com/Jahaja/psdash
+- https://github.com/ajenti/ajenti
+- https://github.com/home-assistant/home-assistant/
+
+========
+Portings
+========
+
+- Go: https://github.com/shirou/gopsutil
+- C: https://github.com/hamon-in/cpslib
+- Node: https://github.com/christkv/node-psutil
+- Rust: https://github.com/borntyping/rust-psutil
+- Ruby: https://github.com/spacewander/posixpsutil
+- Nim: https://github.com/johnscillieri/psutil-nim
+
+==============
+Example usages
+==============
+
+CPU
+===
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.cpu_times()
+    scputimes(user=3961.46, nice=169.729, system=2150.659, idle=16900.540, iowait=629.59, irq=0.0, softirq=19.42, steal=0.0, guest=0, nice=0.0)
+    >>>
+    >>> for x in range(3):
+    ...     psutil.cpu_percent(interval=1)
+    ...
+    4.0
+    5.9
+    3.8
+    >>>
+    >>> for x in range(3):
+    ...     psutil.cpu_percent(interval=1, percpu=True)
+    ...
+    [4.0, 6.9, 3.7, 9.2]
+    [7.0, 8.5, 2.4, 2.1]
+    [1.2, 9.0, 9.9, 7.2]
+    >>>
+    >>> for x in range(3):
+    ...     psutil.cpu_times_percent(interval=1, percpu=False)
+    ...
+    scputimes(user=1.5, nice=0.0, system=0.5, idle=96.5, iowait=1.5, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
+    scputimes(user=1.0, nice=0.0, system=0.0, idle=99.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
+    scputimes(user=2.0, nice=0.0, system=0.0, idle=98.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
+    >>>
+    >>> psutil.cpu_count()
+    4
+    >>> psutil.cpu_count(logical=False)
+    2
+    >>>
+    >>> psutil.cpu_stats()
+    scpustats(ctx_switches=20455687, interrupts=6598984, soft_interrupts=2134212, syscalls=0)
+    >>>
+    >>> psutil.cpu_freq()
+    scpufreq(current=931.42925, min=800.0, max=3500.0)
+    >>>
+
+Memory
+======
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.virtual_memory()
+    svmem(total=10367352832, available=6472179712, percent=37.6, used=8186245120, free=2181107712, active=4748992512, inactive=2758115328, buffers=790724608, cached=3500347392, shared=787554304)
+    >>> psutil.swap_memory()
+    sswap(total=2097147904, used=296128512, free=1801019392, percent=14.1, sin=304193536, sout=677842944)
+    >>>
+
+Disks
+=====
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.disk_partitions()
+    [sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid'),
+     sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw')]
+    >>>
+    >>> psutil.disk_usage('/')
+    sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5)
+    >>>
+    >>> psutil.disk_io_counters(perdisk=False)
+    sdiskio(read_count=719566, write_count=1082197, read_bytes=18626220032, write_bytes=24081764352, read_time=5023392, write_time=63199568, read_merged_count=619166, write_merged_count=812396, busy_time=4523412)
+    >>>
+
+Network
+=======
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.net_io_counters(pernic=True)
+    {'eth0': netio(bytes_sent=485291293, bytes_recv=6004858642, packets_sent=3251564, packets_recv=4787798, errin=0, errout=0, dropin=0, dropout=0),
+     'lo': netio(bytes_sent=2838627, bytes_recv=2838627, packets_sent=30567, packets_recv=30567, errin=0, errout=0, dropin=0, dropout=0)}
+    >>>
+    >>> psutil.net_connections()
+    [sconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED', pid=1254),
+     sconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING', pid=2987),
+     sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=60759), raddr=addr(ip='72.14.234.104', port=80), status='ESTABLISHED', pid=None),
+     sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=51314), raddr=addr(ip='72.14.234.83', port=443), status='SYN_SENT', pid=None)
+     ...]
+    >>>
+    >>> psutil.net_if_addrs()
+    {'lo': [snic(family=<AddressFamily.AF_INET: 2>, address='127.0.0.1', netmask='255.0.0.0', broadcast='127.0.0.1', ptp=None),
+            snic(family=<AddressFamily.AF_INET6: 10>, address='::1', netmask='ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', broadcast=None, ptp=None),
+            snic(family=<AddressFamily.AF_LINK: 17>, address='00:00:00:00:00:00', netmask=None, broadcast='00:00:00:00:00:00', ptp=None)],
+     'wlan0': [snic(family=<AddressFamily.AF_INET: 2>, address='192.168.1.3', netmask='255.255.255.0', broadcast='192.168.1.255', ptp=None),
+               snic(family=<AddressFamily.AF_INET6: 10>, address='fe80::c685:8ff:fe45:641%wlan0', netmask='ffff:ffff:ffff:ffff::', broadcast=None, ptp=None),
+               snic(family=<AddressFamily.AF_LINK: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)]}
+    >>>
+    >>> psutil.net_if_stats()
+    {'eth0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500),
+     'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536)}
+    >>>
+
+Sensors
+=======
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.sensors_temperatures()
+    {'acpitz': [shwtemp(label='', current=47.0, high=103.0, critical=103.0)],
+     'asus': [shwtemp(label='', current=47.0, high=None, critical=None)],
+     'coretemp': [shwtemp(label='Physical id 0', current=52.0, high=100.0, critical=100.0),
+                  shwtemp(label='Core 0', current=45.0, high=100.0, critical=100.0),
+                  shwtemp(label='Core 1', current=52.0, high=100.0, critical=100.0),
+                  shwtemp(label='Core 2', current=45.0, high=100.0, critical=100.0),
+                  shwtemp(label='Core 3', current=47.0, high=100.0, critical=100.0)]}
+    >>>
+    >>> psutil.sensors_fans()
+    {'asus': [sfan(label='cpu_fan', current=3200)]}
+    >>>
+    >>> psutil.sensors_battery()
+    sbattery(percent=93, secsleft=16628, power_plugged=False)
+    >>>
+
+Other system info
+=================
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.users()
+    [suser(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0, pid=1352),
+     suser(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0, pid=1788)]
+    >>>
+    >>> psutil.boot_time()
+    1365519115.0
+    >>>
+
+Process management
+==================
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.pids()
+    [1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, 268, 1215, 1216, 1220, 1221, 1243, 1244,
+     1301, 1601, 2237, 2355, 2637, 2774, 3932, 4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, 4263, 4282,
+     4306, 4311, 4312, 4313, 4314, 4337, 4339, 4357, 4358, 4363, 4383, 4395, 4408, 4433, 4443, 4445, 4446,
+     5167, 5234, 5235, 5252, 5318, 5424, 5644, 6987, 7054, 7055, 7071]
+    >>>
+    >>> p = psutil.Process(7055)
+    >>> p.name()
+    'python'
+    >>> p.exe()
+    '/usr/bin/python'
+    >>> p.cwd()
+    '/home/giampaolo'
+    >>> p.cmdline()
+    ['/usr/bin/python', 'main.py']
+    >>>
+    >>> p.pid
+    7055
+    >>> p.ppid()
+    7054
+    >>> p.parent()
+    <psutil.Process(pid=7054, name='bash') at 140008329539408>
+    >>> p.children()
+    [<psutil.Process(pid=8031, name='python') at 14020832451977>,
+     <psutil.Process(pid=8044, name='python') at 19229444921932>]
+    >>>
+    >>> p.status()
+    'running'
+    >>> p.username()
+    'giampaolo'
+    >>> p.create_time()
+    1267551141.5019531
+    >>> p.terminal()
+    '/dev/pts/0'
+    >>>
+    >>> p.uids()
+    puids(real=1000, effective=1000, saved=1000)
+    >>> p.gids()
+    pgids(real=1000, effective=1000, saved=1000)
+    >>>
+    >>> p.cpu_times()
+    pcputimes(user=1.02, system=0.31, children_user=0.32, children_system=0.1)
+    >>> p.cpu_percent(interval=1.0)
+    12.1
+    >>> p.cpu_affinity()
+    [0, 1, 2, 3]
+    >>> p.cpu_affinity([0, 1])  # set
+    >>> p.cpu_num()
+    1
+    >>>
+    >>> p.memory_info()
+    pmem(rss=10915840, vms=67608576, shared=3313664, text=2310144, lib=0, data=7262208, dirty=0)
+    >>> p.memory_full_info()  # "real" USS memory usage (Linux, OSX, Win only)
+    pfullmem(rss=10199040, vms=52133888, shared=3887104, text=2867200, lib=0, data=5967872, dirty=0, uss=6545408, pss=6872064, swap=0)
+    >>> p.memory_percent()
+    0.7823
+    >>> p.memory_maps()
+    [pmmap_grouped(path='/lib/x8664-linux-gnu/libutil-2.15.so', rss=32768, size=2125824, pss=32768, shared_clean=0, shared_dirty=0, private_clean=20480, private_dirty=12288, referenced=32768, anonymous=12288, swap=0),
+     pmmap_grouped(path='/lib/x8664-linux-gnu/libc-2.15.so', rss=3821568, size=3842048, pss=3821568, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=3821568, referenced=3575808, anonymous=3821568, swap=0),
+     pmmap_grouped(path='/lib/x8664-linux-gnu/libcrypto.so.0.1', rss=34124, rss=32768, size=2134016, pss=15360, shared_clean=24576, shared_dirty=0, private_clean=0, private_dirty=8192, referenced=24576, anonymous=8192, swap=0),
+     pmmap_grouped(path='[heap]',  rss=32768, size=139264, pss=32768, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=32768, referenced=32768, anonymous=32768, swap=0),
+     pmmap_grouped(path='[stack]', rss=2465792, size=2494464, pss=2465792, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=2465792, referenced=2277376, anonymous=2465792, swap=0),
+     ...]
+    >>>
+    >>> p.io_counters()
+    pio(read_count=478001, write_count=59371, read_bytes=700416, write_bytes=69632, read_chars=456232, write_chars=517543)
+    >>>
+    >>> p.open_files()
+    [popenfile(path='/home/giampaolo/svn/psutil/setup.py', fd=3, position=0, mode='r', flags=32768),
+     popenfile(path='/var/log/monitd', fd=4, position=235542, mode='a', flags=33793)]
+    >>>
+    >>> p.connections()
+    [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED'),
+     pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING'),
+     pconn(fd=119, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=60759), raddr=addr(ip='72.14.234.104', port=80), status='ESTABLISHED'),
+     pconn(fd=123, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=51314), raddr=addr(ip='72.14.234.83', port=443), status='SYN_SENT')]
+    >>>
+    >>> p.num_threads()
+    4
+    >>> p.num_fds()
+    8
+    >>> p.threads()
+    [pthread(id=5234, user_time=22.5, system_time=9.2891),
+     pthread(id=5235, user_time=0.0, system_time=0.0),
+     pthread(id=5236, user_time=0.0, system_time=0.0),
+     pthread(id=5237, user_time=0.0707, system_time=1.1)]
+    >>>
+    >>> p.num_ctx_switches()
+    pctxsw(voluntary=78, involuntary=19)
+    >>>
+    >>> p.nice()
+    0
+    >>> p.nice(10)  # set
+    >>>
+    >>> p.ionice(psutil.IOPRIO_CLASS_IDLE)  # IO priority (Win and Linux only)
+    >>> p.ionice()
+    pionice(ioclass=<IOPriority.IOPRIO_CLASS_IDLE: 3>, value=0)
+    >>>
+    >>> p.rlimit(psutil.RLIMIT_NOFILE, (5, 5))  # set resource limits (Linux only)
+    >>> p.rlimit(psutil.RLIMIT_NOFILE)
+    (5, 5)
+    >>>
+    >>> p.environ()
+    {'LC_PAPER': 'it_IT.UTF-8', 'SHELL': '/bin/bash', 'GREP_OPTIONS': '--color=auto',
+    'XDG_CONFIG_DIRS': '/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg', 'COLORTERM': 'gnome-terminal',
+     ...}
+    >>>
+    >>> p.as_dict()
+    {'status': 'running', 'num_ctx_switches': pctxsw(voluntary=63, involuntary=1), 'pid': 5457, ...}
+    >>> p.is_running()
+    True
+    >>> p.suspend()
+    >>> p.resume()
+    >>>
+    >>> p.terminate()
+    >>> p.wait(timeout=3)
+    0
+    >>>
+    >>> psutil.test()
+    USER         PID %CPU %MEM     VSZ     RSS TTY        START    TIME  COMMAND
+    root           1  0.0  0.0   24584    2240            Jun17   00:00  init
+    root           2  0.0  0.0       0       0            Jun17   00:00  kthreadd
+    root           3  0.0  0.0       0       0            Jun17   00:05  ksoftirqd/0
+    ...
+    giampaolo  31475  0.0  0.0   20760    3024 /dev/pts/0 Jun19   00:00  python2.4
+    giampaolo  31721  0.0  2.2  773060  181896            00:04   10:30  chrome
+    root       31763  0.0  0.0       0       0            00:05   00:00  kworker/0:1
+    >>>
+
+Further process APIs
+====================
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> for proc in psutil.process_iter(attrs=['pid', 'name']):
+    ...     print(proc.info)
+    ...
+    {'pid': 1, 'name': 'systemd'}
+    {'pid': 2, 'name': 'kthreadd'}
+    {'pid': 3, 'name': 'ksoftirqd/0'}
+    ...
+    >>>
+    >>> psutil.pid_exists(3)
+    True
+    >>>
+    >>> def on_terminate(proc):
+    ...     print("process {} terminated".format(proc))
+    ...
+    >>> # waits for multiple processes to terminate
+    >>> gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate)
+    >>>
+
+Popen wrapper:
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> from subprocess import PIPE
+    >>> p = psutil.Popen(["/usr/bin/python", "-c", "print('hello')"], stdout=PIPE)
+    >>> p.name()
+    'python'
+    >>> p.username()
+    'giampaolo'
+    >>> p.communicate()
+    ('hello\n', None)
+    >>> p.wait(timeout=2)
+    0
+    >>>
+
+Windows services
+================
+
+.. code-block:: python
+
+    >>> list(psutil.win_service_iter())
+    [<WindowsService(name='AeLookupSvc', display_name='Application Experience') at 38850096>,
+     <WindowsService(name='ALG', display_name='Application Layer Gateway Service') at 38850128>,
+     <WindowsService(name='APNMCP', display_name='Ask Update Service') at 38850160>,
+     <WindowsService(name='AppIDSvc', display_name='Application Identity') at 38850192>,
+     ...]
+    >>> s = psutil.win_service_get('alg')
+    >>> s.as_dict()
+    {'binpath': 'C:\\Windows\\System32\\alg.exe',
+     'description': 'Provides support for 3rd party protocol plug-ins for Internet Connection Sharing',
+     'display_name': 'Application Layer Gateway Service',
+     'name': 'alg',
+     'pid': None,
+     'start_type': 'manual',
+     'status': 'stopped',
+     'username': 'NT AUTHORITY\\LocalService'}
+
+Other samples
+=============
+
+See `doc recipes <http://psutil.readthedocs.io/#recipes>`__.
+
+======
+Author
+======
+
+psutil was created and is maintained by
+`Giampaolo Rodola' <http://grodola.blogspot.com/p/about.html>`__.
+A lot of time and effort went into making psutil as it is right now.
+If you feel psutil is useful to you or your business and want to support its
+future development please consider donating me
+(`Giampaolo <http://grodola.blogspot.com/p/about.html>`__) some money.
+
+.. image:: http://www.paypal.com/en_US/i/btn/x-click-but04.gif
+    :target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8
+    :alt: Donate via PayPal
+
+Don't want to donate money? Then maybe you could `write me a recommendation on Linkedin <https://www.linkedin.com/in/grodola>`_.
+
+
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/METADATA
@@ -0,0 +1,524 @@
+Metadata-Version: 2.0
+Name: psutil
+Version: 5.4.3
+Summary: Cross-platform lib for process and system monitoring in Python.
+Home-page: https://github.com/giampaolo/psutil
+Author: Giampaolo Rodola
+Author-email: g.rodola@gmail.com
+License: BSD
+Keywords: ps,top,kill,free,lsof,netstat,nice,tty,ionice,uptime,taskmgr,process,df,iotop,iostat,ifconfig,taskset,who,pidof,pmap,smem,pstree,monitoring,ulimit,prlimit,smem
+Platform: Platform Independent
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Environment :: Win32 (MS Windows)
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Information Technology
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000
+Classifier: Operating System :: Microsoft
+Classifier: Operating System :: OS Independent
+Classifier: Operating System :: POSIX :: BSD :: FreeBSD
+Classifier: Operating System :: POSIX :: BSD :: NetBSD
+Classifier: Operating System :: POSIX :: BSD :: OpenBSD
+Classifier: Operating System :: POSIX :: BSD
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: POSIX :: SunOS/Solaris
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: System :: Benchmark
+Classifier: Topic :: System :: Hardware
+Classifier: Topic :: System :: Monitoring
+Classifier: Topic :: System :: Networking :: Monitoring
+Classifier: Topic :: System :: Networking
+Classifier: Topic :: System :: Operating System
+Classifier: Topic :: System :: Systems Administration
+Classifier: Topic :: Utilities
+Provides-Extra: enum
+Requires-Dist: enum34; extra == 'enum'
+
+.. image:: https://img.shields.io/travis/giampaolo/psutil/master.svg?maxAge=3600&label=Linux%20/%20OSX
+    :target: https://travis-ci.org/giampaolo/psutil
+    :alt: Linux tests (Travis)
+
+.. image:: https://img.shields.io/appveyor/ci/giampaolo/psutil/master.svg?maxAge=3600&label=Windows
+    :target: https://ci.appveyor.com/project/giampaolo/psutil
+    :alt: Windows tests (Appveyor)
+
+.. image:: https://coveralls.io/repos/github/giampaolo/psutil/badge.svg?branch=master
+    :target: https://coveralls.io/github/giampaolo/psutil?branch=master
+    :alt: Test coverage (coverall.io)
+
+.. image:: https://readthedocs.org/projects/psutil/badge/?version=latest
+    :target: http://psutil.readthedocs.io/en/latest/?badge=latest
+    :alt: Documentation Status
+
+.. image:: https://img.shields.io/pypi/v/psutil.svg?label=pypi
+    :target: https://pypi.python.org/pypi/psutil/
+    :alt: Latest version
+
+.. image:: https://img.shields.io/github/stars/giampaolo/psutil.svg
+    :target: https://github.com/giampaolo/psutil/
+    :alt: Github stars
+
+.. image:: https://img.shields.io/pypi/l/psutil.svg
+    :target: https://pypi.python.org/pypi/psutil/
+    :alt: License
+
+===========
+Quick links
+===========
+
+- `Home page <https://github.com/giampaolo/psutil>`_
+- `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_
+- `Documentation <http://psutil.readthedocs.io>`_
+- `Download <https://pypi.python.org/pypi?:action=display&name=psutil#downloads>`_
+- `Forum <http://groups.google.com/group/psutil/topics>`_
+- `Blog <http://grodola.blogspot.com/search/label/psutil>`_
+- `Development guide <https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst>`_
+- `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`_
+
+=======
+Summary
+=======
+
+psutil (process and system utilities) is a cross-platform library for
+retrieving information on **running processes** and **system utilization**
+(CPU, memory, disks, network, sensors) in Python.
+It is useful mainly for **system monitoring**, **profiling and limiting process
+resources** and **management of running processes**.
+It implements many functionalities offered by UNIX command line tools such as:
+ps, top, lsof, netstat, ifconfig, who, df, kill, free, nice, ionice, iostat,
+iotop, uptime, pidof, tty, taskset, pmap.
+psutil currently supports the following platforms:
+
+- **Linux**
+- **Windows**
+- **OSX**,
+- **FreeBSD, OpenBSD**, **NetBSD**
+- **Sun Solaris**
+- **AIX**
+
+...both **32-bit** and **64-bit** architectures, with Python
+versions from **2.6 to 3.6**.
+`PyPy <http://pypy.org/>`__ is also known to work.
+
+====================
+Example applications
+====================
+
++------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+
+| .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/procinfo-small.png     | .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/top-small.png      |
+|    :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/procinfo.png          |     :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/top.png          |
++------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+
+| .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/procsmem-small.png     | .. image:: https://github.com/giampaolo/psutil/blob/master/docs/_static/pmap-small.png     |
+|     :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/procsmem.png         |     :target: https://github.com/giampaolo/psutil/blob/master/docs/_static/pmap.png         |
++------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------+
+
+Also see `scripts directory <https://github.com/giampaolo/psutil/tree/master/scripts>`__
+and `doc recipes <http://psutil.readthedocs.io/#recipes/>`__.
+
+=====================
+Projects using psutil
+=====================
+
+At the time of writing psutil has roughly
+`2.9 milion downloads <https://github.com/giampaolo/psutil/issues/1053#issuecomment-340166262>`__
+per month and there are over
+`6000 open source projects <https://libraries.io/pypi/psutil/dependent_repositories?page=1>`__
+on github which depend from psutil.
+Here's some I find particularly interesting:
+
+- https://github.com/facebook/osquery/
+- https://github.com/nicolargo/glances
+- https://github.com/google/grr
+- https://github.com/Jahaja/psdash
+- https://github.com/ajenti/ajenti
+- https://github.com/home-assistant/home-assistant/
+
+========
+Portings
+========
+
+- Go: https://github.com/shirou/gopsutil
+- C: https://github.com/hamon-in/cpslib
+- Node: https://github.com/christkv/node-psutil
+- Rust: https://github.com/borntyping/rust-psutil
+- Ruby: https://github.com/spacewander/posixpsutil
+- Nim: https://github.com/johnscillieri/psutil-nim
+
+==============
+Example usages
+==============
+
+CPU
+===
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.cpu_times()
+    scputimes(user=3961.46, nice=169.729, system=2150.659, idle=16900.540, iowait=629.59, irq=0.0, softirq=19.42, steal=0.0, guest=0, nice=0.0)
+    >>>
+    >>> for x in range(3):
+    ...     psutil.cpu_percent(interval=1)
+    ...
+    4.0
+    5.9
+    3.8
+    >>>
+    >>> for x in range(3):
+    ...     psutil.cpu_percent(interval=1, percpu=True)
+    ...
+    [4.0, 6.9, 3.7, 9.2]
+    [7.0, 8.5, 2.4, 2.1]
+    [1.2, 9.0, 9.9, 7.2]
+    >>>
+    >>> for x in range(3):
+    ...     psutil.cpu_times_percent(interval=1, percpu=False)
+    ...
+    scputimes(user=1.5, nice=0.0, system=0.5, idle=96.5, iowait=1.5, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
+    scputimes(user=1.0, nice=0.0, system=0.0, idle=99.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
+    scputimes(user=2.0, nice=0.0, system=0.0, idle=98.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
+    >>>
+    >>> psutil.cpu_count()
+    4
+    >>> psutil.cpu_count(logical=False)
+    2
+    >>>
+    >>> psutil.cpu_stats()
+    scpustats(ctx_switches=20455687, interrupts=6598984, soft_interrupts=2134212, syscalls=0)
+    >>>
+    >>> psutil.cpu_freq()
+    scpufreq(current=931.42925, min=800.0, max=3500.0)
+    >>>
+
+Memory
+======
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.virtual_memory()
+    svmem(total=10367352832, available=6472179712, percent=37.6, used=8186245120, free=2181107712, active=4748992512, inactive=2758115328, buffers=790724608, cached=3500347392, shared=787554304)
+    >>> psutil.swap_memory()
+    sswap(total=2097147904, used=296128512, free=1801019392, percent=14.1, sin=304193536, sout=677842944)
+    >>>
+
+Disks
+=====
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.disk_partitions()
+    [sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid'),
+     sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw')]
+    >>>
+    >>> psutil.disk_usage('/')
+    sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5)
+    >>>
+    >>> psutil.disk_io_counters(perdisk=False)
+    sdiskio(read_count=719566, write_count=1082197, read_bytes=18626220032, write_bytes=24081764352, read_time=5023392, write_time=63199568, read_merged_count=619166, write_merged_count=812396, busy_time=4523412)
+    >>>
+
+Network
+=======
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.net_io_counters(pernic=True)
+    {'eth0': netio(bytes_sent=485291293, bytes_recv=6004858642, packets_sent=3251564, packets_recv=4787798, errin=0, errout=0, dropin=0, dropout=0),
+     'lo': netio(bytes_sent=2838627, bytes_recv=2838627, packets_sent=30567, packets_recv=30567, errin=0, errout=0, dropin=0, dropout=0)}
+    >>>
+    >>> psutil.net_connections()
+    [sconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED', pid=1254),
+     sconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING', pid=2987),
+     sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=60759), raddr=addr(ip='72.14.234.104', port=80), status='ESTABLISHED', pid=None),
+     sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=51314), raddr=addr(ip='72.14.234.83', port=443), status='SYN_SENT', pid=None)
+     ...]
+    >>>
+    >>> psutil.net_if_addrs()
+    {'lo': [snic(family=<AddressFamily.AF_INET: 2>, address='127.0.0.1', netmask='255.0.0.0', broadcast='127.0.0.1', ptp=None),
+            snic(family=<AddressFamily.AF_INET6: 10>, address='::1', netmask='ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', broadcast=None, ptp=None),
+            snic(family=<AddressFamily.AF_LINK: 17>, address='00:00:00:00:00:00', netmask=None, broadcast='00:00:00:00:00:00', ptp=None)],
+     'wlan0': [snic(family=<AddressFamily.AF_INET: 2>, address='192.168.1.3', netmask='255.255.255.0', broadcast='192.168.1.255', ptp=None),
+               snic(family=<AddressFamily.AF_INET6: 10>, address='fe80::c685:8ff:fe45:641%wlan0', netmask='ffff:ffff:ffff:ffff::', broadcast=None, ptp=None),
+               snic(family=<AddressFamily.AF_LINK: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)]}
+    >>>
+    >>> psutil.net_if_stats()
+    {'eth0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500),
+     'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536)}
+    >>>
+
+Sensors
+=======
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.sensors_temperatures()
+    {'acpitz': [shwtemp(label='', current=47.0, high=103.0, critical=103.0)],
+     'asus': [shwtemp(label='', current=47.0, high=None, critical=None)],
+     'coretemp': [shwtemp(label='Physical id 0', current=52.0, high=100.0, critical=100.0),
+                  shwtemp(label='Core 0', current=45.0, high=100.0, critical=100.0),
+                  shwtemp(label='Core 1', current=52.0, high=100.0, critical=100.0),
+                  shwtemp(label='Core 2', current=45.0, high=100.0, critical=100.0),
+                  shwtemp(label='Core 3', current=47.0, high=100.0, critical=100.0)]}
+    >>>
+    >>> psutil.sensors_fans()
+    {'asus': [sfan(label='cpu_fan', current=3200)]}
+    >>>
+    >>> psutil.sensors_battery()
+    sbattery(percent=93, secsleft=16628, power_plugged=False)
+    >>>
+
+Other system info
+=================
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.users()
+    [suser(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0, pid=1352),
+     suser(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0, pid=1788)]
+    >>>
+    >>> psutil.boot_time()
+    1365519115.0
+    >>>
+
+Process management
+==================
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> psutil.pids()
+    [1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, 268, 1215, 1216, 1220, 1221, 1243, 1244,
+     1301, 1601, 2237, 2355, 2637, 2774, 3932, 4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, 4263, 4282,
+     4306, 4311, 4312, 4313, 4314, 4337, 4339, 4357, 4358, 4363, 4383, 4395, 4408, 4433, 4443, 4445, 4446,
+     5167, 5234, 5235, 5252, 5318, 5424, 5644, 6987, 7054, 7055, 7071]
+    >>>
+    >>> p = psutil.Process(7055)
+    >>> p.name()
+    'python'
+    >>> p.exe()
+    '/usr/bin/python'
+    >>> p.cwd()
+    '/home/giampaolo'
+    >>> p.cmdline()
+    ['/usr/bin/python', 'main.py']
+    >>>
+    >>> p.pid
+    7055
+    >>> p.ppid()
+    7054
+    >>> p.parent()
+    <psutil.Process(pid=7054, name='bash') at 140008329539408>
+    >>> p.children()
+    [<psutil.Process(pid=8031, name='python') at 14020832451977>,
+     <psutil.Process(pid=8044, name='python') at 19229444921932>]
+    >>>
+    >>> p.status()
+    'running'
+    >>> p.username()
+    'giampaolo'
+    >>> p.create_time()
+    1267551141.5019531
+    >>> p.terminal()
+    '/dev/pts/0'
+    >>>
+    >>> p.uids()
+    puids(real=1000, effective=1000, saved=1000)
+    >>> p.gids()
+    pgids(real=1000, effective=1000, saved=1000)
+    >>>
+    >>> p.cpu_times()
+    pcputimes(user=1.02, system=0.31, children_user=0.32, children_system=0.1)
+    >>> p.cpu_percent(interval=1.0)
+    12.1
+    >>> p.cpu_affinity()
+    [0, 1, 2, 3]
+    >>> p.cpu_affinity([0, 1])  # set
+    >>> p.cpu_num()
+    1
+    >>>
+    >>> p.memory_info()
+    pmem(rss=10915840, vms=67608576, shared=3313664, text=2310144, lib=0, data=7262208, dirty=0)
+    >>> p.memory_full_info()  # "real" USS memory usage (Linux, OSX, Win only)
+    pfullmem(rss=10199040, vms=52133888, shared=3887104, text=2867200, lib=0, data=5967872, dirty=0, uss=6545408, pss=6872064, swap=0)
+    >>> p.memory_percent()
+    0.7823
+    >>> p.memory_maps()
+    [pmmap_grouped(path='/lib/x8664-linux-gnu/libutil-2.15.so', rss=32768, size=2125824, pss=32768, shared_clean=0, shared_dirty=0, private_clean=20480, private_dirty=12288, referenced=32768, anonymous=12288, swap=0),
+     pmmap_grouped(path='/lib/x8664-linux-gnu/libc-2.15.so', rss=3821568, size=3842048, pss=3821568, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=3821568, referenced=3575808, anonymous=3821568, swap=0),
+     pmmap_grouped(path='/lib/x8664-linux-gnu/libcrypto.so.0.1', rss=34124, rss=32768, size=2134016, pss=15360, shared_clean=24576, shared_dirty=0, private_clean=0, private_dirty=8192, referenced=24576, anonymous=8192, swap=0),
+     pmmap_grouped(path='[heap]',  rss=32768, size=139264, pss=32768, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=32768, referenced=32768, anonymous=32768, swap=0),
+     pmmap_grouped(path='[stack]', rss=2465792, size=2494464, pss=2465792, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=2465792, referenced=2277376, anonymous=2465792, swap=0),
+     ...]
+    >>>
+    >>> p.io_counters()
+    pio(read_count=478001, write_count=59371, read_bytes=700416, write_bytes=69632, read_chars=456232, write_chars=517543)
+    >>>
+    >>> p.open_files()
+    [popenfile(path='/home/giampaolo/svn/psutil/setup.py', fd=3, position=0, mode='r', flags=32768),
+     popenfile(path='/var/log/monitd', fd=4, position=235542, mode='a', flags=33793)]
+    >>>
+    >>> p.connections()
+    [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED'),
+     pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING'),
+     pconn(fd=119, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=60759), raddr=addr(ip='72.14.234.104', port=80), status='ESTABLISHED'),
+     pconn(fd=123, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=51314), raddr=addr(ip='72.14.234.83', port=443), status='SYN_SENT')]
+    >>>
+    >>> p.num_threads()
+    4
+    >>> p.num_fds()
+    8
+    >>> p.threads()
+    [pthread(id=5234, user_time=22.5, system_time=9.2891),
+     pthread(id=5235, user_time=0.0, system_time=0.0),
+     pthread(id=5236, user_time=0.0, system_time=0.0),
+     pthread(id=5237, user_time=0.0707, system_time=1.1)]
+    >>>
+    >>> p.num_ctx_switches()
+    pctxsw(voluntary=78, involuntary=19)
+    >>>
+    >>> p.nice()
+    0
+    >>> p.nice(10)  # set
+    >>>
+    >>> p.ionice(psutil.IOPRIO_CLASS_IDLE)  # IO priority (Win and Linux only)
+    >>> p.ionice()
+    pionice(ioclass=<IOPriority.IOPRIO_CLASS_IDLE: 3>, value=0)
+    >>>
+    >>> p.rlimit(psutil.RLIMIT_NOFILE, (5, 5))  # set resource limits (Linux only)
+    >>> p.rlimit(psutil.RLIMIT_NOFILE)
+    (5, 5)
+    >>>
+    >>> p.environ()
+    {'LC_PAPER': 'it_IT.UTF-8', 'SHELL': '/bin/bash', 'GREP_OPTIONS': '--color=auto',
+    'XDG_CONFIG_DIRS': '/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg', 'COLORTERM': 'gnome-terminal',
+     ...}
+    >>>
+    >>> p.as_dict()
+    {'status': 'running', 'num_ctx_switches': pctxsw(voluntary=63, involuntary=1), 'pid': 5457, ...}
+    >>> p.is_running()
+    True
+    >>> p.suspend()
+    >>> p.resume()
+    >>>
+    >>> p.terminate()
+    >>> p.wait(timeout=3)
+    0
+    >>>
+    >>> psutil.test()
+    USER         PID %CPU %MEM     VSZ     RSS TTY        START    TIME  COMMAND
+    root           1  0.0  0.0   24584    2240            Jun17   00:00  init
+    root           2  0.0  0.0       0       0            Jun17   00:00  kthreadd
+    root           3  0.0  0.0       0       0            Jun17   00:05  ksoftirqd/0
+    ...
+    giampaolo  31475  0.0  0.0   20760    3024 /dev/pts/0 Jun19   00:00  python2.4
+    giampaolo  31721  0.0  2.2  773060  181896            00:04   10:30  chrome
+    root       31763  0.0  0.0       0       0            00:05   00:00  kworker/0:1
+    >>>
+
+Further process APIs
+====================
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> for proc in psutil.process_iter(attrs=['pid', 'name']):
+    ...     print(proc.info)
+    ...
+    {'pid': 1, 'name': 'systemd'}
+    {'pid': 2, 'name': 'kthreadd'}
+    {'pid': 3, 'name': 'ksoftirqd/0'}
+    ...
+    >>>
+    >>> psutil.pid_exists(3)
+    True
+    >>>
+    >>> def on_terminate(proc):
+    ...     print("process {} terminated".format(proc))
+    ...
+    >>> # waits for multiple processes to terminate
+    >>> gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate)
+    >>>
+
+Popen wrapper:
+
+.. code-block:: python
+
+    >>> import psutil
+    >>> from subprocess import PIPE
+    >>> p = psutil.Popen(["/usr/bin/python", "-c", "print('hello')"], stdout=PIPE)
+    >>> p.name()
+    'python'
+    >>> p.username()
+    'giampaolo'
+    >>> p.communicate()
+    ('hello\n', None)
+    >>> p.wait(timeout=2)
+    0
+    >>>
+
+Windows services
+================
+
+.. code-block:: python
+
+    >>> list(psutil.win_service_iter())
+    [<WindowsService(name='AeLookupSvc', display_name='Application Experience') at 38850096>,
+     <WindowsService(name='ALG', display_name='Application Layer Gateway Service') at 38850128>,
+     <WindowsService(name='APNMCP', display_name='Ask Update Service') at 38850160>,
+     <WindowsService(name='AppIDSvc', display_name='Application Identity') at 38850192>,
+     ...]
+    >>> s = psutil.win_service_get('alg')
+    >>> s.as_dict()
+    {'binpath': 'C:\\Windows\\System32\\alg.exe',
+     'description': 'Provides support for 3rd party protocol plug-ins for Internet Connection Sharing',
+     'display_name': 'Application Layer Gateway Service',
+     'name': 'alg',
+     'pid': None,
+     'start_type': 'manual',
+     'status': 'stopped',
+     'username': 'NT AUTHORITY\\LocalService'}
+
+Other samples
+=============
+
+See `doc recipes <http://psutil.readthedocs.io/#recipes>`__.
+
+======
+Author
+======
+
+psutil was created and is maintained by
+`Giampaolo Rodola' <http://grodola.blogspot.com/p/about.html>`__.
+A lot of time and effort went into making psutil as it is right now.
+If you feel psutil is useful to you or your business and want to support its
+future development please consider donating me
+(`Giampaolo <http://grodola.blogspot.com/p/about.html>`__) some money.
+
+.. image:: http://www.paypal.com/en_US/i/btn/x-click-but04.gif
+    :target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8
+    :alt: Donate via PayPal
+
+Don't want to donate money? Then maybe you could `write me a recommendation on Linkedin <https://www.linkedin.com/in/grodola>`_.
+
+
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/RECORD
@@ -0,0 +1,34 @@
+psutil/__init__.py,sha256=220_B4k7vC9eoVP_OW8SFcaXCSBGpj_nacxvfBQ5O2o,84989
+psutil/_common.py,sha256=df5J2HqrWvGoFdumxDL1q9fgJOOYyiCmV0EFxb4ghuY,17852
+psutil/_compat.py,sha256=0VqlfcCGD5tZRBW-mFroguS0J7YNw6y3LjEcX5ChXDc,8170
+psutil/_exceptions.py,sha256=37eRgLuwTy7X4tjovePfZfYif-veW6LFeRlPqsgnNBU,3009
+psutil/_psaix.py,sha256=uAqwhQHS0upGc8OIk8Qa5PScCwXNvaMkoIdltZZ0n8A,19376
+psutil/_psbsd.py,sha256=ewmj2_DfzZpQZEcRvo1xvjVFhQqJidnENLgS8jrBb68,30244
+psutil/_pslinux.py,sha256=ME3wEgxfOWSFxgKXpTKbFw29ur1toMT4DdkKLJGkaD4,74850
+psutil/_psosx.py,sha256=X2WzhpuGjffjzFt3B3hphX7piPRCrPsjJrk6p6vu96w,17213
+psutil/_psposix.py,sha256=AmpZmKnvzCFcf7RpYypiUxOxKoRdXHRy2p9hFEIAJVc,6345
+psutil/_pssunos.py,sha256=uEsntvZPXplCuP9ER2W1QaGXjOqUZx_qFx2iLeyc5V8,25650
+psutil/_psutil_windows.pyd,sha256=wbQ_dSXTvdmCyQV02P7uCk95MwDkZ_D1g5r4G7IiYr8,59904
+psutil/_pswindows.py,sha256=_KYB-Vyhnz2A3l8I7tuXvFSrBPqKIb9slcwh5Azj0QU,33121
+psutil/tests/__init__.py,sha256=hkGHtLQNHjKrqhuV3Sk0kkHir17Mlam-amZBkLdfGwA,37656
+psutil/tests/__main__.py,sha256=dssAzXI-sAHkObtruDvc1cYUFNWOi-ZXETQgwaLKJS0,2784
+psutil/tests/test_aix.py,sha256=_ItFqb-CmYHlnlcduSKbgsPBw_xEIfE5v_pOr_YD8gw,4483
+psutil/tests/test_bsd.py,sha256=aif308A3TRnPZGY1tmNlH25YCweeSMpwbkah2K13wZs,17785
+psutil/tests/test_connections.py,sha256=eXOPOkusHO6Xc8gNrDxPBhq1Ni2fYYe0AAzeGU4gijQ,20676
+psutil/tests/test_contracts.py,sha256=ryPd7QDkQKxh8Mjcl-iPflXw5OTi8TWWLzQjIJ4EW2w,24136
+psutil/tests/test_linux.py,sha256=_gQBxUSiRSygoGfFXn-2m5XpXJQMQSiurVobrwkaAgc,78746
+psutil/tests/test_memory_leaks.py,sha256=v-RmU0BAzH05pkQ1dUV1hUXxlMva7943GFvT9Erl6J4,18259
+psutil/tests/test_misc.py,sha256=4qu61Sdmvzbaz2heOXKlg_PveYrzamBIUJ5QlLIYmSQ,37821
+psutil/tests/test_osx.py,sha256=UL3sovPp-5f0LIjNqjHu12NnpXF2uuv8Z62wbeAJj2c,9652
+psutil/tests/test_posix.py,sha256=4gm2VeCYoPYnstn_2GMG18c6xvMV7APXA26W5Dcm99s,16397
+psutil/tests/test_process.py,sha256=IE-v3DHKIje6Kwnas8kzfsbz15L4-0dpI555lXg7-NE,60495
+psutil/tests/test_sunos.py,sha256=UNywEx_qlSOyUHV48lMKqmnu8RKPBDCQrO0llulCUHY,1322
+psutil/tests/test_system.py,sha256=fv3otFKJVOn3VpRrD5IMivbI-hfajL6wmzVQF_JJqDI,34885
+psutil/tests/test_unicode.py,sha256=M0VTr_kGgmDKiNq6nSvraaw-711pRXe_6R948qHAr8I,12913
+psutil/tests/test_windows.py,sha256=AG2Feks59VOahEW_hJs_o4f5wt20vS1jxxaUCijcwAQ,32609
+psutil-5.4.3.dist-info/DESCRIPTION.rst,sha256=oATWrkVX56oC_b77TIXQsrm1CxSgD93PM71km6ud5G4,19853
+psutil-5.4.3.dist-info/METADATA,sha256=neLhT7NMfWptGzaSxueDDYqVDOR0C9cNvE8pAK14C2c,22835
+psutil-5.4.3.dist-info/RECORD,,
+psutil-5.4.3.dist-info/WHEEL,sha256=oT-jJdPOIfRdvpqTJO9X6OjCsyUImH2THoT54KREdwI,106
+psutil-5.4.3.dist-info/metadata.json,sha256=RqO1wxtF8F5B2HqYztzUkdKh5XmF8Wq_PxB9l571GM0,2492
+psutil-5.4.3.dist-info/top_level.txt,sha256=gCNhn57wzksDjSAISmgMJ0aiXzQulk0GJhb2-BAyYgw,7
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.30.0)
+Root-Is-Purelib: false
+Tag: cp27-cp27m-win_amd64
+
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/metadata.json
@@ -0,0 +1,1 @@
+{"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: Win32 (MS Windows)", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: BSD License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows :: Windows NT/2000", "Operating System :: Microsoft", "Operating System :: OS Independent", "Operating System :: POSIX :: BSD :: FreeBSD", "Operating System :: POSIX :: BSD :: NetBSD", "Operating System :: POSIX :: BSD :: OpenBSD", "Operating System :: POSIX :: BSD", "Operating System :: POSIX :: Linux", "Operating System :: POSIX :: SunOS/Solaris", "Operating System :: POSIX", "Programming Language :: C", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries", "Topic :: System :: Benchmark", "Topic :: System :: Hardware", "Topic :: System :: Monitoring", "Topic :: System :: Networking :: Monitoring", "Topic :: System :: Networking", "Topic :: System :: Operating System", "Topic :: System :: Systems Administration", "Topic :: Utilities"], "extensions": {"python.details": {"contacts": [{"email": "g.rodola@gmail.com", "name": "Giampaolo Rodola", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/giampaolo/psutil"}}}, "extras": ["enum"], "generator": "bdist_wheel (0.30.0)", "keywords": ["ps", "top", "kill", "free", "lsof", "netstat", "nice", "tty", "ionice", "uptime", "taskmgr", "process", "df", "iotop", "iostat", "ifconfig", "taskset", "who", "pidof", "pmap", "smem", "pstree", "monitoring", "ulimit", "prlimit", "smem"], "license": "BSD", "metadata_version": "2.0", "name": "psutil", "platform": "Platform Independent", "run_requires": [{"extra": "enum", "requires": ["enum34"]}], "summary": "Cross-platform lib for process and system monitoring in Python.", "test_requires": [{"requires": ["ipaddress", "mock"]}], "version": "5.4.3"}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/top_level.txt
@@ -0,0 +1,1 @@
+psutil
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/__init__.py
@@ -0,0 +1,2349 @@
+# -*- coding: utf-8 -*-
+
+# 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.
+
+"""psutil is a cross-platform library for retrieving information on
+running processes and system utilization (CPU, memory, disks, network,
+sensors) in Python. Supported platforms:
+
+ - Linux
+ - Windows
+ - OSX
+ - FreeBSD
+ - OpenBSD
+ - NetBSD
+ - Sun Solaris
+ - AIX
+
+Works with Python versions from 2.6 to 3.X.
+"""
+
+from __future__ import division
+
+import collections
+import contextlib
+import datetime
+import errno
+import functools
+import os
+import signal
+import subprocess
+import sys
+import time
+import traceback
+try:
+    import pwd
+except ImportError:
+    pwd = None
+
+from . import _common
+from ._common import deprecated_method
+from ._common import memoize
+from ._common import memoize_when_activated
+from ._common import wrap_numbers as _wrap_numbers
+from ._compat import callable
+from ._compat import long
+from ._compat import PY3 as _PY3
+
+from ._common import STATUS_DEAD
+from ._common import STATUS_DISK_SLEEP
+from ._common import STATUS_IDLE  # bsd
+from ._common import STATUS_LOCKED
+from ._common import STATUS_RUNNING
+from ._common import STATUS_SLEEPING
+from ._common import STATUS_STOPPED
+from ._common import STATUS_TRACING_STOP
+from ._common import STATUS_WAITING  # bsd
+from ._common import STATUS_WAKING
+from ._common import STATUS_ZOMBIE
+
+from ._common import CONN_CLOSE
+from ._common import CONN_CLOSE_WAIT
+from ._common import CONN_CLOSING
+from ._common import CONN_ESTABLISHED
+from ._common import CONN_FIN_WAIT1
+from ._common import CONN_FIN_WAIT2
+from ._common import CONN_LAST_ACK
+from ._common import CONN_LISTEN
+from ._common import CONN_NONE
+from ._common import CONN_SYN_RECV
+from ._common import CONN_SYN_SENT
+from ._common import CONN_TIME_WAIT
+from ._common import NIC_DUPLEX_FULL
+from ._common import NIC_DUPLEX_HALF
+from ._common import NIC_DUPLEX_UNKNOWN
+
+from ._common import AIX
+from ._common import BSD
+from ._common import FREEBSD  # NOQA
+from ._common import LINUX
+from ._common import NETBSD  # NOQA
+from ._common import OPENBSD  # NOQA
+from ._common import OSX
+from ._common import POSIX  # NOQA
+from ._common import SUNOS
+from ._common import WINDOWS
+
+from ._exceptions import AccessDenied
+from ._exceptions import Error
+from ._exceptions import NoSuchProcess
+from ._exceptions import TimeoutExpired
+from ._exceptions import ZombieProcess
+
+if LINUX:
+    # This is public API and it will be retrieved from _pslinux.py
+    # via sys.modules.
+    PROCFS_PATH = "/proc"
+
+    from . import _pslinux as _psplatform
+
+    from ._pslinux import IOPRIO_CLASS_BE  # NOQA
+    from ._pslinux import IOPRIO_CLASS_IDLE  # NOQA
+    from ._pslinux import IOPRIO_CLASS_NONE  # NOQA
+    from ._pslinux import IOPRIO_CLASS_RT  # NOQA
+    # Linux >= 2.6.36
+    if _psplatform.HAS_PRLIMIT:
+        from ._psutil_linux import RLIM_INFINITY  # NOQA
+        from ._psutil_linux import RLIMIT_AS  # NOQA
+        from ._psutil_linux import RLIMIT_CORE  # NOQA
+        from ._psutil_linux import RLIMIT_CPU  # NOQA
+        from ._psutil_linux import RLIMIT_DATA  # NOQA
+        from ._psutil_linux import RLIMIT_FSIZE  # NOQA
+        from ._psutil_linux import RLIMIT_LOCKS  # NOQA
+        from ._psutil_linux import RLIMIT_MEMLOCK  # NOQA
+        from ._psutil_linux import RLIMIT_NOFILE  # NOQA
+        from ._psutil_linux import RLIMIT_NPROC  # NOQA
+        from ._psutil_linux import RLIMIT_RSS  # NOQA
+        from ._psutil_linux import RLIMIT_STACK  # NOQA
+        # Kinda ugly but considerably faster than using hasattr() and
+        # setattr() against the module object (we are at import time:
+        # speed matters).
+        from . import _psutil_linux
+        try:
+            RLIMIT_MSGQUEUE = _psutil_linux.RLIMIT_MSGQUEUE
+        except AttributeError:
+            pass
+        try:
+            RLIMIT_NICE = _psutil_linux.RLIMIT_NICE
+        except AttributeError:
+            pass
+        try:
+            RLIMIT_RTPRIO = _psutil_linux.RLIMIT_RTPRIO
+        except AttributeError:
+            pass
+        try:
+            RLIMIT_RTTIME = _psutil_linux.RLIMIT_RTTIME
+        except AttributeError:
+            pass
+        try:
+            RLIMIT_SIGPENDING = _psutil_linux.RLIMIT_SIGPENDING
+        except AttributeError:
+            pass
+
+elif WINDOWS:
+    from . import _pswindows as _psplatform
+    from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS  # NOQA
+    from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS  # NOQA
+    from ._psutil_windows import HIGH_PRIORITY_CLASS  # NOQA
+    from ._psutil_windows import IDLE_PRIORITY_CLASS  # NOQA
+    from ._psutil_windows import NORMAL_PRIORITY_CLASS  # NOQA
+    from ._psutil_windows import REALTIME_PRIORITY_CLASS  # NOQA
+    from ._pswindows import CONN_DELETE_TCB  # NOQA
+
+elif OSX:
+    from . import _psosx as _psplatform
+
+elif BSD:
+    from . import _psbsd as _psplatform
+
+elif SUNOS:
+    from . import _pssunos as _psplatform
+    from ._pssunos import CONN_BOUND  # NOQA
+    from ._pssunos import CONN_IDLE  # NOQA
+
+    # This is public writable API which is read from _pslinux.py and
+    # _pssunos.py via sys.modules.
+    PROCFS_PATH = "/proc"
+
+elif AIX:
+    from . import _psaix as _psplatform
+
+    # This is public API and it will be retrieved from _pslinux.py
+    # via sys.modules.
+    PROCFS_PATH = "/proc"
+
+else:  # pragma: no cover
+    raise NotImplementedError('platform %s is not supported' % sys.platform)
+
+
+__all__ = [
+    # exceptions
+    "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied",
+    "TimeoutExpired",
+
+    # constants
+    "version_info", "__version__",
+
+    "STATUS_RUNNING", "STATUS_IDLE", "STATUS_SLEEPING", "STATUS_DISK_SLEEP",
+    "STATUS_STOPPED", "STATUS_TRACING_STOP", "STATUS_ZOMBIE", "STATUS_DEAD",
+    "STATUS_WAKING", "STATUS_LOCKED", "STATUS_WAITING", "STATUS_LOCKED",
+
+    "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
+    "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
+    "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE",
+
+    "AF_LINK",
+
+    "NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN",
+
+    "POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED",
+
+    "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "OSX", "POSIX", "SUNOS",
+    "WINDOWS", "AIX",
+
+    # classes
+    "Process", "Popen",
+
+    # functions
+    "pid_exists", "pids", "process_iter", "wait_procs",             # proc
+    "virtual_memory", "swap_memory",                                # memory
+    "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count",   # cpu
+    "cpu_stats",  # "cpu_freq",
+    "net_io_counters", "net_connections", "net_if_addrs",           # network
+    "net_if_stats",
+    "disk_io_counters", "disk_partitions", "disk_usage",            # disk
+    # "sensors_temperatures", "sensors_battery", "sensors_fans"     # sensors
+    "users", "boot_time",                                           # others
+]
+__all__.extend(_psplatform.__extra__all__)
+__author__ = "Giampaolo Rodola'"
+__version__ = "5.4.3"
+version_info = tuple([int(num) for num in __version__.split('.')])
+AF_LINK = _psplatform.AF_LINK
+POWER_TIME_UNLIMITED = _common.POWER_TIME_UNLIMITED
+POWER_TIME_UNKNOWN = _common.POWER_TIME_UNKNOWN
+_TOTAL_PHYMEM = None
+_timer = getattr(time, 'monotonic', time.time)
+
+
+# Sanity check in case the user messed up with psutil installation
+# or did something weird with sys.path. In this case we might end
+# up importing a python module using a C extension module which
+# was compiled for a different version of psutil.
+# We want to prevent that by failing sooner rather than later.
+# See: https://github.com/giampaolo/psutil/issues/564
+if (int(__version__.replace('.', '')) !=
+        getattr(_psplatform.cext, 'version', None)):
+    msg = "version conflict: %r C extension module was built for another " \
+          "version of psutil" % getattr(_psplatform.cext, "__file__")
+    if hasattr(_psplatform.cext, 'version'):
+        msg += " (%s instead of %s)" % (
+            '.'.join([x for x in str(_psplatform.cext.version)]), __version__)
+    else:
+        msg += " (different than %s)" % __version__
+    msg += "; you may try to 'pip uninstall psutil', manually remove %s" % (
+        getattr(_psplatform.cext, "__file__",
+                "the existing psutil install directory"))
+    msg += " or clean the virtual env somehow, then reinstall"
+    raise ImportError(msg)
+
+
+# =====================================================================
+# --- Utils
+# =====================================================================
+
+
+if hasattr(_psplatform, 'ppid_map'):
+    # Faster version (Windows and Linux).
+    _ppid_map = _psplatform.ppid_map
+else:
+    def _ppid_map():
+        """Return a {pid: ppid, ...} dict for all running processes in
+        one shot. Used to speed up Process.children().
+        """
+        ret = {}
+        for pid in pids():
+            try:
+                proc = _psplatform.Process(pid)
+                ppid = proc.ppid()
+            except (NoSuchProcess, AccessDenied):
+                # Note: AccessDenied is unlikely to happen.
+                pass
+            else:
+                ret[pid] = ppid
+        return ret
+
+
+def _assert_pid_not_reused(fun):
+    """Decorator which raises NoSuchProcess in case a process is no
+    longer running or its PID has been reused.
+    """
+    @functools.wraps(fun)
+    def wrapper(self, *args, **kwargs):
+        if not self.is_running():
+            raise NoSuchProcess(self.pid, self._name)
+        return fun(self, *args, **kwargs)
+    return wrapper
+
+
+def _pprint_secs(secs):
+    """Format seconds in a human readable form."""
+    now = time.time()
+    secs_ago = int(now - secs)
+    if secs_ago < 60 * 60 * 24:
+        fmt = "%H:%M:%S"
+    else:
+        fmt = "%Y-%m-%d %H:%M:%S"
+    return datetime.datetime.fromtimestamp(secs).strftime(fmt)
+
+
+# =====================================================================
+# --- Process class
+# =====================================================================
+
+
+class Process(object):
+    """Represents an OS process with the given PID.
+    If PID is omitted current process PID (os.getpid()) is used.
+    Raise NoSuchProcess if PID does not exist.
+
+    Note that most of the methods of this class do not make sure
+    the PID of the process being queried has been reused over time.
+    That means you might end up retrieving an information referring
+    to another process in case the original one this instance
+    refers to is gone in the meantime.
+
+    The only exceptions for which process identity is pre-emptively
+    checked and guaranteed are:
+
+     - parent()
+     - children()
+     - nice() (set)
+     - ionice() (set)
+     - rlimit() (set)
+     - cpu_affinity (set)
+     - suspend()
+     - resume()
+     - send_signal()
+     - terminate()
+     - kill()
+
+    To prevent this problem for all other methods you can:
+     - use is_running() before querying the process
+     - if you're continuously iterating over a set of Process
+       instances use process_iter() which pre-emptively checks
+     process identity for every yielded instance
+    """
+
+    def __init__(self, pid=None):
+        self._init(pid)
+
+    def _init(self, pid, _ignore_nsp=False):
+        if pid is None:
+            pid = os.getpid()
+        else:
+            if not _PY3 and not isinstance(pid, (int, long)):
+                raise TypeError('pid must be an integer (got %r)' % pid)
+            if pid < 0:
+                raise ValueError('pid must be a positive integer (got %s)'
+                                 % pid)
+        self._pid = pid
+        self._name = None
+        self._exe = None
+        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
+        # implementation class
+        self._proc = _psplatform.Process(pid)
+        self._last_sys_cpu_times = None
+        self._last_proc_cpu_times = None
+        # cache creation time for later use in is_running() method
+        try:
+            self.create_time()
+        except AccessDenied:
+            # We should never get here as AFAIK we're able to get
+            # process creation time on all platforms even as a
+            # limited user.
+            pass
+        except ZombieProcess:
+            # Zombies can still be queried by this class (although
+            # not always) and pids() return them so just go on.
+            pass
+        except NoSuchProcess:
+            if not _ignore_nsp:
+                msg = 'no process found with pid %s' % pid
+                raise NoSuchProcess(pid, None, msg)
+            else:
+                self._gone = True
+        # This pair is supposed to indentify a Process instance
+        # univocally over time (the PID alone is not enough as
+        # it might refer to a process whose PID has been reused).
+        # This will be used later in __eq__() and is_running().
+        self._ident = (self.pid, self._create_time)
+
+    def __str__(self):
+        try:
+            info = collections.OrderedDict()
+        except AttributeError:
+            info = {}  # Python 2.6
+        info["pid"] = self.pid
+        try:
+            info["name"] = self.name()
+            if self._create_time:
+                info['started'] = _pprint_secs(self._create_time)
+        except ZombieProcess:
+            info["status"] = "zombie"
+        except NoSuchProcess:
+            info["status"] = "terminated"
+        except AccessDenied:
+            pass
+        return "%s.%s(%s)" % (
+            self.__class__.__module__,
+            self.__class__.__name__,
+            ", ".join(["%s=%r" % (k, v) for k, v in info.items()]))
+
+    __repr__ = __str__
+
+    def __eq__(self, other):
+        # Test for equality with another Process object based
+        # on PID and creation time.
+        if not isinstance(other, Process):
+            return NotImplemented
+        return self._ident == other._ident
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __hash__(self):
+        if self._hash is None:
+            self._hash = hash(self._ident)
+        return self._hash
+
+    @property
+    def pid(self):
+        """The process PID."""
+        return self._pid
+
+    # --- 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.
+        """
+        valid_names = _as_dict_attrnames
+        if attrs is not None:
+            if not isinstance(attrs, (list, tuple, set, frozenset)):
+                raise TypeError("invalid attrs type %s" % type(attrs))
+            attrs = set(attrs)
+            invalid_names = attrs - valid_names
+            if invalid_names:
+                raise ValueError("invalid attr name%s %s" % (
+                    "s" if len(invalid_names) > 1 else "",
+                    ", ".join(map(repr, invalid_names))))
+
+        retdict = dict()
+        ls = attrs or valid_names
+        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):
+        """Return the parent process as a Process object pre-emptively
+        checking whether PID has been reused.
+        If no parent is known return None.
+        """
+        ppid = self.ppid()
+        if ppid is not None:
+            ctime = self.create_time()
+            try:
+                parent = Process(ppid)
+                if parent.create_time() <= ctime:
+                    return parent
+                # ...else ppid has been reused by another process
+            except NoSuchProcess:
+                pass
+
+    def is_running(self):
+        """Return whether this process is running.
+        It also checks if PID has been reused by another process in
+        which case return False.
+        """
+        if self._gone:
+            return False
+        try:
+            # Checking if PID is alive is not enough as the PID might
+            # have been reused by another process: we also want to
+            # verify process identity.
+            # Process identity / uniqueness over time is guaranteed by
+            # (PID + creation time) and that is verified in __eq__.
+            return self == Process(self.pid)
+        except ZombieProcess:
+            # We should never get here as it's already handled in
+            # Process.__init__; here just for extra safety.
+            return True
+        except NoSuchProcess:
+            self._gone = True
+            return False
+
+    # --- actual API
+
+    @memoize_when_activated
+    def ppid(self):
+        """The process parent PID.
+        On Windows the return value is cached after first call.
+        """
+        # On POSIX we don't want to cache the ppid as it may unexpectedly
+        # change to 1 (init) in case this process turns into a zombie:
+        # https://github.com/giampaolo/psutil/issues/321
+        # http://stackoverflow.com/questions/356722/
+
+        # XXX should we check creation time here rather than in
+        # Process.parent()?
+        if POSIX:
+            return self._proc.ppid()
+        else:  # pragma: no cover
+            self._ppid = self._ppid or self._proc.ppid()
+            return self._ppid
+
+    def name(self):
+        """The process name. The return value is cached after first call."""
+        # Process name is only cached on Windows as on POSIX it may
+        # change, see:
+        # https://github.com/giampaolo/psutil/issues/692
+        if WINDOWS and self._name is not None:
+            return self._name
+        name = self._proc.name()
+        if POSIX and len(name) >= 15:
+            # On UNIX the name gets truncated to the first 15 characters.
+            # If it matches the first part of the cmdline we return that
+            # one instead because it's usually more explicative.
+            # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon".
+            try:
+                cmdline = self.cmdline()
+            except AccessDenied:
+                pass
+            else:
+                if cmdline:
+                    extended_name = os.path.basename(cmdline[0])
+                    if extended_name.startswith(name):
+                        name = extended_name
+        self._name = name
+        self._proc._name = name
+        return name
+
+    def exe(self):
+        """The process executable as an absolute path.
+        May also be an empty string.
+        The return value is cached after first call.
+        """
+        def guess_it(fallback):
+            # try to guess exe from cmdline[0] in absence of a native
+            # exe representation
+            cmdline = self.cmdline()
+            if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'):
+                exe = cmdline[0]  # the possible exe
+                # Attempt to guess only in case of an absolute path.
+                # It is not safe otherwise as the process might have
+                # changed cwd.
+                if (os.path.isabs(exe) and
+                        os.path.isfile(exe) and
+                        os.access(exe, os.X_OK)):
+                    return exe
+            if isinstance(fallback, AccessDenied):
+                raise fallback
+            return fallback
+
+        if self._exe is None:
+            try:
+                exe = self._proc.exe()
+            except AccessDenied as err:
+                return guess_it(fallback=err)
+            else:
+                if not exe:
+                    # underlying implementation can legitimately return an
+                    # empty string; if that's the case we don't want to
+                    # raise AD while guessing from the cmdline
+                    try:
+                        exe = guess_it(fallback=exe)
+                    except AccessDenied:
+                        pass
+                self._exe = exe
+        return self._exe
+
+    def cmdline(self):
+        """The command line this process has been called with."""
+        return self._proc.cmdline()
+
+    def status(self):
+        """The process current status as a STATUS_* constant."""
+        try:
+            return self._proc.status()
+        except ZombieProcess:
+            return STATUS_ZOMBIE
+
+    def username(self):
+        """The name of the user that owns the process.
+        On UNIX this is calculated by using *real* process uid.
+        """
+        if POSIX:
+            if pwd is None:
+                # might happen if python was installed from sources
+                raise ImportError(
+                    "requires pwd module shipped with standard python")
+            real_uid = self.uids().real
+            try:
+                return pwd.getpwuid(real_uid).pw_name
+            except KeyError:
+                # the uid can't be resolved by the system
+                return str(real_uid)
+        else:
+            return self._proc.username()
+
+    def create_time(self):
+        """The process creation time as a floating point number
+        expressed in seconds since the epoch, in UTC.
+        The return value is cached after first call.
+        """
+        if self._create_time is None:
+            self._create_time = self._proc.create_time()
+        return self._create_time
+
+    def cwd(self):
+        """Process current working directory as an absolute path."""
+        return self._proc.cwd()
+
+    def nice(self, value=None):
+        """Get or set process niceness (priority)."""
+        if value is None:
+            return self._proc.nice_get()
+        else:
+            if not self.is_running():
+                raise NoSuchProcess(self.pid, self._name)
+            self._proc.nice_set(value)
+
+    if POSIX:
+
+        @memoize_when_activated
+        def uids(self):
+            """Return process UIDs as a (real, effective, saved)
+            namedtuple.
+            """
+            return self._proc.uids()
+
+        def gids(self):
+            """Return process GIDs as a (real, effective, saved)
+            namedtuple.
+            """
+            return self._proc.gids()
+
+        def terminal(self):
+            """The terminal associated with this process, if any,
+            else None.
+            """
+            return self._proc.terminal()
+
+        def num_fds(self):
+            """Return the number of file descriptors opened by this
+            process (POSIX only).
+            """
+            return self._proc.num_fds()
+
+    # Linux, BSD, AIX and Windows only
+    if hasattr(_psplatform.Process, "io_counters"):
+
+        def io_counters(self):
+            """Return process I/O statistics as a
+            (read_count, write_count, read_bytes, write_bytes)
+            namedtuple.
+            Those are the number of read/write calls performed and the
+            amount of bytes read and written by the process.
+            """
+            return self._proc.io_counters()
+
+    # Linux and Windows >= Vista only
+    if hasattr(_psplatform.Process, "ionice_get"):
+
+        def ionice(self, ioclass=None, value=None):
+            """Get or set process I/O niceness (priority).
+
+            On Linux *ioclass* is one of the IOPRIO_CLASS_* constants.
+            *value* is a number which goes from 0 to 7. The higher the
+            value, the lower the I/O priority of the process.
+
+            On Windows only *ioclass* is used and it can be set to 2
+            (normal), 1 (low) or 0 (very low).
+
+            Available on Linux and Windows > Vista only.
+            """
+            if ioclass is None:
+                if value is not None:
+                    raise ValueError("'ioclass' argument must be specified")
+                return self._proc.ionice_get()
+            else:
+                return self._proc.ionice_set(ioclass, value)
+
+    # Linux only
+    if hasattr(_psplatform.Process, "rlimit"):
+
+        def rlimit(self, resource, limits=None):
+            """Get or set process resource limits as a (soft, hard)
+            tuple.
+
+            *resource* is one of the RLIMIT_* constants.
+            *limits* is supposed to be a (soft, hard)  tuple.
+
+            See "man prlimit" for further info.
+            Available on Linux only.
+            """
+            if limits is None:
+                return self._proc.rlimit(resource)
+            else:
+                return self._proc.rlimit(resource, limits)
+
+    # Windows, Linux and FreeBSD only
+    if hasattr(_psplatform.Process, "cpu_affinity_get"):
+
+        def cpu_affinity(self, cpus=None):
+            """Get or set process CPU affinity.
+            If specified, *cpus* must be a list of CPUs for which you
+            want to set the affinity (e.g. [0, 1]).
+            If an empty list is passed, all egible CPUs are assumed
+            (and set).
+            (Windows, Linux and BSD only).
+            """
+            # Automatically remove duplicates both on get and
+            # set (for get it's not really necessary, it's
+            # just for extra safety).
+            if cpus is None:
+                return list(set(self._proc.cpu_affinity_get()))
+            else:
+                if not cpus:
+                    if hasattr(self._proc, "_get_eligible_cpus"):
+                        cpus = self._proc._get_eligible_cpus()
+                    else:
+                        cpus = tuple(range(len(cpu_times(percpu=True))))
+                self._proc.cpu_affinity_set(list(set(cpus)))
+
+    # Linux, FreeBSD, SunOS
+    if hasattr(_psplatform.Process, "cpu_num"):
+
+        def cpu_num(self):
+            """Return what CPU this process is currently running on.
+            The returned number should be <= psutil.cpu_count()
+            and <= len(psutil.cpu_percent(percpu=True)).
+            It may be used in conjunction with
+            psutil.cpu_percent(percpu=True) to observe the system
+            workload distributed across CPUs.
+            """
+            return self._proc.cpu_num()
+
+    # Linux, OSX and Windows only
+    if hasattr(_psplatform.Process, "environ"):
+
+        def environ(self):
+            """The environment variables of the process as a dict.  Note: this
+            might not reflect changes made after the process started.  """
+            return self._proc.environ()
+
+    if WINDOWS:
+
+        def num_handles(self):
+            """Return the number of handles opened by this process
+            (Windows only).
+            """
+            return self._proc.num_handles()
+
+    def num_ctx_switches(self):
+        """Return the number of voluntary and involuntary context
+        switches performed by this process.
+        """
+        return self._proc.num_ctx_switches()
+
+    def num_threads(self):
+        """Return the number of threads used by this process."""
+        return self._proc.num_threads()
+
+    if hasattr(_psplatform.Process, "threads"):
+
+        def threads(self):
+            """Return threads opened by process as a list of
+            (id, user_time, system_time) namedtuples representing
+            thread id and thread CPU times (user/system).
+            On OpenBSD this method requires root access.
+            """
+            return self._proc.threads()
+
+    @_assert_pid_not_reused
+    def children(self, recursive=False):
+        """Return the children of this process as a list of Process
+        instances, pre-emptively checking whether PID has been reused.
+        If *recursive* is True return all the parent descendants.
+
+        Example (A == this process):
+
+         A ─┐
+            │
+            ├─ B (child) ─┐
+            │             └─ X (grandchild) ─┐
+            │                                └─ Y (great grandchild)
+            ├─ C (child)
+            └─ D (child)
+
+        >>> import psutil
+        >>> p = psutil.Process()
+        >>> p.children()
+        B, C, D
+        >>> p.children(recursive=True)
+        B, X, Y, C, D
+
+        Note that in the example above if process X disappears
+        process Y won't be listed as the reference to process A
+        is lost.
+        """
+        ppid_map = _ppid_map()
+        ret = []
+        if not recursive:
+            for pid, ppid in ppid_map.items():
+                if ppid == self.pid:
+                    try:
+                        child = Process(pid)
+                        # if child happens to be older than its parent
+                        # (self) it means child's PID has been reused
+                        if self.create_time() <= child.create_time():
+                            ret.append(child)
+                    except (NoSuchProcess, ZombieProcess):
+                        pass
+        else:
+            # Construct a {pid: [child pids]} dict
+            reverse_ppid_map = collections.defaultdict(list)
+            for pid, ppid in ppid_map.items():
+                reverse_ppid_map[ppid].append(pid)
+            # Recursively traverse that dict, starting from self.pid,
+            # such that we only call Process() on actual children
+            seen = set()
+            stack = [self.pid]
+            while stack:
+                pid = stack.pop()
+                if pid in seen:
+                    # Since pids can be reused while the ppid_map is
+                    # constructed, there may be rare instances where
+                    # there's a cycle in the recorded process "tree".
+                    continue
+                seen.add(pid)
+                for child_pid in reverse_ppid_map[pid]:
+                    try:
+                        child = Process(child_pid)
+                        # if child happens to be older than its parent
+                        # (self) it means child's PID has been reused
+                        intime = self.create_time() <= child.create_time()
+                        if intime:
+                            ret.append(child)
+                            stack.append(child_pid)
+                    except (NoSuchProcess, ZombieProcess):
+                        pass
+        return ret
+
+    def cpu_percent(self, interval=None):
+        """Return a float representing the current process CPU
+        utilization as a percentage.
+
+        When *interval* is 0.0 or None (default) compares process times
+        to system CPU times elapsed since last call, returning
+        immediately (non-blocking). That means that the first time
+        this is called it will return a meaningful 0.0 value.
+
+        When *interval* is > 0.0 compares process times to system CPU
+        times elapsed before and after the interval (blocking).
+
+        In this case is recommended for accuracy that this function
+        be called with at least 0.1 seconds between calls.
+
+        A value > 100.0 can be returned in case of processes running
+        multiple threads on different CPU cores.
+
+        The returned value is explicitly NOT split evenly between
+        all available logical CPUs. This means that a busy loop process
+        running on a system with 2 logical CPUs will be reported as
+        having 100% CPU utilization instead of 50%.
+
+        Examples:
+
+          >>> import psutil
+          >>> p = psutil.Process(os.getpid())
+          >>> # blocking
+          >>> p.cpu_percent(interval=1)
+          2.0
+          >>> # non-blocking (percentage since last call)
+          >>> p.cpu_percent(interval=None)
+          2.9
+          >>>
+        """
+        blocking = interval is not None and interval > 0.0
+        if interval is not None and interval < 0:
+            raise ValueError("interval is not positive (got %r)" % interval)
+        num_cpus = cpu_count() or 1
+
+        def timer():
+            return _timer() * num_cpus
+
+        if blocking:
+            st1 = timer()
+            pt1 = self._proc.cpu_times()
+            time.sleep(interval)
+            st2 = timer()
+            pt2 = self._proc.cpu_times()
+        else:
+            st1 = self._last_sys_cpu_times
+            pt1 = self._last_proc_cpu_times
+            st2 = timer()
+            pt2 = self._proc.cpu_times()
+            if st1 is None or pt1 is None:
+                self._last_sys_cpu_times = st2
+                self._last_proc_cpu_times = pt2
+                return 0.0
+
+        delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system)
+        delta_time = st2 - st1
+        # reset values for next call in case of interval == None
+        self._last_sys_cpu_times = st2
+        self._last_proc_cpu_times = pt2
+
+        try:
+            # This is the utilization split evenly between all CPUs.
+            # E.g. a busy loop process on a 2-CPU-cores system at this
+            # point is reported as 50% instead of 100%.
+            overall_cpus_percent = ((delta_proc / delta_time) * 100)
+        except ZeroDivisionError:
+            # interval was too low
+            return 0.0
+        else:
+            # Note 1:
+            # in order to emulate "top" we multiply the value for the num
+            # of CPU cores. This way the busy process will be reported as
+            # having 100% (or more) usage.
+            #
+            # Note 2:
+            # taskmgr.exe on Windows differs in that it will show 50%
+            # instead.
+            #
+            # Note 3:
+            # a percentage > 100 is legitimate as it can result from a
+            # process with multiple threads running on different CPU
+            # cores (top does the same), see:
+            # http://stackoverflow.com/questions/1032357
+            # https://github.com/giampaolo/psutil/issues/474
+            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
+        seconds.
+        This is similar to os.times() but per-process.
+        On OSX and Windows children_user and children_system are
+        always set to 0.
+        """
+        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.
+
+        The "portable" fields available on all plaforms are `rss` and `vms`.
+
+        All numbers are expressed in bytes.
+        """
+        return self._proc.memory_info()
+
+    @deprecated_method(replacement="memory_info")
+    def memory_info_ex(self):
+        return self.memory_info()
+
+    def memory_full_info(self):
+        """This method returns the same information as memory_info(),
+        plus, on some platform (Linux, OSX, Windows), also provides
+        additional metrics (USS, PSS and swap).
+        The additional metrics provide a better representation of actual
+        process memory usage.
+
+        Namely USS is the memory which is unique to a process and which
+        would be freed if the process was terminated right now.
+
+        It does so by passing through the whole process address.
+        As such it usually requires higher user privileges than
+        memory_info() and is considerably slower.
+        """
+        return self._proc.memory_full_info()
+
+    def memory_percent(self, memtype="rss"):
+        """Compare process memory to total physical system memory and
+        calculate process memory utilization as a percentage.
+        *memtype* argument is a string that dictates what type of
+        process memory you want to compare against (defaults to "rss").
+        The list of available strings can be obtained like this:
+
+        >>> psutil.Process().memory_info()._fields
+        ('rss', 'vms', 'shared', 'text', 'lib', 'data', 'dirty', 'uss', 'pss')
+        """
+        valid_types = list(_psplatform.pfullmem._fields)
+        if memtype not in valid_types:
+            raise ValueError("invalid memtype %r; valid types are %r" % (
+                memtype, tuple(valid_types)))
+        fun = self.memory_info if memtype in _psplatform.pmem._fields else \
+            self.memory_full_info
+        metrics = fun()
+        value = getattr(metrics, memtype)
+
+        # use cached value if available
+        total_phymem = _TOTAL_PHYMEM or virtual_memory().total
+        if not total_phymem > 0:
+            # we should never get here
+            raise ValueError(
+                "can't calculate process memory percent because "
+                "total physical system memory is not positive (%r)"
+                % total_phymem)
+        return (value / float(total_phymem)) * 100
+
+    if hasattr(_psplatform.Process, "memory_maps"):
+        # Available everywhere except OpenBSD and NetBSD.
+        def memory_maps(self, grouped=True):
+            """Return process' mapped memory regions as a list of namedtuples
+            whose fields are variable depending on the platform.
+
+            If *grouped* is True the mapped regions with the same 'path'
+            are grouped together and the different memory fields are summed.
+
+            If *grouped* is False every mapped region is shown as a single
+            entity and the namedtuple will also include the mapped region's
+            address space ('addr') and permission set ('perms').
+            """
+            it = self._proc.memory_maps()
+            if grouped:
+                d = {}
+                for tupl in it:
+                    path = tupl[2]
+                    nums = tupl[3:]
+                    try:
+                        d[path] = map(lambda x, y: x + y, d[path], nums)
+                    except KeyError:
+                        d[path] = nums
+                nt = _psplatform.pmmap_grouped
+                return [nt(path, *d[path]) for path in d]  # NOQA
+            else:
+                nt = _psplatform.pmmap_ext
+                return [nt(*x) for x in it]
+
+    def open_files(self):
+        """Return files opened by process as a list of
+        (path, fd) namedtuples including the absolute file name
+        and file descriptor number.
+        """
+        return self._proc.open_files()
+
+    def connections(self, kind='inet'):
+        """Return socket connections opened by process as a list of
+        (fd, family, type, laddr, raddr, status) namedtuples.
+        The *kind* parameter filters for connections that match the
+        following criteria:
+
+        +------------+----------------------------------------------------+
+        | Kind Value | Connections using                                  |
+        +------------+----------------------------------------------------+
+        | inet       | IPv4 and IPv6                                      |
+        | inet4      | IPv4                                               |
+        | inet6      | IPv6                                               |
+        | tcp        | TCP                                                |
+        | tcp4       | TCP over IPv4                                      |
+        | tcp6       | TCP over IPv6                                      |
+        | udp        | UDP                                                |
+        | udp4       | UDP over IPv4                                      |
+        | udp6       | UDP over IPv6                                      |
+        | unix       | UNIX socket (both UDP and TCP protocols)           |
+        | all        | the sum of all the possible families and protocols |
+        +------------+----------------------------------------------------+
+        """
+        return self._proc.connections(kind)
+
+    # --- signals
+
+    if POSIX:
+        def _send_signal(self, sig):
+            assert not self.pid < 0, self.pid
+            if self.pid == 0:
+                # see "man 2 kill"
+                raise ValueError(
+                    "preventing sending signal to process with PID 0 as it "
+                    "would affect every process in the process group of the "
+                    "calling process (os.getpid()) instead of PID 0")
+            try:
+                os.kill(self.pid, sig)
+            except OSError as err:
+                if err.errno == errno.ESRCH:
+                    if OPENBSD and pid_exists(self.pid):
+                        # We do this because os.kill() lies in case of
+                        # zombie processes.
+                        raise ZombieProcess(self.pid, self._name, self._ppid)
+                    else:
+                        self._gone = True
+                        raise NoSuchProcess(self.pid, self._name)
+                if err.errno in (errno.EPERM, errno.EACCES):
+                    raise AccessDenied(self.pid, self._name)
+                raise
+
+    @_assert_pid_not_reused
+    def send_signal(self, sig):
+        """Send a signal *sig* to process pre-emptively checking
+        whether PID has been reused (see signal module constants) .
+        On Windows only SIGTERM is valid and is treated as an alias
+        for kill().
+        """
+        if POSIX:
+            self._send_signal(sig)
+        else:  # pragma: no cover
+            if sig == signal.SIGTERM:
+                self._proc.kill()
+            # py >= 2.7
+            elif sig in (getattr(signal, "CTRL_C_EVENT", object()),
+                         getattr(signal, "CTRL_BREAK_EVENT", object())):
+                self._proc.send_signal(sig)
+            else:
+                raise ValueError(
+                    "only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT signals "
+                    "are supported on Windows")
+
+    @_assert_pid_not_reused
+    def suspend(self):
+        """Suspend process execution with SIGSTOP pre-emptively checking
+        whether PID has been reused.
+        On Windows this has the effect ot suspending all process threads.
+        """
+        if POSIX:
+            self._send_signal(signal.SIGSTOP)
+        else:  # pragma: no cover
+            self._proc.suspend()
+
+    @_assert_pid_not_reused
+    def resume(self):
+        """Resume process execution with SIGCONT pre-emptively checking
+        whether PID has been reused.
+        On Windows this has the effect of resuming all process threads.
+        """
+        if POSIX:
+            self._send_signal(signal.SIGCONT)
+        else:  # pragma: no cover
+            self._proc.resume()
+
+    @_assert_pid_not_reused
+    def terminate(self):
+        """Terminate the process with SIGTERM pre-emptively checking
+        whether PID has been reused.
+        On Windows this is an alias for kill().
+        """
+        if POSIX:
+            self._send_signal(signal.SIGTERM)
+        else:  # pragma: no cover
+            self._proc.kill()
+
+    @_assert_pid_not_reused
+    def kill(self):
+        """Kill the current process with SIGKILL pre-emptively checking
+        whether PID has been reused.
+        """
+        if POSIX:
+            self._send_signal(signal.SIGKILL)
+        else:  # pragma: no cover
+            self._proc.kill()
+
+    def wait(self, timeout=None):
+        """Wait for process to terminate and, if process is a children
+        of os.getpid(), also return its exit code, else None.
+
+        If the process is already terminated immediately return None
+        instead of raising NoSuchProcess.
+
+        If *timeout* (in seconds) is specified and process is still
+        alive raise TimeoutExpired.
+
+        To wait for multiple Process(es) use psutil.wait_procs().
+        """
+        if timeout is not None and not timeout >= 0:
+            raise ValueError("timeout must be a positive integer")
+        return self._proc.wait(timeout)
+
+
+# =====================================================================
+# --- Popen class
+# =====================================================================
+
+
+class Popen(Process):
+    """A more convenient interface to stdlib subprocess.Popen class.
+    It starts a sub process and deals with it exactly as when using
+    subprocess.Popen class but in addition also provides all the
+    properties and methods of psutil.Process class as a unified
+    interface:
+
+      >>> import psutil
+      >>> from subprocess import PIPE
+      >>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE)
+      >>> p.name()
+      'python'
+      >>> p.uids()
+      user(real=1000, effective=1000, saved=1000)
+      >>> p.username()
+      'giampaolo'
+      >>> p.communicate()
+      ('hi\n', None)
+      >>> p.terminate()
+      >>> p.wait(timeout=2)
+      0
+      >>>
+
+    For method names common to both classes such as kill(), terminate()
+    and wait(), psutil.Process implementation takes precedence.
+
+    Unlike subprocess.Popen this class pre-emptively checks whether PID
+    has been reused on send_signal(), terminate() and kill() so that
+    you don't accidentally terminate another process, fixing
+    http://bugs.python.org/issue6973.
+
+    For a complete documentation refer to:
+    http://docs.python.org/library/subprocess.html
+    """
+
+    def __init__(self, *args, **kwargs):
+        # Explicitly avoid to raise NoSuchProcess in case the process
+        # spawned by subprocess.Popen terminates too quickly, see:
+        # https://github.com/giampaolo/psutil/issues/193
+        self.__subproc = subprocess.Popen(*args, **kwargs)
+        self._init(self.__subproc.pid, _ignore_nsp=True)
+
+    def __dir__(self):
+        return sorted(set(dir(Popen) + dir(subprocess.Popen)))
+
+    def __enter__(self):
+        if hasattr(self.__subproc, '__enter__'):
+            self.__subproc.__enter__()
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        if hasattr(self.__subproc, '__exit__'):
+            return self.__subproc.__exit__(*args, **kwargs)
+        else:
+            if self.stdout:
+                self.stdout.close()
+            if self.stderr:
+                self.stderr.close()
+            try:
+                # Flushing a BufferedWriter may raise an error.
+                if self.stdin:
+                    self.stdin.close()
+            finally:
+                # Wait for the process to terminate, to avoid zombies.
+                self.wait()
+
+    def __getattribute__(self, name):
+        try:
+            return object.__getattribute__(self, name)
+        except AttributeError:
+            try:
+                return object.__getattribute__(self.__subproc, name)
+            except AttributeError:
+                raise AttributeError("%s instance has no attribute '%s'"
+                                     % (self.__class__.__name__, name))
+
+    def wait(self, timeout=None):
+        if self.__subproc.returncode is not None:
+            return self.__subproc.returncode
+        ret = super(Popen, self).wait(timeout)
+        self.__subproc.returncode = ret
+        return ret
+
+
+# The valid attr names which can be processed by Process.as_dict().
+_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', 'oneshot']])
+
+
+# =====================================================================
+# --- system processes related functions
+# =====================================================================
+
+
+def pids():
+    """Return a list of current running PIDs."""
+    return _psplatform.pids()
+
+
+def pid_exists(pid):
+    """Return True if given PID exists in the current process list.
+    This is faster than doing "pid in psutil.pids()" and
+    should be preferred.
+    """
+    if pid < 0:
+        return False
+    elif pid == 0 and POSIX:
+        # On POSIX we use os.kill() to determine PID existence.
+        # According to "man 2 kill" PID 0 has a special meaning
+        # though: it refers to <<every process in the process
+        # group of the calling process>> and that is not we want
+        # to do here.
+        return pid in pids()
+    else:
+        return _psplatform.pid_exists(pid)
+
+
+_pmap = {}
+
+
+def process_iter(attrs=None, ad_value=None):
+    """Return a generator yielding a Process instance for all
+    running processes.
+
+    Every new Process instance is only created once and then cached
+    into an internal table which is updated every time this is used.
+
+    Cached Process instances are checked for identity so that you're
+    safe in case a PID has been reused by another process, in which
+    case the cached instance is updated.
+
+    The sorting order in which processes are yielded is based on
+    their PIDs.
+
+    *attrs* and *ad_value* have the same meaning as in
+    Process.as_dict(). If *attrs* is specified as_dict() is called
+    and the resulting dict is stored as a 'info' attribute attached
+    to returned Process instance.
+    If *attrs* is an empty list it will retrieve all process info
+    (slow).
+    """
+    def add(pid):
+        proc = Process(pid)
+        if attrs is not None:
+            proc.info = proc.as_dict(attrs=attrs, ad_value=ad_value)
+        _pmap[proc.pid] = proc
+        return proc
+
+    def remove(pid):
+        _pmap.pop(pid, None)
+
+    a = set(pids())
+    b = set(_pmap.keys())
+    new_pids = a - b
+    gone_pids = b - a
+
+    for pid in gone_pids:
+        remove(pid)
+    for pid, proc in sorted(list(_pmap.items()) +
+                            list(dict.fromkeys(new_pids).items())):
+        try:
+            if proc is None:  # new process
+                yield add(pid)
+            else:
+                # use is_running() to check whether PID has been reused by
+                # another process in which case yield a new Process instance
+                if proc.is_running():
+                    if attrs is not None:
+                        proc.info = proc.as_dict(
+                            attrs=attrs, ad_value=ad_value)
+                    yield proc
+                else:
+                    yield add(pid)
+        except NoSuchProcess:
+            remove(pid)
+        except AccessDenied:
+            # Process creation time can't be determined hence there's
+            # no way to tell whether the pid of the cached process
+            # has been reused. Just return the cached version.
+            if proc is None and pid in _pmap:
+                try:
+                    yield _pmap[pid]
+                except KeyError:
+                    # If we get here it is likely that 2 threads were
+                    # using process_iter().
+                    pass
+            else:
+                raise
+
+
+def wait_procs(procs, timeout=None, callback=None):
+    """Convenience function which waits for a list of processes to
+    terminate.
+
+    Return a (gone, alive) tuple indicating which processes
+    are gone and which ones are still alive.
+
+    The gone ones will have a new *returncode* attribute indicating
+    process exit status (may be None).
+
+    *callback* is a function which gets called every time a process
+    terminates (a Process instance is passed as callback argument).
+
+    Function will return as soon as all processes terminate or when
+    *timeout* occurs.
+    Differently from Process.wait() it will not raise TimeoutExpired if
+    *timeout* occurs.
+
+    Typical use case is:
+
+     - send SIGTERM to a list of processes
+     - give them some time to terminate
+     - send SIGKILL to those ones which are still alive
+
+    Example:
+
+    >>> def on_terminate(proc):
+    ...     print("process {} terminated".format(proc))
+    ...
+    >>> for p in procs:
+    ...    p.terminate()
+    ...
+    >>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate)
+    >>> for p in alive:
+    ...     p.kill()
+    """
+    def check_gone(proc, timeout):
+        try:
+            returncode = proc.wait(timeout=timeout)
+        except TimeoutExpired:
+            pass
+        else:
+            if returncode is not None or not proc.is_running():
+                proc.returncode = returncode
+                gone.add(proc)
+                if callback is not None:
+                    callback(proc)
+
+    if timeout is not None and not timeout >= 0:
+        msg = "timeout must be a positive integer, got %s" % timeout
+        raise ValueError(msg)
+    gone = set()
+    alive = set(procs)
+    if callback is not None and not callable(callback):
+        raise TypeError("callback %r is not a callable" % callable)
+    if timeout is not None:
+        deadline = _timer() + timeout
+
+    while alive:
+        if timeout is not None and timeout <= 0:
+            break
+        for proc in alive:
+            # Make sure that every complete iteration (all processes)
+            # will last max 1 sec.
+            # We do this because we don't want to wait too long on a
+            # single process: in case it terminates too late other
+            # processes may disappear in the meantime and their PID
+            # reused.
+            max_timeout = 1.0 / len(alive)
+            if timeout is not None:
+                timeout = min((deadline - _timer()), max_timeout)
+                if timeout <= 0:
+                    break
+                check_gone(proc, timeout)
+            else:
+                check_gone(proc, max_timeout)
+        alive = alive - gone
+
+    if alive:
+        # Last attempt over processes survived so far.
+        # timeout == 0 won't make this function wait any further.
+        for proc in alive:
+            check_gone(proc, 0)
+        alive = alive - gone
+
+    return (list(gone), list(alive))
+
+
+# =====================================================================
+# --- CPU related functions
+# =====================================================================
+
+
+def cpu_count(logical=True):
+    """Return the number of logical CPUs in the system (same as
+    os.cpu_count() in Python 3.4).
+
+    If *logical* is False return the number of physical cores only
+    (e.g. hyper thread CPUs are excluded).
+
+    Return None if undetermined.
+
+    The return value is cached after first call.
+    If desired cache can be cleared like this:
+
+    >>> psutil.cpu_count.cache_clear()
+    """
+    if logical:
+        ret = _psplatform.cpu_count_logical()
+    else:
+        ret = _psplatform.cpu_count_physical()
+    if ret is not None and ret < 1:
+        ret = None
+    return ret
+
+
+def cpu_times(percpu=False):
+    """Return system-wide CPU times as a namedtuple.
+    Every CPU time represents the seconds the CPU has spent in the
+    given mode. The namedtuple's fields availability varies depending on the
+    platform:
+
+     - user
+     - system
+     - idle
+     - nice (UNIX)
+     - iowait (Linux)
+     - irq (Linux, FreeBSD)
+     - softirq (Linux)
+     - steal (Linux >= 2.6.11)
+     - guest (Linux >= 2.6.24)
+     - guest_nice (Linux >= 3.2.0)
+
+    When *percpu* is True return a list of namedtuples for each CPU.
+    First element of the list refers to first CPU, second element
+    to second CPU and so on.
+    The order of the list is consistent across calls.
+    """
+    if not percpu:
+        return _psplatform.cpu_times()
+    else:
+        return _psplatform.per_cpu_times()
+
+
+try:
+    _last_cpu_times = cpu_times()
+except Exception:
+    # Don't want to crash at import time.
+    _last_cpu_times = None
+    traceback.print_exc()
+
+try:
+    _last_per_cpu_times = cpu_times(percpu=True)
+except Exception:
+    # Don't want to crash at import time.
+    _last_per_cpu_times = None
+    traceback.print_exc()
+
+
+def _cpu_tot_time(times):
+    """Given a cpu_time() ntuple calculates the total CPU time
+    (including idle time).
+    """
+    tot = sum(times)
+    if LINUX:
+        # On Linux guest times are already accounted in "user" or
+        # "nice" times, so we subtract them from total.
+        # Htop does the same. References:
+        # https://github.com/giampaolo/psutil/pull/940
+        # http://unix.stackexchange.com/questions/178045
+        # https://github.com/torvalds/linux/blob/
+        #     447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/
+        #     cputime.c#L158
+        tot -= getattr(times, "guest", 0)  # Linux 2.6.24+
+        tot -= getattr(times, "guest_nice", 0)  # Linux 3.2.0+
+    return tot
+
+
+def _cpu_busy_time(times):
+    """Given a cpu_time() ntuple calculates the busy CPU time.
+    We do so by subtracting all idle CPU times.
+    """
+    busy = _cpu_tot_time(times)
+    busy -= times.idle
+    # Linux: "iowait" is time during which the CPU does not do anything
+    # (waits for IO to complete). On Linux IO wait is *not* accounted
+    # in "idle" time so we subtract it. Htop does the same.
+    # References:
+    # https://github.com/torvalds/linux/blob/
+    #     447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/cputime.c#L244
+    busy -= getattr(times, "iowait", 0)
+    return busy
+
+
+def cpu_percent(interval=None, percpu=False):
+    """Return a float representing the current system-wide CPU
+    utilization as a percentage.
+
+    When *interval* is > 0.0 compares system CPU times elapsed before
+    and after the interval (blocking).
+
+    When *interval* is 0.0 or None compares system CPU times elapsed
+    since last call or module import, returning immediately (non
+    blocking). That means the first time this is called it will
+    return a meaningless 0.0 value which you should ignore.
+    In this case is recommended for accuracy that this function be
+    called with at least 0.1 seconds between calls.
+
+    When *percpu* is True returns a list of floats representing the
+    utilization as a percentage for each CPU.
+    First element of the list refers to first CPU, second element
+    to second CPU and so on.
+    The order of the list is consistent across calls.
+
+    Examples:
+
+      >>> # blocking, system-wide
+      >>> psutil.cpu_percent(interval=1)
+      2.0
+      >>>
+      >>> # blocking, per-cpu
+      >>> psutil.cpu_percent(interval=1, percpu=True)
+      [2.0, 1.0]
+      >>>
+      >>> # non-blocking (percentage since last call)
+      >>> psutil.cpu_percent(interval=None)
+      2.9
+      >>>
+    """
+    global _last_cpu_times
+    global _last_per_cpu_times
+    blocking = interval is not None and interval > 0.0
+    if interval is not None and interval < 0:
+        raise ValueError("interval is not positive (got %r)" % interval)
+
+    def calculate(t1, t2):
+        t1_all = _cpu_tot_time(t1)
+        t1_busy = _cpu_busy_time(t1)
+
+        t2_all = _cpu_tot_time(t2)
+        t2_busy = _cpu_busy_time(t2)
+
+        # this usually indicates a float precision issue
+        if t2_busy <= t1_busy:
+            return 0.0
+
+        busy_delta = t2_busy - t1_busy
+        all_delta = t2_all - t1_all
+        try:
+            busy_perc = (busy_delta / all_delta) * 100
+        except ZeroDivisionError:
+            return 0.0
+        else:
+            return round(busy_perc, 1)
+
+    # system-wide usage
+    if not percpu:
+        if blocking:
+            t1 = cpu_times()
+            time.sleep(interval)
+        else:
+            t1 = _last_cpu_times
+            if t1 is None:
+                # Something bad happened at import time. We'll
+                # get a meaningful result on the next call. See:
+                # https://github.com/giampaolo/psutil/pull/715
+                t1 = cpu_times()
+        _last_cpu_times = cpu_times()
+        return calculate(t1, _last_cpu_times)
+    # per-cpu usage
+    else:
+        ret = []
+        if blocking:
+            tot1 = cpu_times(percpu=True)
+            time.sleep(interval)
+        else:
+            tot1 = _last_per_cpu_times
+            if tot1 is None:
+                # Something bad happened at import time. We'll
+                # get a meaningful result on the next call. See:
+                # https://github.com/giampaolo/psutil/pull/715
+                tot1 = cpu_times(percpu=True)
+        _last_per_cpu_times = cpu_times(percpu=True)
+        for t1, t2 in zip(tot1, _last_per_cpu_times):
+            ret.append(calculate(t1, t2))
+        return ret
+
+
+# Use separate global vars for cpu_times_percent() so that it's
+# independent from cpu_percent() and they can both be used within
+# the same program.
+_last_cpu_times_2 = _last_cpu_times
+_last_per_cpu_times_2 = _last_per_cpu_times
+
+
+def cpu_times_percent(interval=None, percpu=False):
+    """Same as cpu_percent() but provides utilization percentages
+    for each specific CPU time as is returned by cpu_times().
+    For instance, on Linux we'll get:
+
+      >>> cpu_times_percent()
+      cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0,
+                 irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
+      >>>
+
+    *interval* and *percpu* arguments have the same meaning as in
+    cpu_percent().
+    """
+    global _last_cpu_times_2
+    global _last_per_cpu_times_2
+    blocking = interval is not None and interval > 0.0
+    if interval is not None and interval < 0:
+        raise ValueError("interval is not positive (got %r)" % interval)
+
+    def calculate(t1, t2):
+        nums = []
+        all_delta = _cpu_tot_time(t2) - _cpu_tot_time(t1)
+        for field in t1._fields:
+            field_delta = getattr(t2, field) - getattr(t1, field)
+            try:
+                field_perc = (100 * field_delta) / all_delta
+            except ZeroDivisionError:
+                field_perc = 0.0
+            field_perc = round(field_perc, 1)
+            # CPU times are always supposed to increase over time
+            # or at least remain the same and that's because time
+            # cannot go backwards.
+            # Surprisingly sometimes this might not be the case (at
+            # least on Windows and Linux), see:
+            # https://github.com/giampaolo/psutil/issues/392
+            # https://github.com/giampaolo/psutil/issues/645
+            # I really don't know what to do about that except
+            # forcing the value to 0 or 100.
+            if field_perc > 100.0:
+                field_perc = 100.0
+            # `<=` because `-0.0 == 0.0` evaluates to True
+            elif field_perc <= 0.0:
+                field_perc = 0.0
+            nums.append(field_perc)
+        return _psplatform.scputimes(*nums)
+
+    # system-wide usage
+    if not percpu:
+        if blocking:
+            t1 = cpu_times()
+            time.sleep(interval)
+        else:
+            t1 = _last_cpu_times_2
+            if t1 is None:
+                # Something bad happened at import time. We'll
+                # get a meaningful result on the next call. See:
+                # https://github.com/giampaolo/psutil/pull/715
+                t1 = cpu_times()
+        _last_cpu_times_2 = cpu_times()
+        return calculate(t1, _last_cpu_times_2)
+    # per-cpu usage
+    else:
+        ret = []
+        if blocking:
+            tot1 = cpu_times(percpu=True)
+            time.sleep(interval)
+        else:
+            tot1 = _last_per_cpu_times_2
+            if tot1 is None:
+                # Something bad happened at import time. We'll
+                # get a meaningful result on the next call. See:
+                # https://github.com/giampaolo/psutil/pull/715
+                tot1 = cpu_times(percpu=True)
+        _last_per_cpu_times_2 = cpu_times(percpu=True)
+        for t1, t2 in zip(tot1, _last_per_cpu_times_2):
+            ret.append(calculate(t1, t2))
+        return ret
+
+
+def cpu_stats():
+    """Return CPU statistics."""
+    return _psplatform.cpu_stats()
+
+
+if hasattr(_psplatform, "cpu_freq"):
+
+    def cpu_freq(percpu=False):
+        """Return CPU frequency as a nameduple including current,
+        min and max frequency expressed in Mhz.
+
+        If *percpu* is True and the system supports per-cpu frequency
+        retrieval (Linux only) a list of frequencies is returned for
+        each CPU. If not a list with one element is returned.
+        """
+        ret = _psplatform.cpu_freq()
+        if percpu:
+            return ret
+        else:
+            num_cpus = float(len(ret))
+            if num_cpus == 0:
+                return None
+            elif num_cpus == 1:
+                return ret[0]
+            else:
+                currs, mins, maxs = 0.0, 0.0, 0.0
+                for cpu in ret:
+                    currs += cpu.current
+                    mins += cpu.min
+                    maxs += cpu.max
+                current = currs / num_cpus
+                min_ = mins / num_cpus
+                max_ = maxs / num_cpus
+                return _common.scpufreq(current, min_, max_)
+
+    __all__.append("cpu_freq")
+
+
+# =====================================================================
+# --- system memory related functions
+# =====================================================================
+
+
+def virtual_memory():
+    """Return statistics about system memory usage as a namedtuple
+    including the following fields, expressed in bytes:
+
+     - total:
+       total physical memory available.
+
+     - available:
+       the memory that can be given instantly to processes without the
+       system going into swap.
+       This is calculated by summing different memory values depending
+       on the platform and it is supposed to be used to monitor actual
+       memory usage in a cross platform fashion.
+
+     - percent:
+       the percentage usage calculated as (total - available) / total * 100
+
+     - used:
+        memory used, calculated differently depending on the platform and
+        designed for informational purposes only:
+        OSX: active + inactive + wired
+        BSD: active + wired + cached
+        LINUX: total - free
+
+     - free:
+       memory not being used at all (zeroed) that is readily available;
+       note that this doesn't reflect the actual memory available
+       (use 'available' instead)
+
+    Platform-specific fields:
+
+     - active (UNIX):
+       memory currently in use or very recently used, and so it is in RAM.
+
+     - inactive (UNIX):
+       memory that is marked as not used.
+
+     - buffers (BSD, Linux):
+       cache for things like file system metadata.
+
+     - cached (BSD, OSX):
+       cache for various things.
+
+     - wired (OSX, BSD):
+       memory that is marked to always stay in RAM. It is never moved to disk.
+
+     - shared (BSD):
+       memory that may be simultaneously accessed by multiple processes.
+
+    The sum of 'used' and 'available' does not necessarily equal total.
+    On Windows 'available' and 'free' are the same.
+    """
+    global _TOTAL_PHYMEM
+    ret = _psplatform.virtual_memory()
+    # cached for later use in Process.memory_percent()
+    _TOTAL_PHYMEM = ret.total
+    return ret
+
+
+def swap_memory():
+    """Return system swap memory statistics as a namedtuple including
+    the following fields:
+
+     - total:   total swap memory in bytes
+     - used:    used swap memory in bytes
+     - free:    free swap memory in bytes
+     - percent: the percentage usage
+     - sin:     no. of bytes the system has swapped in from disk (cumulative)
+     - sout:    no. of bytes the system has swapped out from disk (cumulative)
+
+    'sin' and 'sout' on Windows are meaningless and always set to 0.
+    """
+    return _psplatform.swap_memory()
+
+
+# =====================================================================
+# --- disks/paritions related functions
+# =====================================================================
+
+
+def disk_usage(path):
+    """Return disk usage statistics about the given *path* as a
+    namedtuple including total, used and free space expressed in bytes
+    plus the percentage usage.
+    """
+    return _psplatform.disk_usage(path)
+
+
+def disk_partitions(all=False):
+    """Return mounted partitions as a list of
+    (device, mountpoint, fstype, opts) namedtuple.
+    'opts' field is a raw string separated by commas indicating mount
+    options which may vary depending on the platform.
+
+    If *all* parameter is False return physical devices only and ignore
+    all others.
+    """
+    return _psplatform.disk_partitions(all)
+
+
+def disk_io_counters(perdisk=False, nowrap=True):
+    """Return system disk I/O statistics as a namedtuple including
+    the following fields:
+
+     - read_count:  number of reads
+     - write_count: number of writes
+     - read_bytes:  number of bytes read
+     - write_bytes: number of bytes written
+     - read_time:   time spent reading from disk (in ms)
+     - write_time:  time spent writing to disk (in ms)
+
+    Platform specific:
+
+     - busy_time: (Linux, FreeBSD) time spent doing actual I/Os (in ms)
+     - read_merged_count (Linux): number of merged reads
+     - write_merged_count (Linux): number of merged writes
+
+    If *perdisk* is True return the same information for every
+    physical disk installed on the system as a dictionary
+    with partition names as the keys and the namedtuple
+    described above as the values.
+
+    If *nowrap* is True it detects and adjust the numbers which overflow
+    and wrap (restart from 0) and add "old value" to "new value" so that
+    the returned numbers will always be increasing or remain the same,
+    but never decrease.
+    "disk_io_counters.cache_clear()" can be used to invalidate the
+    cache.
+
+    On recent Windows versions 'diskperf -y' command may need to be
+    executed first otherwise this function won't find any disk.
+    """
+    rawdict = _psplatform.disk_io_counters()
+    if not rawdict:
+        return {} if perdisk else None
+    if nowrap:
+        rawdict = _wrap_numbers(rawdict, 'psutil.disk_io_counters')
+    nt = getattr(_psplatform, "sdiskio", _common.sdiskio)
+    if perdisk:
+        for disk, fields in rawdict.items():
+            rawdict[disk] = nt(*fields)
+        return rawdict
+    else:
+        return nt(*[sum(x) for x in zip(*rawdict.values())])
+
+
+disk_io_counters.cache_clear = functools.partial(
+    _wrap_numbers.cache_clear, 'psutil.disk_io_counters')
+disk_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
+
+
+# =====================================================================
+# --- network related functions
+# =====================================================================
+
+
+def net_io_counters(pernic=False, nowrap=True):
+    """Return network I/O statistics as a namedtuple including
+    the following fields:
+
+     - bytes_sent:   number of bytes sent
+     - bytes_recv:   number of bytes received
+     - packets_sent: number of packets sent
+     - packets_recv: number of packets received
+     - errin:        total number of errors while receiving
+     - errout:       total number of errors while sending
+     - dropin:       total number of incoming packets which were dropped
+     - dropout:      total number of outgoing packets which were dropped
+                     (always 0 on OSX and BSD)
+
+    If *pernic* is True return the same information for every
+    network interface installed on the system as a dictionary
+    with network interface names as the keys and the namedtuple
+    described above as the values.
+
+    If *nowrap* is True it detects and adjust the numbers which overflow
+    and wrap (restart from 0) and add "old value" to "new value" so that
+    the returned numbers will always be increasing or remain the same,
+    but never decrease.
+    "disk_io_counters.cache_clear()" can be used to invalidate the
+    cache.
+    """
+    rawdict = _psplatform.net_io_counters()
+    if not rawdict:
+        return {} if pernic else None
+    if nowrap:
+        rawdict = _wrap_numbers(rawdict, 'psutil.net_io_counters')
+    if pernic:
+        for nic, fields in rawdict.items():
+            rawdict[nic] = _common.snetio(*fields)
+        return rawdict
+    else:
+        return _common.snetio(*[sum(x) for x in zip(*rawdict.values())])
+
+
+net_io_counters.cache_clear = functools.partial(
+    _wrap_numbers.cache_clear, 'psutil.net_io_counters')
+net_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
+
+
+def net_connections(kind='inet'):
+    """Return system-wide socket connections as a list of
+    (fd, family, type, laddr, raddr, status, pid) namedtuples.
+    In case of limited privileges 'fd' and 'pid' may be set to -1
+    and None respectively.
+    The *kind* parameter filters for connections that fit the
+    following criteria:
+
+    +------------+----------------------------------------------------+
+    | Kind Value | Connections using                                  |
+    +------------+----------------------------------------------------+
+    | inet       | IPv4 and IPv6                                      |
+    | inet4      | IPv4                                               |
+    | inet6      | IPv6                                               |
+    | tcp        | TCP                                                |
+    | tcp4       | TCP over IPv4                                      |
+    | tcp6       | TCP over IPv6                                      |
+    | udp        | UDP                                                |
+    | udp4       | UDP over IPv4                                      |
+    | udp6       | UDP over IPv6                                      |
+    | unix       | UNIX socket (both UDP and TCP protocols)           |
+    | all        | the sum of all the possible families and protocols |
+    +------------+----------------------------------------------------+
+
+    On OSX this function requires root privileges.
+    """
+    return _psplatform.net_connections(kind)
+
+
+def net_if_addrs():
+    """Return the addresses associated to each NIC (network interface
+    card) installed on the system as a dictionary whose keys are the
+    NIC names and value is a list of namedtuples for each address
+    assigned to the NIC. Each namedtuple includes 5 fields:
+
+     - family: can be either socket.AF_INET, socket.AF_INET6 or
+               psutil.AF_LINK, which refers to a MAC address.
+     - address: is the primary address and it is always set.
+     - netmask: and 'broadcast' and 'ptp' may be None.
+     - ptp: stands for "point to point" and references the
+            destination address on a point to point interface
+            (typically a VPN).
+     - broadcast: and *ptp* are mutually exclusive.
+
+    Note: you can have more than one address of the same family
+    associated with each interface.
+    """
+    has_enums = sys.version_info >= (3, 4)
+    if has_enums:
+        import socket
+    rawlist = _psplatform.net_if_addrs()
+    rawlist.sort(key=lambda x: x[1])  # sort by family
+    ret = collections.defaultdict(list)
+    for name, fam, addr, mask, broadcast, ptp in rawlist:
+        if has_enums:
+            try:
+                fam = socket.AddressFamily(fam)
+            except ValueError:
+                if WINDOWS and fam == -1:
+                    fam = _psplatform.AF_LINK
+                elif (hasattr(_psplatform, "AF_LINK") and
+                        _psplatform.AF_LINK == fam):
+                    # Linux defines AF_LINK as an alias for AF_PACKET.
+                    # We re-set the family here so that repr(family)
+                    # will show AF_LINK rather than AF_PACKET
+                    fam = _psplatform.AF_LINK
+        if fam == _psplatform.AF_LINK:
+            # The underlying C function may return an incomplete MAC
+            # address in which case we fill it with null bytes, see:
+            # https://github.com/giampaolo/psutil/issues/786
+            separator = ":" if POSIX else "-"
+            while addr.count(separator) < 5:
+                addr += "%s00" % separator
+        ret[name].append(_common.snic(fam, addr, mask, broadcast, ptp))
+    return dict(ret)
+
+
+def net_if_stats():
+    """Return information about each NIC (network interface card)
+    installed on the system as a dictionary whose keys are the
+    NIC names and value is a namedtuple with the following fields:
+
+     - isup: whether the interface is up (bool)
+     - duplex: can be either NIC_DUPLEX_FULL, NIC_DUPLEX_HALF or
+               NIC_DUPLEX_UNKNOWN
+     - speed: the NIC speed expressed in mega bits (MB); if it can't
+              be determined (e.g. 'localhost') it will be set to 0.
+     - mtu: the maximum transmission unit expressed in bytes.
+    """
+    return _psplatform.net_if_stats()
+
+
+# =====================================================================
+# --- sensors
+# =====================================================================
+
+
+# Linux
+if hasattr(_psplatform, "sensors_temperatures"):
+
+    def sensors_temperatures(fahrenheit=False):
+        """Return hardware temperatures. Each entry is a namedtuple
+        representing a certain hardware sensor (it may be a CPU, an
+        hard disk or something else, depending on the OS and its
+        configuration).
+        All temperatures are expressed in celsius unless *fahrenheit*
+        is set to True.
+        """
+        def convert(n):
+            if n is not None:
+                return (float(n) * 9 / 5) + 32 if fahrenheit else n
+
+        ret = collections.defaultdict(list)
+        rawdict = _psplatform.sensors_temperatures()
+
+        for name, values in rawdict.items():
+            while values:
+                label, current, high, critical = values.pop(0)
+                current = convert(current)
+                high = convert(high)
+                critical = convert(critical)
+
+                if high and not critical:
+                    critical = high
+                elif critical and not high:
+                    high = critical
+
+                ret[name].append(
+                    _common.shwtemp(label, current, high, critical))
+
+        return dict(ret)
+
+    __all__.append("sensors_temperatures")
+
+
+# Linux
+if hasattr(_psplatform, "sensors_fans"):
+
+    def sensors_fans():
+        """Return fans speed. Each entry is a namedtuple
+        representing a certain hardware sensor.
+        All speed are expressed in RPM (rounds per minute).
+        """
+        return _psplatform.sensors_fans()
+
+    __all__.append("sensors_fans")
+
+
+# Linux, Windows, FreeBSD, OSX
+if hasattr(_psplatform, "sensors_battery"):
+
+    def sensors_battery():
+        """Return battery information. If no battery is installed
+        returns None.
+
+         - percent: battery power left as a percentage.
+         - secsleft: a rough approximation of how many seconds are left
+                     before the battery runs out of power. May be
+                     POWER_TIME_UNLIMITED or POWER_TIME_UNLIMITED.
+         - power_plugged: True if the AC power cable is connected.
+        """
+        return _psplatform.sensors_battery()
+
+    __all__.append("sensors_battery")
+
+
+# =====================================================================
+# --- other system related functions
+# =====================================================================
+
+
+def boot_time():
+    """Return the system boot time expressed in seconds since the epoch."""
+    # Note: we are not caching this because it is subject to
+    # system clock updates.
+    return _psplatform.boot_time()
+
+
+def users():
+    """Return users currently connected on the system as a list of
+    namedtuples including the following fields.
+
+     - user: the name of the user
+     - terminal: the tty or pseudo-tty associated with the user, if any.
+     - host: the host name associated with the entry, if any.
+     - started: the creation time as a floating point number expressed in
+       seconds since the epoch.
+    """
+    return _psplatform.users()
+
+
+# =====================================================================
+# --- Windows services
+# =====================================================================
+
+
+if WINDOWS:
+
+    def win_service_iter():
+        """Return a generator yielding a WindowsService instance for all
+        Windows services installed.
+        """
+        return _psplatform.win_service_iter()
+
+    def win_service_get(name):
+        """Get a Windows service by *name*.
+        Raise NoSuchProcess if no service with such name exists.
+        """
+        return _psplatform.win_service_get(name)
+
+
+# =====================================================================
+
+
+def test():  # pragma: no cover
+    """List info of all currently running processes emulating ps aux
+    output.
+    """
+    today_day = datetime.date.today()
+    templ = "%-10s %5s %4s %7s %7s %-13s %5s %7s  %s"
+    attrs = ['pid', 'memory_percent', 'name', 'cpu_times', 'create_time',
+             'memory_info']
+    if POSIX:
+        attrs.append('uids')
+        attrs.append('terminal')
+    print(templ % ("USER", "PID", "%MEM", "VSZ", "RSS", "TTY", "START", "TIME",
+                   "COMMAND"))
+    for p in process_iter(attrs=attrs, ad_value=''):
+        if p.info['create_time']:
+            ctime = datetime.datetime.fromtimestamp(p.info['create_time'])
+            if ctime.date() == today_day:
+                ctime = ctime.strftime("%H:%M")
+            else:
+                ctime = ctime.strftime("%b%d")
+        else:
+            ctime = ''
+        cputime = time.strftime("%M:%S",
+                                time.localtime(sum(p.info['cpu_times'])))
+        try:
+            user = p.username()
+        except Error:
+            user = ''
+        if WINDOWS and '\\' in user:
+            user = user.split('\\')[1]
+        vms = p.info['memory_info'] and \
+            int(p.info['memory_info'].vms / 1024) or '?'
+        rss = p.info['memory_info'] and \
+            int(p.info['memory_info'].rss / 1024) or '?'
+        memp = p.info['memory_percent'] and \
+            round(p.info['memory_percent'], 1) or '?'
+        print(templ % (
+            user[:10],
+            p.info['pid'],
+            memp,
+            vms,
+            rss,
+            p.info.get('terminal', '') or '?',
+            ctime,
+            cputime,
+            p.info['name'].strip() or '?'))
+
+
+del memoize, memoize_when_activated, division, deprecated_method
+if sys.version_info[0] < 3:
+    del num, x
+
+if __name__ == "__main__":
+    test()
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/_common.py
@@ -0,0 +1,575 @@
+# 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.
+
+"""Common objects shared by __init__.py and _ps*.py modules."""
+
+# Note: this module is imported by setup.py so it should not import
+# psutil or third-party modules.
+
+from __future__ import division
+
+import contextlib
+import errno
+import functools
+import os
+import socket
+import stat
+import sys
+import threading
+import warnings
+from collections import defaultdict
+from collections import namedtuple
+from socket import AF_INET
+from socket import SOCK_DGRAM
+from socket import SOCK_STREAM
+try:
+    from socket import AF_INET6
+except ImportError:
+    AF_INET6 = None
+try:
+    from socket import AF_UNIX
+except ImportError:
+    AF_UNIX = None
+
+if sys.version_info >= (3, 4):
+    import enum
+else:
+    enum = None
+
+# can't take it from _common.py as this script is imported by setup.py
+PY3 = sys.version_info[0] == 3
+
+__all__ = [
+    # constants
+    'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'OSX', 'POSIX', 'SUNOS',
+    'WINDOWS',
+    'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
+    # connection constants
+    'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
+    'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
+    'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT',
+    # net constants
+    'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN',
+    # process status constants
+    'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED',
+    'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
+    'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
+    'STATUS_WAKING', 'STATUS_ZOMBIE',
+    # named tuples
+    'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile',
+    'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart',
+    'sdiskusage', 'snetio', 'snic', 'snicstats', 'sswap', 'suser',
+    # utility functions
+    'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
+    'parse_environ_block', 'path_exists_strict', 'usage_percent',
+    'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers",
+]
+
+
+# ===================================================================
+# --- OS constants
+# ===================================================================
+
+
+POSIX = os.name == "posix"
+WINDOWS = os.name == "nt"
+LINUX = sys.platform.startswith("linux")
+OSX = sys.platform.startswith("darwin")
+FREEBSD = sys.platform.startswith("freebsd")
+OPENBSD = sys.platform.startswith("openbsd")
+NETBSD = sys.platform.startswith("netbsd")
+BSD = FREEBSD or OPENBSD or NETBSD
+SUNOS = sys.platform.startswith("sunos") or sys.platform.startswith("solaris")
+AIX = sys.platform.startswith("aix")
+
+
+# ===================================================================
+# --- API constants
+# ===================================================================
+
+
+# Process.status()
+STATUS_RUNNING = "running"
+STATUS_SLEEPING = "sleeping"
+STATUS_DISK_SLEEP = "disk-sleep"
+STATUS_STOPPED = "stopped"
+STATUS_TRACING_STOP = "tracing-stop"
+STATUS_ZOMBIE = "zombie"
+STATUS_DEAD = "dead"
+STATUS_WAKE_KILL = "wake-kill"
+STATUS_WAKING = "waking"
+STATUS_IDLE = "idle"  # FreeBSD, OSX
+STATUS_LOCKED = "locked"  # FreeBSD
+STATUS_WAITING = "waiting"  # FreeBSD
+STATUS_SUSPENDED = "suspended"  # NetBSD
+
+# Process.connections() and psutil.net_connections()
+CONN_ESTABLISHED = "ESTABLISHED"
+CONN_SYN_SENT = "SYN_SENT"
+CONN_SYN_RECV = "SYN_RECV"
+CONN_FIN_WAIT1 = "FIN_WAIT1"
+CONN_FIN_WAIT2 = "FIN_WAIT2"
+CONN_TIME_WAIT = "TIME_WAIT"
+CONN_CLOSE = "CLOSE"
+CONN_CLOSE_WAIT = "CLOSE_WAIT"
+CONN_LAST_ACK = "LAST_ACK"
+CONN_LISTEN = "LISTEN"
+CONN_CLOSING = "CLOSING"
+CONN_NONE = "NONE"
+
+# net_if_stats()
+if enum is None:
+    NIC_DUPLEX_FULL = 2
+    NIC_DUPLEX_HALF = 1
+    NIC_DUPLEX_UNKNOWN = 0
+else:
+    class NicDuplex(enum.IntEnum):
+        NIC_DUPLEX_FULL = 2
+        NIC_DUPLEX_HALF = 1
+        NIC_DUPLEX_UNKNOWN = 0
+
+    globals().update(NicDuplex.__members__)
+
+# sensors_battery()
+if enum is None:
+    POWER_TIME_UNKNOWN = -1
+    POWER_TIME_UNLIMITED = -2
+else:
+    class BatteryTime(enum.IntEnum):
+        POWER_TIME_UNKNOWN = -1
+        POWER_TIME_UNLIMITED = -2
+
+    globals().update(BatteryTime.__members__)
+
+# --- others
+
+ENCODING = sys.getfilesystemencoding()
+if not PY3:
+    ENCODING_ERRS = "replace"
+else:
+    try:
+        ENCODING_ERRS = sys.getfilesystemencodeerrors()  # py 3.6
+    except AttributeError:
+        ENCODING_ERRS = "surrogateescape" if POSIX else "replace"
+
+
+# ===================================================================
+# --- namedtuples
+# ===================================================================
+
+# --- for system functions
+
+# psutil.swap_memory()
+sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin',
+                             'sout'])
+# psutil.disk_usage()
+sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent'])
+# psutil.disk_io_counters()
+sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
+                                 'read_bytes', 'write_bytes',
+                                 'read_time', 'write_time'])
+# psutil.disk_partitions()
+sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts'])
+# psutil.net_io_counters()
+snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv',
+                               'packets_sent', 'packets_recv',
+                               'errin', 'errout',
+                               'dropin', 'dropout'])
+# psutil.users()
+suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid'])
+# psutil.net_connections()
+sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
+                             'status', 'pid'])
+# psutil.net_if_addrs()
+snic = namedtuple('snic', ['family', 'address', 'netmask', 'broadcast', 'ptp'])
+# psutil.net_if_stats()
+snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu'])
+# psutil.cpu_stats()
+scpustats = namedtuple(
+    'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'])
+# psutil.cpu_freq()
+scpufreq = namedtuple('scpufreq', ['current', 'min', 'max'])
+# psutil.sensors_temperatures()
+shwtemp = namedtuple(
+    'shwtemp', ['label', 'current', 'high', 'critical'])
+# psutil.sensors_battery()
+sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged'])
+# psutil.sensors_battery()
+sfan = namedtuple('sfan', ['label', 'current'])
+
+# --- for Process methods
+
+# psutil.Process.cpu_times()
+pcputimes = namedtuple('pcputimes',
+                       ['user', 'system', 'children_user', 'children_system'])
+# psutil.Process.open_files()
+popenfile = namedtuple('popenfile', ['path', 'fd'])
+# psutil.Process.threads()
+pthread = namedtuple('pthread', ['id', 'user_time', 'system_time'])
+# psutil.Process.uids()
+puids = namedtuple('puids', ['real', 'effective', 'saved'])
+# psutil.Process.gids()
+pgids = namedtuple('pgids', ['real', 'effective', 'saved'])
+# psutil.Process.io_counters()
+pio = namedtuple('pio', ['read_count', 'write_count',
+                         'read_bytes', 'write_bytes'])
+# psutil.Process.ionice()
+pionice = namedtuple('pionice', ['ioclass', 'value'])
+# psutil.Process.ctx_switches()
+pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary'])
+# psutil.Process.connections()
+pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr',
+                             'status'])
+
+# psutil.connections() and psutil.Process.connections()
+addr = namedtuple('addr', ['ip', 'port'])
+
+
+# ===================================================================
+# --- Process.connections() 'kind' parameter mapping
+# ===================================================================
+
+
+conn_tmap = {
+    "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
+    "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
+    "tcp4": ([AF_INET], [SOCK_STREAM]),
+    "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
+    "udp4": ([AF_INET], [SOCK_DGRAM]),
+    "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
+    "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
+    "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
+}
+
+if AF_INET6 is not None:
+    conn_tmap.update({
+        "tcp6": ([AF_INET6], [SOCK_STREAM]),
+        "udp6": ([AF_INET6], [SOCK_DGRAM]),
+    })
+
+if AF_UNIX is not None:
+    conn_tmap.update({
+        "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
+    })
+
+del AF_INET, AF_UNIX, SOCK_STREAM, SOCK_DGRAM
+
+
+# ===================================================================
+# --- utils
+# ===================================================================
+
+
+def usage_percent(used, total, _round=None):
+    """Calculate percentage usage of 'used' against 'total'."""
+    try:
+        ret = (used / total) * 100
+    except ZeroDivisionError:
+        ret = 0.0 if isinstance(used, float) or isinstance(total, float) else 0
+    if _round is not None:
+        return round(ret, _round)
+    else:
+        return ret
+
+
+def memoize(fun):
+    """A simple memoize decorator for functions supporting (hashable)
+    positional arguments.
+    It also provides a cache_clear() function for clearing the cache:
+
+    >>> @memoize
+    ... def foo()
+    ...     return 1
+        ...
+    >>> foo()
+    1
+    >>> foo.cache_clear()
+    >>>
+    """
+    @functools.wraps(fun)
+    def wrapper(*args, **kwargs):
+        key = (args, frozenset(sorted(kwargs.items())))
+        try:
+            return cache[key]
+        except KeyError:
+            ret = cache[key] = fun(*args, **kwargs)
+            return ret
+
+    def cache_clear():
+        """Clear cache."""
+        cache.clear()
+
+    cache = {}
+    wrapper.cache_clear = cache_clear
+    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:
+    http://mail.python.org/pipermail/python-dev/2012-June/120787.html
+    """
+    try:
+        st = os.stat(path)
+    except OSError as err:
+        if err.errno in (errno.EPERM, errno.EACCES):
+            raise
+        return False
+    else:
+        return stat.S_ISREG(st.st_mode)
+
+
+def path_exists_strict(path):
+    """Same as os.path.exists() but does not swallow EACCES / EPERM
+    exceptions, see:
+    http://mail.python.org/pipermail/python-dev/2012-June/120787.html
+    """
+    try:
+        os.stat(path)
+    except OSError as err:
+        if err.errno in (errno.EPERM, errno.EACCES):
+            raise
+        return False
+    else:
+        return True
+
+
+@memoize
+def supports_ipv6():
+    """Return True if IPv6 is supported on this platform."""
+    if not socket.has_ipv6 or AF_INET6 is None:
+        return False
+    try:
+        sock = socket.socket(AF_INET6, socket.SOCK_STREAM)
+        with contextlib.closing(sock):
+            sock.bind(("::1", 0))
+        return True
+    except socket.error:
+        return False
+
+
+def parse_environ_block(data):
+    """Parse a C environ block of environment variables into a dictionary."""
+    # The block is usually raw data from the target process.  It might contain
+    # trailing garbage and lines that do not look like assignments.
+    ret = {}
+    pos = 0
+
+    # localize global variable to speed up access.
+    WINDOWS_ = WINDOWS
+    while True:
+        next_pos = data.find("\0", pos)
+        # nul byte at the beginning or double nul byte means finish
+        if next_pos <= pos:
+            break
+        # there might not be an equals sign
+        equal_pos = data.find("=", pos, next_pos)
+        if equal_pos > pos:
+            key = data[pos:equal_pos]
+            value = data[equal_pos + 1:next_pos]
+            # Windows expects environment variables to be uppercase only
+            if WINDOWS_:
+                key = key.upper()
+            ret[key] = value
+        pos = next_pos + 1
+
+    return ret
+
+
+def sockfam_to_enum(num):
+    """Convert a numeric socket family value to an IntEnum member.
+    If it's not a known member, return the numeric value itself.
+    """
+    if enum is None:
+        return num
+    else:  # pragma: no cover
+        try:
+            return socket.AddressFamily(num)
+        except (ValueError, AttributeError):
+            return num
+
+
+def socktype_to_enum(num):
+    """Convert a numeric socket type value to an IntEnum member.
+    If it's not a known member, return the numeric value itself.
+    """
+    if enum is None:
+        return num
+    else:  # pragma: no cover
+        try:
+            return socket.AddressType(num)
+        except (ValueError, AttributeError):
+            return num
+
+
+def deprecated_method(replacement):
+    """A decorator which can be used to mark a method as deprecated
+    'replcement' is the method name which will be called instead.
+    """
+    def outer(fun):
+        msg = "%s() is deprecated and will be removed; use %s() instead" % (
+            fun.__name__, replacement)
+        if fun.__doc__ is None:
+            fun.__doc__ = msg
+
+        @functools.wraps(fun)
+        def inner(self, *args, **kwargs):
+            warnings.warn(msg, category=FutureWarning, stacklevel=2)
+            return getattr(self, replacement)(*args, **kwargs)
+        return inner
+    return outer
+
+
+class _WrapNumbers:
+    """Watches numbers so that they don't overflow and wrap
+    (reset to zero).
+    """
+
+    def __init__(self):
+        self.lock = threading.Lock()
+        self.cache = {}
+        self.reminders = {}
+        self.reminder_keys = {}
+
+    def _add_dict(self, input_dict, name):
+        assert name not in self.cache
+        assert name not in self.reminders
+        assert name not in self.reminder_keys
+        self.cache[name] = input_dict
+        self.reminders[name] = defaultdict(int)
+        self.reminder_keys[name] = defaultdict(set)
+
+    def _remove_dead_reminders(self, input_dict, name):
+        """In case the number of keys changed between calls (e.g. a
+        disk disappears) this removes the entry from self.reminders.
+        """
+        old_dict = self.cache[name]
+        gone_keys = set(old_dict.keys()) - set(input_dict.keys())
+        for gone_key in gone_keys:
+            for remkey in self.reminder_keys[name][gone_key]:
+                del self.reminders[name][remkey]
+            del self.reminder_keys[name][gone_key]
+
+    def run(self, input_dict, name):
+        """Cache dict and sum numbers which overflow and wrap.
+        Return an updated copy of `input_dict`
+        """
+        if name not in self.cache:
+            # This was the first call.
+            self._add_dict(input_dict, name)
+            return input_dict
+
+        self._remove_dead_reminders(input_dict, name)
+
+        old_dict = self.cache[name]
+        new_dict = {}
+        for key in input_dict.keys():
+            input_tuple = input_dict[key]
+            try:
+                old_tuple = old_dict[key]
+            except KeyError:
+                # The input dict has a new key (e.g. a new disk or NIC)
+                # which didn't exist in the previous call.
+                new_dict[key] = input_tuple
+                continue
+
+            bits = []
+            for i in range(len(input_tuple)):
+                input_value = input_tuple[i]
+                old_value = old_tuple[i]
+                remkey = (key, i)
+                if input_value < old_value:
+                    # it wrapped!
+                    self.reminders[name][remkey] += old_value
+                    self.reminder_keys[name][key].add(remkey)
+                bits.append(input_value + self.reminders[name][remkey])
+
+            new_dict[key] = tuple(bits)
+
+        self.cache[name] = input_dict
+        return new_dict
+
+    def cache_clear(self, name=None):
+        """Clear the internal cache, optionally only for function 'name'."""
+        with self.lock:
+            if name is None:
+                self.cache.clear()
+                self.reminders.clear()
+                self.reminder_keys.clear()
+            else:
+                self.cache.pop(name, None)
+                self.reminders.pop(name, None)
+                self.reminder_keys.pop(name, None)
+
+    def cache_info(self):
+        """Return internal cache dicts as a tuple of 3 elements."""
+        with self.lock:
+            return (self.cache, self.reminders, self.reminder_keys)
+
+
+def wrap_numbers(input_dict, name):
+    """Given an `input_dict` and a function `name`, adjust the numbers
+    which "wrap" (restart from zero) across different calls by adding
+    "old value" to "new value" and return an updated dict.
+    """
+    with _wn.lock:
+        return _wn.run(input_dict, name)
+
+
+_wn = _WrapNumbers()
+wrap_numbers.cache_clear = _wn.cache_clear
+wrap_numbers.cache_info = _wn.cache_info
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/_compat.py
@@ -0,0 +1,249 @@
+# 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.
+
+"""Module which provides compatibility with older Python versions."""
+
+import collections
+import functools
+import os
+import sys
+
+__all__ = ["PY3", "long", "xrange", "unicode", "basestring", "u", "b",
+           "callable", "lru_cache", "which"]
+
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+    long = int
+    xrange = range
+    unicode = str
+    basestring = str
+
+    def u(s):
+        return s
+
+    def b(s):
+        return s.encode("latin-1")
+else:
+    long = long
+    xrange = xrange
+    unicode = unicode
+    basestring = basestring
+
+    def u(s):
+        return unicode(s, "unicode_escape")
+
+    def b(s):
+        return s
+
+
+# removed in 3.0, reintroduced in 3.2
+try:
+    callable = callable
+except NameError:
+    def callable(obj):
+        return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+
+
+# --- stdlib additions
+
+
+# py 3.2 functools.lru_cache
+# Taken from: http://code.activestate.com/recipes/578078
+# Credit: Raymond Hettinger
+try:
+    from functools import lru_cache
+except ImportError:
+    try:
+        from threading import RLock
+    except ImportError:
+        from dummy_threading import RLock
+
+    _CacheInfo = collections.namedtuple(
+        "CacheInfo", ["hits", "misses", "maxsize", "currsize"])
+
+    class _HashedSeq(list):
+        __slots__ = 'hashvalue'
+
+        def __init__(self, tup, hash=hash):
+            self[:] = tup
+            self.hashvalue = hash(tup)
+
+        def __hash__(self):
+            return self.hashvalue
+
+    def _make_key(args, kwds, typed,
+                  kwd_mark=(object(), ),
+                  fasttypes=set((int, str, frozenset, type(None))),
+                  sorted=sorted, tuple=tuple, type=type, len=len):
+        key = args
+        if kwds:
+            sorted_items = sorted(kwds.items())
+            key += kwd_mark
+            for item in sorted_items:
+                key += item
+        if typed:
+            key += tuple(type(v) for v in args)
+            if kwds:
+                key += tuple(type(v) for k, v in sorted_items)
+        elif len(key) == 1 and type(key[0]) in fasttypes:
+            return key[0]
+        return _HashedSeq(key)
+
+    def lru_cache(maxsize=100, typed=False):
+        """Least-recently-used cache decorator, see:
+        http://docs.python.org/3/library/functools.html#functools.lru_cache
+        """
+        def decorating_function(user_function):
+            cache = dict()
+            stats = [0, 0]
+            HITS, MISSES = 0, 1
+            make_key = _make_key
+            cache_get = cache.get
+            _len = len
+            lock = RLock()
+            root = []
+            root[:] = [root, root, None, None]
+            nonlocal_root = [root]
+            PREV, NEXT, KEY, RESULT = 0, 1, 2, 3
+            if maxsize == 0:
+                def wrapper(*args, **kwds):
+                    result = user_function(*args, **kwds)
+                    stats[MISSES] += 1
+                    return result
+            elif maxsize is None:
+                def wrapper(*args, **kwds):
+                    key = make_key(args, kwds, typed)
+                    result = cache_get(key, root)
+                    if result is not root:
+                        stats[HITS] += 1
+                        return result
+                    result = user_function(*args, **kwds)
+                    cache[key] = result
+                    stats[MISSES] += 1
+                    return result
+            else:
+                def wrapper(*args, **kwds):
+                    if kwds or typed:
+                        key = make_key(args, kwds, typed)
+                    else:
+                        key = args
+                    lock.acquire()
+                    try:
+                        link = cache_get(key)
+                        if link is not None:
+                            root, = nonlocal_root
+                            link_prev, link_next, key, result = link
+                            link_prev[NEXT] = link_next
+                            link_next[PREV] = link_prev
+                            last = root[PREV]
+                            last[NEXT] = root[PREV] = link
+                            link[PREV] = last
+                            link[NEXT] = root
+                            stats[HITS] += 1
+                            return result
+                    finally:
+                        lock.release()
+                    result = user_function(*args, **kwds)
+                    lock.acquire()
+                    try:
+                        root, = nonlocal_root
+                        if key in cache:
+                            pass
+                        elif _len(cache) >= maxsize:
+                            oldroot = root
+                            oldroot[KEY] = key
+                            oldroot[RESULT] = result
+                            root = nonlocal_root[0] = oldroot[NEXT]
+                            oldkey = root[KEY]
+                            root[KEY] = root[RESULT] = None
+                            del cache[oldkey]
+                            cache[key] = oldroot
+                        else:
+                            last = root[PREV]
+                            link = [last, root, key, result]
+                            last[NEXT] = root[PREV] = cache[key] = link
+                        stats[MISSES] += 1
+                    finally:
+                        lock.release()
+                    return result
+
+            def cache_info():
+                """Report cache statistics"""
+                lock.acquire()
+                try:
+                    return _CacheInfo(stats[HITS], stats[MISSES], maxsize,
+                                      len(cache))
+                finally:
+                    lock.release()
+
+            def cache_clear():
+                """Clear the cache and cache statistics"""
+                lock.acquire()
+                try:
+                    cache.clear()
+                    root = nonlocal_root[0]
+                    root[:] = [root, root, None, None]
+                    stats[:] = [0, 0]
+                finally:
+                    lock.release()
+
+            wrapper.__wrapped__ = user_function
+            wrapper.cache_info = cache_info
+            wrapper.cache_clear = cache_clear
+            return functools.update_wrapper(wrapper, user_function)
+
+        return decorating_function
+
+
+# python 3.3
+try:
+    from shutil import which
+except ImportError:
+    def which(cmd, mode=os.F_OK | os.X_OK, path=None):
+        """Given a command, mode, and a PATH string, return the path which
+        conforms to the given mode on the PATH, or None if there is no such
+        file.
+
+        `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
+        of os.environ.get("PATH"), or can be overridden with a custom search
+        path.
+        """
+        def _access_check(fn, mode):
+            return (os.path.exists(fn) and os.access(fn, mode) and
+                    not os.path.isdir(fn))
+
+        if os.path.dirname(cmd):
+            if _access_check(cmd, mode):
+                return cmd
+            return None
+
+        if path is None:
+            path = os.environ.get("PATH", os.defpath)
+        if not path:
+            return None
+        path = path.split(os.pathsep)
+
+        if sys.platform == "win32":
+            if os.curdir not in path:
+                path.insert(0, os.curdir)
+
+            pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
+            if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
+                files = [cmd]
+            else:
+                files = [cmd + ext for ext in pathext]
+        else:
+            files = [cmd]
+
+        seen = set()
+        for dir in path:
+            normdir = os.path.normcase(dir)
+            if normdir not in seen:
+                seen.add(normdir)
+                for thefile in files:
+                    name = os.path.join(dir, thefile)
+                    if _access_check(name, mode):
+                        return name
+        return None
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/_exceptions.py
@@ -0,0 +1,94 @@
+# 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.
+
+
+class Error(Exception):
+    """Base exception class. All other psutil exceptions inherit
+    from this one.
+    """
+
+    def __init__(self, msg=""):
+        Exception.__init__(self, msg)
+        self.msg = msg
+
+    def __repr__(self):
+        ret = "psutil.%s %s" % (self.__class__.__name__, self.msg)
+        return ret.strip()
+
+    __str__ = __repr__
+
+
+class NoSuchProcess(Error):
+    """Exception raised when a process with a certain PID doesn't
+    or no longer exists.
+    """
+
+    def __init__(self, pid, name=None, msg=None):
+        Error.__init__(self, msg)
+        self.pid = pid
+        self.name = name
+        self.msg = msg
+        if msg is None:
+            if name:
+                details = "(pid=%s, name=%s)" % (self.pid, repr(self.name))
+            else:
+                details = "(pid=%s)" % self.pid
+            self.msg = "process no longer exists " + details
+
+
+class ZombieProcess(NoSuchProcess):
+    """Exception raised when querying a zombie process. This is
+    raised on OSX, BSD and Solaris only, and not always: depending
+    on the query the OS may be able to succeed anyway.
+    On Linux all zombie processes are querable (hence this is never
+    raised). Windows doesn't have zombie processes.
+    """
+
+    def __init__(self, pid, name=None, ppid=None, msg=None):
+        NoSuchProcess.__init__(self, msg)
+        self.pid = pid
+        self.ppid = ppid
+        self.name = name
+        self.msg = msg
+        if msg is None:
+            args = ["pid=%s" % pid]
+            if name:
+                args.append("name=%s" % repr(self.name))
+            if ppid:
+                args.append("ppid=%s" % self.ppid)
+            details = "(%s)" % ", ".join(args)
+            self.msg = "process still exists but it's a zombie " + details
+
+
+class AccessDenied(Error):
+    """Exception raised when permission to perform an action is denied."""
+
+    def __init__(self, pid=None, name=None, msg=None):
+        Error.__init__(self, msg)
+        self.pid = pid
+        self.name = name
+        self.msg = msg
+        if msg is None:
+            if (pid is not None) and (name is not None):
+                self.msg = "(pid=%s, name=%s)" % (pid, repr(name))
+            elif (pid is not None):
+                self.msg = "(pid=%s)" % self.pid
+            else:
+                self.msg = ""
+
+
+class TimeoutExpired(Error):
+    """Raised on Process.wait(timeout) if timeout expires and process
+    is still alive.
+    """
+
+    def __init__(self, seconds, pid=None, name=None):
+        Error.__init__(self, "timeout after %s seconds" % seconds)
+        self.seconds = seconds
+        self.pid = pid
+        self.name = name
+        if (pid is not None) and (name is not None):
+            self.msg += " (pid=%s, name=%s)" % (pid, repr(name))
+        elif (pid is not None):
+            self.msg += " (pid=%s)" % self.pid
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/_psaix.py
@@ -0,0 +1,573 @@
+# Copyright (c) 2009, Giampaolo Rodola'
+# Copyright (c) 2017, Arnon Yaari
+# All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""AIX platform implementation."""
+
+import errno
+import glob
+import os
+import re
+import subprocess
+import sys
+from collections import namedtuple
+from socket import AF_INET
+
+from . import _common
+from . import _psposix
+from . import _psutil_aix as cext
+from . import _psutil_posix as cext_posix
+from ._common import AF_INET6
+from ._common import memoize_when_activated
+from ._common import NIC_DUPLEX_FULL
+from ._common import NIC_DUPLEX_HALF
+from ._common import NIC_DUPLEX_UNKNOWN
+from ._common import sockfam_to_enum
+from ._common import socktype_to_enum
+from ._common import usage_percent
+from ._compat import PY3
+from ._exceptions import AccessDenied
+from ._exceptions import NoSuchProcess
+from ._exceptions import ZombieProcess
+
+
+__extra__all__ = ["PROCFS_PATH"]
+
+
+# =====================================================================
+# --- globals
+# =====================================================================
+
+
+HAS_THREADS = hasattr(cext, "proc_threads")
+
+PAGE_SIZE = os.sysconf('SC_PAGE_SIZE')
+AF_LINK = cext_posix.AF_LINK
+
+PROC_STATUSES = {
+    cext.SIDL: _common.STATUS_IDLE,
+    cext.SZOMB: _common.STATUS_ZOMBIE,
+    cext.SACTIVE: _common.STATUS_RUNNING,
+    cext.SSWAP: _common.STATUS_RUNNING,      # TODO what status is this?
+    cext.SSTOP: _common.STATUS_STOPPED,
+}
+
+TCP_STATUSES = {
+    cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
+    cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
+    cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
+    cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
+    cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
+    cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
+    cext.TCPS_CLOSED: _common.CONN_CLOSE,
+    cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
+    cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
+    cext.TCPS_LISTEN: _common.CONN_LISTEN,
+    cext.TCPS_CLOSING: _common.CONN_CLOSING,
+    cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
+}
+
+proc_info_map = dict(
+    ppid=0,
+    rss=1,
+    vms=2,
+    create_time=3,
+    nice=4,
+    num_threads=5,
+    status=6,
+    ttynr=7)
+
+
+# =====================================================================
+# --- named tuples
+# =====================================================================
+
+
+# psutil.Process.memory_info()
+pmem = namedtuple('pmem', ['rss', 'vms'])
+# psutil.Process.memory_full_info()
+pfullmem = pmem
+# psutil.Process.cpu_times()
+scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
+# psutil.virtual_memory()
+svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
+# psutil.Process.memory_maps(grouped=True)
+pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss', 'anon', 'locked'])
+# psutil.Process.memory_maps(grouped=False)
+pmmap_ext = namedtuple(
+    'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
+
+
+# =====================================================================
+# --- utils
+# =====================================================================
+
+
+def get_procfs_path():
+    """Return updated psutil.PROCFS_PATH constant."""
+    return sys.modules['psutil'].PROCFS_PATH
+
+
+# =====================================================================
+# --- memory
+# =====================================================================
+
+
+def virtual_memory():
+    total, avail, free, pinned, inuse = cext.virtual_mem()
+    percent = usage_percent((total - avail), total, _round=1)
+    return svmem(total, avail, percent, inuse, free)
+
+
+def swap_memory():
+    """Swap system memory as a (total, used, free, sin, sout) tuple."""
+    total, free, sin, sout = cext.swap_mem()
+    used = total - free
+    percent = usage_percent(used, total, _round=1)
+    return _common.sswap(total, used, free, percent, sin, sout)
+
+
+# =====================================================================
+# --- CPU
+# =====================================================================
+
+
+def cpu_times():
+    """Return system-wide CPU times as a named tuple"""
+    ret = cext.per_cpu_times()
+    return scputimes(*[sum(x) for x in zip(*ret)])
+
+
+def per_cpu_times():
+    """Return system per-CPU times as a list of named tuples"""
+    ret = cext.per_cpu_times()
+    return [scputimes(*x) for x in ret]
+
+
+def cpu_count_logical():
+    """Return the number of logical CPUs in the system."""
+    try:
+        return os.sysconf("SC_NPROCESSORS_ONLN")
+    except ValueError:
+        # mimic os.cpu_count() behavior
+        return None
+
+
+def cpu_count_physical():
+    cmd = "lsdev -Cc processor"
+    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
+                         stderr=subprocess.PIPE)
+    stdout, stderr = p.communicate()
+    if PY3:
+        stdout, stderr = [x.decode(sys.stdout.encoding)
+                          for x in (stdout, stderr)]
+    if p.returncode != 0:
+        raise RuntimeError("%r command error\n%s" % (cmd, stderr))
+    processors = stdout.strip().splitlines()
+    return len(processors) or None
+
+
+def cpu_stats():
+    """Return various CPU stats as a named tuple."""
+    ctx_switches, interrupts, soft_interrupts, syscalls = cext.cpu_stats()
+    return _common.scpustats(
+        ctx_switches, interrupts, soft_interrupts, syscalls)
+
+
+# =====================================================================
+# --- disks
+# =====================================================================
+
+
+disk_io_counters = cext.disk_io_counters
+disk_usage = _psposix.disk_usage
+
+
+def disk_partitions(all=False):
+    """Return system disk partitions."""
+    # TODO - the filtering logic should be better checked so that
+    # it tries to reflect 'df' as much as possible
+    retlist = []
+    partitions = cext.disk_partitions()
+    for partition in partitions:
+        device, mountpoint, fstype, opts = partition
+        if device == 'none':
+            device = ''
+        if not all:
+            # Differently from, say, Linux, we don't have a list of
+            # common fs types so the best we can do, AFAIK, is to
+            # filter by filesystem having a total size > 0.
+            if not disk_usage(mountpoint).total:
+                continue
+        ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+        retlist.append(ntuple)
+    return retlist
+
+
+# =====================================================================
+# --- network
+# =====================================================================
+
+
+net_if_addrs = cext_posix.net_if_addrs
+net_io_counters = cext.net_io_counters
+
+
+def net_connections(kind, _pid=-1):
+    """Return socket connections.  If pid == -1 return system-wide
+    connections (as opposed to connections opened by one process only).
+    """
+    cmap = _common.conn_tmap
+    if kind not in cmap:
+        raise ValueError("invalid %r kind argument; choose between %s"
+                         % (kind, ', '.join([repr(x) for x in cmap])))
+    families, types = _common.conn_tmap[kind]
+    rawlist = cext.net_connections(_pid)
+    ret = set()
+    for item in rawlist:
+        fd, fam, type_, laddr, raddr, status, pid = item
+        if fam not in families:
+            continue
+        if type_ not in types:
+            continue
+        status = TCP_STATUSES[status]
+        if fam in (AF_INET, AF_INET6):
+            if laddr:
+                laddr = _common.addr(*laddr)
+            if raddr:
+                raddr = _common.addr(*raddr)
+        fam = sockfam_to_enum(fam)
+        type_ = socktype_to_enum(type_)
+        if _pid == -1:
+            nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid)
+        else:
+            nt = _common.pconn(fd, fam, type_, laddr, raddr, status)
+        ret.add(nt)
+    return list(ret)
+
+
+def net_if_stats():
+    """Get NIC stats (isup, duplex, speed, mtu)."""
+    duplex_map = {"Full": NIC_DUPLEX_FULL,
+                  "Half": NIC_DUPLEX_HALF}
+    names = set([x[0] for x in net_if_addrs()])
+    ret = {}
+    for name in names:
+        isup, mtu = cext.net_if_stats(name)
+
+        # try to get speed and duplex
+        # TODO: rewrite this in C (entstat forks, so use truss -f to follow.
+        # looks like it is using an undocumented ioctl?)
+        duplex = ""
+        speed = 0
+        p = subprocess.Popen(["/usr/bin/entstat", "-d", name],
+                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        stdout, stderr = p.communicate()
+        if PY3:
+            stdout, stderr = [x.decode(sys.stdout.encoding)
+                              for x in (stdout, stderr)]
+        if p.returncode == 0:
+            re_result = re.search("Running: (\d+) Mbps.*?(\w+) Duplex", stdout)
+            if re_result is not None:
+                speed = int(re_result.group(1))
+                duplex = re_result.group(2)
+
+        duplex = duplex_map.get(duplex, NIC_DUPLEX_UNKNOWN)
+        ret[name] = _common.snicstats(isup, duplex, speed, mtu)
+    return ret
+
+
+# =====================================================================
+# --- other system functions
+# =====================================================================
+
+
+def boot_time():
+    """The system boot time expressed in seconds since the epoch."""
+    return cext.boot_time()
+
+
+def users():
+    """Return currently connected users as a list of namedtuples."""
+    retlist = []
+    rawlist = cext.users()
+    localhost = (':0.0', ':0')
+    for item in rawlist:
+        user, tty, hostname, tstamp, user_process, pid = item
+        # note: the underlying C function includes entries about
+        # system boot, run level and others.  We might want
+        # to use them in the future.
+        if not user_process:
+            continue
+        if hostname in localhost:
+            hostname = 'localhost'
+        nt = _common.suser(user, tty, hostname, tstamp, pid)
+        retlist.append(nt)
+    return retlist
+
+
+# =====================================================================
+# --- processes
+# =====================================================================
+
+
+def pids():
+    """Returns a list of PIDs currently running on the system."""
+    return [int(x) for x in os.listdir(get_procfs_path()) if x.isdigit()]
+
+
+def pid_exists(pid):
+    """Check for the existence of a unix pid."""
+    return os.path.exists(os.path.join(get_procfs_path(), str(pid), "psinfo"))
+
+
+def wrap_exceptions(fun):
+    """Call callable into a try/except clause and translate ENOENT,
+    EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
+    """
+
+    def wrapper(self, *args, **kwargs):
+        try:
+            return fun(self, *args, **kwargs)
+        except EnvironmentError as err:
+            # support for private module import
+            if (NoSuchProcess is None or AccessDenied is None or
+                    ZombieProcess is None):
+                raise
+            # ENOENT (no such file or directory) gets raised on open().
+            # ESRCH (no such process) can get raised on read() if
+            # process is gone in meantime.
+            if err.errno in (errno.ENOENT, errno.ESRCH):
+                if not pid_exists(self.pid):
+                    raise NoSuchProcess(self.pid, self._name)
+                else:
+                    raise ZombieProcess(self.pid, self._name, self._ppid)
+            if err.errno in (errno.EPERM, errno.EACCES):
+                raise AccessDenied(self.pid, self._name)
+            raise
+    return wrapper
+
+
+class Process(object):
+    """Wrapper class around underlying C implementation."""
+
+    __slots__ = ["pid", "_name", "_ppid", "_procfs_path"]
+
+    def __init__(self, pid):
+        self.pid = pid
+        self._name = None
+        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):
+        if self.pid == 0:
+            return "swapper"
+        # note: this is limited to 15 characters
+        return self._proc_name_and_args()[0].rstrip("\x00")
+
+    @wrap_exceptions
+    def exe(self):
+        # there is no way to get executable path in AIX other than to guess,
+        # and guessing is more complex than what's in the wrapping class
+        exe = self.cmdline()[0]
+        if os.path.sep in exe:
+            # relative or absolute path
+            if not os.path.isabs(exe):
+                # if cwd has changed, we're out of luck - this may be wrong!
+                exe = os.path.abspath(os.path.join(self.cwd(), exe))
+            if (os.path.isabs(exe) and
+               os.path.isfile(exe) and
+               os.access(exe, os.X_OK)):
+                return exe
+            # not found, move to search in PATH using basename only
+            exe = os.path.basename(exe)
+        # search for exe name PATH
+        for path in os.environ["PATH"].split(":"):
+            possible_exe = os.path.abspath(os.path.join(path, exe))
+            if (os.path.isfile(possible_exe) and
+               os.access(possible_exe, os.X_OK)):
+                return possible_exe
+        return ''
+
+    @wrap_exceptions
+    def cmdline(self):
+        return self._proc_name_and_args()[1].split(' ')
+
+    @wrap_exceptions
+    def create_time(self):
+        return self._proc_basic_info()[proc_info_map['create_time']]
+
+    @wrap_exceptions
+    def num_threads(self):
+        return self._proc_basic_info()[proc_info_map['num_threads']]
+
+    if HAS_THREADS:
+        @wrap_exceptions
+        def threads(self):
+            rawlist = cext.proc_threads(self.pid)
+            retlist = []
+            for thread_id, utime, stime in rawlist:
+                ntuple = _common.pthread(thread_id, utime, stime)
+                retlist.append(ntuple)
+            # The underlying C implementation retrieves all OS threads
+            # and filters them by PID.  At this point we can't tell whether
+            # an empty list means there were no connections for process or
+            # process is no longer active so we force NSP in case the PID
+            # is no longer there.
+            if not retlist:
+                # will raise NSP if process is gone
+                os.stat('%s/%s' % (self._procfs_path, self.pid))
+            return retlist
+
+    @wrap_exceptions
+    def connections(self, kind='inet'):
+        ret = net_connections(kind, _pid=self.pid)
+        # The underlying C implementation retrieves all OS connections
+        # and filters them by PID.  At this point we can't tell whether
+        # an empty list means there were no connections for process or
+        # process is no longer active so we force NSP in case the PID
+        # is no longer there.
+        if not ret:
+            # will raise NSP if process is gone
+            os.stat('%s/%s' % (self._procfs_path, self.pid))
+        return ret
+
+    @wrap_exceptions
+    def nice_get(self):
+        return cext_posix.getpriority(self.pid)
+
+    @wrap_exceptions
+    def nice_set(self, value):
+        return cext_posix.setpriority(self.pid, value)
+
+    @wrap_exceptions
+    def ppid(self):
+        self._ppid = self._proc_basic_info()[proc_info_map['ppid']]
+        return self._ppid
+
+    @wrap_exceptions
+    def uids(self):
+        real, effective, saved, _, _, _ = self._proc_cred()
+        return _common.puids(real, effective, saved)
+
+    @wrap_exceptions
+    def gids(self):
+        _, _, _, real, effective, saved = self._proc_cred()
+        return _common.puids(real, effective, saved)
+
+    @wrap_exceptions
+    def cpu_times(self):
+        cpu_times = cext.proc_cpu_times(self.pid, self._procfs_path)
+        return _common.pcputimes(*cpu_times)
+
+    @wrap_exceptions
+    def terminal(self):
+        ttydev = self._proc_basic_info()[proc_info_map['ttynr']]
+        # convert from 64-bit dev_t to 32-bit dev_t and then map the device
+        ttydev = (((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF))
+        # try to match rdev of /dev/pts/* files ttydev
+        for dev in glob.glob("/dev/**/*"):
+            if os.stat(dev).st_rdev == ttydev:
+                return dev
+        return None
+
+    @wrap_exceptions
+    def cwd(self):
+        procfs_path = self._procfs_path
+        try:
+            result = os.readlink("%s/%s/cwd" % (procfs_path, self.pid))
+            return result.rstrip('/')
+        except OSError as err:
+            if err.errno == errno.ENOENT:
+                os.stat("%s/%s" % (procfs_path, self.pid))  # raise NSP or AD
+                return None
+            raise
+
+    @wrap_exceptions
+    def memory_info(self):
+        ret = self._proc_basic_info()
+        rss = ret[proc_info_map['rss']] * 1024
+        vms = ret[proc_info_map['vms']] * 1024
+        return pmem(rss, vms)
+
+    memory_full_info = memory_info
+
+    @wrap_exceptions
+    def status(self):
+        code = self._proc_basic_info()[proc_info_map['status']]
+        # XXX is '?' legit? (we're not supposed to return it anyway)
+        return PROC_STATUSES.get(code, '?')
+
+    def open_files(self):
+        # TODO rewrite without using procfiles (stat /proc/pid/fd/* and then
+        # find matching name of the inode)
+        p = subprocess.Popen(["/usr/bin/procfiles", "-n", str(self.pid)],
+                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        stdout, stderr = p.communicate()
+        if PY3:
+            stdout, stderr = [x.decode(sys.stdout.encoding)
+                              for x in (stdout, stderr)]
+        if "no such process" in stderr.lower():
+            raise NoSuchProcess(self.pid, self._name)
+        procfiles = re.findall("(\d+): S_IFREG.*\s*.*name:(.*)\n", stdout)
+        retlist = []
+        for fd, path in procfiles:
+            path = path.strip()
+            if path.startswith("//"):
+                path = path[1:]
+            if path.lower() == "cannot be retrieved":
+                continue
+            retlist.append(_common.popenfile(path, int(fd)))
+        return retlist
+
+    @wrap_exceptions
+    def num_fds(self):
+        if self.pid == 0:       # no /proc/0/fd
+            return 0
+        return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)))
+
+    @wrap_exceptions
+    def num_ctx_switches(self):
+        return _common.pctxsw(
+            *cext.proc_num_ctx_switches(self.pid))
+
+    @wrap_exceptions
+    def wait(self, timeout=None):
+        return _psposix.wait_pid(self.pid, timeout, self._name)
+
+    @wrap_exceptions
+    def io_counters(self):
+        try:
+            rc, wc, rb, wb = cext.proc_io_counters(self.pid)
+        except OSError:
+            # if process is terminated, proc_io_counters returns OSError
+            # instead of NSP
+            if not pid_exists(self.pid):
+                raise NoSuchProcess(self.pid, self._name)
+            raise
+        return _common.pio(rc, wc, rb, wb)
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/_psbsd.py
@@ -0,0 +1,873 @@
+# 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.
+
+"""FreeBSD, OpenBSD and NetBSD platforms implementation."""
+
+import contextlib
+import errno
+import functools
+import os
+import xml.etree.ElementTree as ET
+from collections import namedtuple
+from socket import AF_INET
+
+from . import _common
+from . import _psposix
+from . import _psutil_bsd as cext
+from . import _psutil_posix as cext_posix
+from ._common import AF_INET6
+from ._common import conn_tmap
+from ._common import FREEBSD
+from ._common import memoize
+from ._common import memoize_when_activated
+from ._common import NETBSD
+from ._common import OPENBSD
+from ._common import sockfam_to_enum
+from ._common import socktype_to_enum
+from ._common import usage_percent
+from ._compat import which
+from ._exceptions import AccessDenied
+from ._exceptions import NoSuchProcess
+from ._exceptions import ZombieProcess
+
+__extra__all__ = []
+
+
+# =====================================================================
+# --- globals
+# =====================================================================
+
+
+if FREEBSD:
+    PROC_STATUSES = {
+        cext.SIDL: _common.STATUS_IDLE,
+        cext.SRUN: _common.STATUS_RUNNING,
+        cext.SSLEEP: _common.STATUS_SLEEPING,
+        cext.SSTOP: _common.STATUS_STOPPED,
+        cext.SZOMB: _common.STATUS_ZOMBIE,
+        cext.SWAIT: _common.STATUS_WAITING,
+        cext.SLOCK: _common.STATUS_LOCKED,
+    }
+elif OPENBSD or NETBSD:
+    PROC_STATUSES = {
+        cext.SIDL: _common.STATUS_IDLE,
+        cext.SSLEEP: _common.STATUS_SLEEPING,
+        cext.SSTOP: _common.STATUS_STOPPED,
+        # According to /usr/include/sys/proc.h SZOMB is unused.
+        # test_zombie_process() shows that SDEAD is the right
+        # equivalent. Also it appears there's no equivalent of
+        # psutil.STATUS_DEAD. SDEAD really means STATUS_ZOMBIE.
+        # cext.SZOMB: _common.STATUS_ZOMBIE,
+        cext.SDEAD: _common.STATUS_ZOMBIE,
+        cext.SZOMB: _common.STATUS_ZOMBIE,
+        # From http://www.eecs.harvard.edu/~margo/cs161/videos/proc.h.txt
+        # OpenBSD has SRUN and SONPROC: SRUN indicates that a process
+        # is runnable but *not* yet running, i.e. is on a run queue.
+        # SONPROC indicates that the process is actually executing on
+        # a CPU, i.e. it is no longer on a run queue.
+        # As such we'll map SRUN to STATUS_WAKING and SONPROC to
+        # STATUS_RUNNING
+        cext.SRUN: _common.STATUS_WAKING,
+        cext.SONPROC: _common.STATUS_RUNNING,
+    }
+elif NETBSD:
+    PROC_STATUSES = {
+        cext.SIDL: _common.STATUS_IDLE,
+        cext.SACTIVE: _common.STATUS_RUNNING,
+        cext.SDYING: _common.STATUS_ZOMBIE,
+        cext.SSTOP: _common.STATUS_STOPPED,
+        cext.SZOMB: _common.STATUS_ZOMBIE,
+        cext.SDEAD: _common.STATUS_DEAD,
+        cext.SSUSPENDED: _common.STATUS_SUSPENDED,  # unique to NetBSD
+    }
+
+TCP_STATUSES = {
+    cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
+    cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
+    cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
+    cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
+    cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
+    cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
+    cext.TCPS_CLOSED: _common.CONN_CLOSE,
+    cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
+    cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
+    cext.TCPS_LISTEN: _common.CONN_LISTEN,
+    cext.TCPS_CLOSING: _common.CONN_CLOSING,
+    cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
+}
+
+if NETBSD:
+    PAGESIZE = os.sysconf("SC_PAGESIZE")
+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,
+    cpunum=23,
+    name=24,
+)
+
+
+# =====================================================================
+# --- named tuples
+# =====================================================================
+
+
+# psutil.virtual_memory()
+svmem = namedtuple(
+    'svmem', ['total', 'available', 'percent', 'used', 'free',
+              'active', 'inactive', 'buffers', 'cached', 'shared', 'wired'])
+# psutil.cpu_times()
+scputimes = namedtuple(
+    'scputimes', ['user', 'nice', 'system', 'idle', 'irq'])
+# psutil.Process.memory_info()
+pmem = namedtuple('pmem', ['rss', 'vms', 'text', 'data', 'stack'])
+# psutil.Process.memory_full_info()
+pfullmem = pmem
+# psutil.Process.cpu_times()
+pcputimes = namedtuple('pcputimes',
+                       ['user', 'system', 'children_user', 'children_system'])
+# psutil.Process.memory_maps(grouped=True)
+pmmap_grouped = namedtuple(
+    'pmmap_grouped', 'path rss, private, ref_count, shadow_count')
+# psutil.Process.memory_maps(grouped=False)
+pmmap_ext = namedtuple(
+    'pmmap_ext', 'addr, perms path rss, private, ref_count, shadow_count')
+# psutil.disk_io_counters()
+if FREEBSD:
+    sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
+                                     'read_bytes', 'write_bytes',
+                                     'read_time', 'write_time',
+                                     'busy_time'])
+else:
+    sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
+                                     'read_bytes', 'write_bytes'])
+
+
+# =====================================================================
+# --- memory
+# =====================================================================
+
+
+def virtual_memory():
+    """System virtual memory as a namedtuple."""
+    mem = cext.virtual_mem()
+    total, free, active, inactive, wired, cached, buffers, shared = mem
+    if NETBSD:
+        # On NetBSD buffers and shared mem is determined via /proc.
+        # The C ext set them to 0.
+        with open('/proc/meminfo', 'rb') as f:
+            for line in f:
+                if line.startswith(b'Buffers:'):
+                    buffers = int(line.split()[1]) * 1024
+                elif line.startswith(b'MemShared:'):
+                    shared = int(line.split()[1]) * 1024
+    avail = inactive + cached + free
+    used = active + wired + cached
+    percent = usage_percent((total - avail), total, _round=1)
+    return svmem(total, avail, percent, used, free,
+                 active, inactive, buffers, cached, shared, wired)
+
+
+def swap_memory():
+    """System swap memory as (total, used, free, sin, sout) namedtuple."""
+    total, used, free, sin, sout = cext.swap_mem()
+    percent = usage_percent(used, total, _round=1)
+    return _common.sswap(total, used, free, percent, sin, sout)
+
+
+# =====================================================================
+# --- CPU
+# =====================================================================
+
+
+def cpu_times():
+    """Return system per-CPU times as a namedtuple"""
+    user, nice, system, idle, irq = cext.cpu_times()
+    return scputimes(user, nice, system, idle, irq)
+
+
+if hasattr(cext, "per_cpu_times"):
+    def per_cpu_times():
+        """Return system CPU times as a namedtuple"""
+        ret = []
+        for cpu_t in cext.per_cpu_times():
+            user, nice, system, idle, irq = cpu_t
+            item = scputimes(user, nice, system, idle, irq)
+            ret.append(item)
+        return ret
+else:
+    # XXX
+    # Ok, this is very dirty.
+    # On FreeBSD < 8 we cannot gather per-cpu information, see:
+    # https://github.com/giampaolo/psutil/issues/226
+    # If num cpus > 1, on first call we return single cpu times to avoid a
+    # crash at psutil import time.
+    # Next calls will fail with NotImplementedError
+    def per_cpu_times():
+        """Return system CPU times as a namedtuple"""
+        if cpu_count_logical() == 1:
+            return [cpu_times()]
+        if per_cpu_times.__called__:
+            raise NotImplementedError("supported only starting from FreeBSD 8")
+        per_cpu_times.__called__ = True
+        return [cpu_times()]
+
+    per_cpu_times.__called__ = False
+
+
+def cpu_count_logical():
+    """Return the number of logical CPUs in the system."""
+    return cext.cpu_count_logical()
+
+
+if OPENBSD or NETBSD:
+    def cpu_count_physical():
+        # OpenBSD and NetBSD do not implement this.
+        return 1 if cpu_count_logical() == 1 else None
+else:
+    def cpu_count_physical():
+        """Return the number of physical CPUs in the system."""
+        # From the C module we'll get an XML string similar to this:
+        # http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html
+        # We may get None in case "sysctl kern.sched.topology_spec"
+        # is not supported on this BSD version, in which case we'll mimic
+        # os.cpu_count() and return None.
+        ret = None
+        s = cext.cpu_count_phys()
+        if s is not None:
+            # get rid of padding chars appended at the end of the string
+            index = s.rfind("</groups>")
+            if index != -1:
+                s = s[:index + 9]
+                root = ET.fromstring(s)
+                try:
+                    ret = len(root.findall('group/children/group/cpu')) or None
+                finally:
+                    # needed otherwise it will memleak
+                    root.clear()
+        if not ret:
+            # If logical CPUs are 1 it's obvious we'll have only 1
+            # physical CPU.
+            if cpu_count_logical() == 1:
+                return 1
+        return ret
+
+
+def cpu_stats():
+    """Return various CPU stats as a named tuple."""
+    if FREEBSD:
+        # Note: the C ext is returning some metrics we are not exposing:
+        # traps.
+        ctxsw, intrs, soft_intrs, syscalls, traps = cext.cpu_stats()
+    elif NETBSD:
+        # XXX
+        # Note about intrs: the C extension returns 0. intrs
+        # can be determined via /proc/stat; it has the same value as
+        # soft_intrs thought so the kernel is faking it (?).
+        #
+        # Note about syscalls: the C extension always sets it to 0 (?).
+        #
+        # Note: the C ext is returning some metrics we are not exposing:
+        # traps, faults and forks.
+        ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \
+            cext.cpu_stats()
+        with open('/proc/stat', 'rb') as f:
+            for line in f:
+                if line.startswith(b'intr'):
+                    intrs = int(line.split()[1])
+    elif OPENBSD:
+        # Note: the C ext is returning some metrics we are not exposing:
+        # traps, faults and forks.
+        ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \
+            cext.cpu_stats()
+    return _common.scpustats(ctxsw, intrs, soft_intrs, syscalls)
+
+
+# =====================================================================
+# --- disks
+# =====================================================================
+
+
+def disk_partitions(all=False):
+    """Return mounted disk partitions as a list of namedtuples.
+    'all' argument is ignored, see:
+    https://github.com/giampaolo/psutil/issues/906
+    """
+    retlist = []
+    partitions = cext.disk_partitions()
+    for partition in partitions:
+        device, mountpoint, fstype, opts = partition
+        ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+        retlist.append(ntuple)
+    return retlist
+
+
+disk_usage = _psposix.disk_usage
+disk_io_counters = cext.disk_io_counters
+
+
+# =====================================================================
+# --- network
+# =====================================================================
+
+
+net_io_counters = cext.net_io_counters
+net_if_addrs = cext_posix.net_if_addrs
+
+
+def net_if_stats():
+    """Get NIC stats (isup, duplex, speed, mtu)."""
+    names = net_io_counters().keys()
+    ret = {}
+    for name in names:
+        mtu = cext_posix.net_if_mtu(name)
+        isup = cext_posix.net_if_flags(name)
+        duplex, speed = cext_posix.net_if_duplex_speed(name)
+        if hasattr(_common, 'NicDuplex'):
+            duplex = _common.NicDuplex(duplex)
+        ret[name] = _common.snicstats(isup, duplex, speed, mtu)
+    return ret
+
+
+def net_connections(kind):
+    """System-wide network connections."""
+    if OPENBSD:
+        ret = []
+        for pid in pids():
+            try:
+                cons = Process(pid).connections(kind)
+            except (NoSuchProcess, ZombieProcess):
+                continue
+            else:
+                for conn in cons:
+                    conn = list(conn)
+                    conn.append(pid)
+                    ret.append(_common.sconn(*conn))
+        return ret
+
+    if kind not in _common.conn_tmap:
+        raise ValueError("invalid %r kind argument; choose between %s"
+                         % (kind, ', '.join([repr(x) for x in conn_tmap])))
+    families, types = conn_tmap[kind]
+    ret = set()
+    if NETBSD:
+        rawlist = cext.net_connections(-1)
+    else:
+        rawlist = cext.net_connections()
+    for item in rawlist:
+        fd, fam, type, laddr, raddr, status, pid = item
+        # TODO: apply filter at C level
+        if fam in families and type in types:
+            try:
+                status = TCP_STATUSES[status]
+            except KeyError:
+                # XXX: Not sure why this happens. I saw this occurring
+                # with IPv6 sockets opened by 'vim'. Those sockets
+                # have a very short lifetime so maybe the kernel
+                # can't initialize their status?
+                status = TCP_STATUSES[cext.PSUTIL_CONN_NONE]
+            if fam in (AF_INET, AF_INET6):
+                if laddr:
+                    laddr = _common.addr(*laddr)
+                if raddr:
+                    raddr = _common.addr(*raddr)
+            fam = sockfam_to_enum(fam)
+            type = socktype_to_enum(type)
+            nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid)
+            ret.add(nt)
+    return list(ret)
+
+
+# =====================================================================
+#  --- sensors
+# =====================================================================
+
+
+if FREEBSD:
+
+    def sensors_battery():
+        """Return battery info."""
+        try:
+            percent, minsleft, power_plugged = cext.sensors_battery()
+        except NotImplementedError:
+            # See: https://github.com/giampaolo/psutil/issues/1074
+            return None
+        power_plugged = power_plugged == 1
+        if power_plugged:
+            secsleft = _common.POWER_TIME_UNLIMITED
+        elif minsleft == -1:
+            secsleft = _common.POWER_TIME_UNKNOWN
+        else:
+            secsleft = minsleft * 60
+        return _common.sbattery(percent, secsleft, power_plugged)
+
+
+# =====================================================================
+#  --- other system functions
+# =====================================================================
+
+
+def boot_time():
+    """The system boot time expressed in seconds since the epoch."""
+    return cext.boot_time()
+
+
+def users():
+    """Return currently connected users as a list of namedtuples."""
+    retlist = []
+    rawlist = cext.users()
+    for item in rawlist:
+        user, tty, hostname, tstamp, pid = item
+        if pid == -1:
+            assert OPENBSD
+            pid = None
+        if tty == '~':
+            continue  # reboot or shutdown
+        nt = _common.suser(user, tty or None, hostname, tstamp, pid)
+        retlist.append(nt)
+    return retlist
+
+
+# =====================================================================
+# --- processes
+# =====================================================================
+
+
+@memoize
+def _pid_0_exists():
+    try:
+        Process(0).name()
+    except NoSuchProcess:
+        return False
+    except AccessDenied:
+        return True
+    else:
+        return True
+
+
+def pids():
+    """Returns a list of PIDs currently running on the system."""
+    ret = cext.pids()
+    if OPENBSD and (0 not in ret) and _pid_0_exists():
+        # On OpenBSD the kernel does not return PID 0 (neither does
+        # ps) but it's actually querable (Process(0) will succeed).
+        ret.insert(0, 0)
+    return ret
+
+
+if OPENBSD or NETBSD:
+    def pid_exists(pid):
+        """Return True if pid exists."""
+        exists = _psposix.pid_exists(pid)
+        if not exists:
+            # We do this because _psposix.pid_exists() lies in case of
+            # zombie processes.
+            return pid in pids()
+        else:
+            return True
+else:
+    pid_exists = _psposix.pid_exists
+
+
+def wrap_exceptions(fun):
+    """Decorator which translates bare OSError exceptions into
+    NoSuchProcess and AccessDenied.
+    """
+    @functools.wraps(fun)
+    def wrapper(self, *args, **kwargs):
+        try:
+            return fun(self, *args, **kwargs)
+        except OSError as err:
+            if self.pid == 0:
+                if 0 in pids():
+                    raise AccessDenied(self.pid, self._name)
+                else:
+                    raise
+            if err.errno == errno.ESRCH:
+                if not pid_exists(self.pid):
+                    raise NoSuchProcess(self.pid, self._name)
+                else:
+                    raise ZombieProcess(self.pid, self._name, self._ppid)
+            if err.errno in (errno.EPERM, errno.EACCES):
+                raise AccessDenied(self.pid, self._name)
+            raise
+    return wrapper
+
+
+@contextlib.contextmanager
+def wrap_exceptions_procfs(inst):
+    """Same as above, for routines relying on reading /proc fs."""
+    try:
+        yield
+    except EnvironmentError as err:
+        # ENOENT (no such file or directory) gets raised on open().
+        # ESRCH (no such process) can get raised on read() if
+        # process is gone in meantime.
+        if err.errno in (errno.ENOENT, errno.ESRCH):
+            if not pid_exists(inst.pid):
+                raise NoSuchProcess(inst.pid, inst._name)
+            else:
+                raise ZombieProcess(inst.pid, inst._name, inst._ppid)
+        if err.errno in (errno.EPERM, errno.EACCES):
+            raise AccessDenied(inst.pid, inst._name)
+        raise
+
+
+class Process(object):
+    """Wrapper class around underlying C implementation."""
+
+    __slots__ = ["pid", "_name", "_ppid"]
+
+    def __init__(self, pid):
+        self.pid = pid
+        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):
+        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):
+        if FREEBSD:
+            return cext.proc_exe(self.pid)
+        elif NETBSD:
+            if self.pid == 0:
+                # /proc/0 dir exists but /proc/0/exe doesn't
+                return ""
+            with wrap_exceptions_procfs(self):
+                return os.readlink("/proc/%s/exe" % self.pid)
+        else:
+            # OpenBSD: exe cannot be determined; references:
+            # https://chromium.googlesource.com/chromium/src/base/+/
+            #     master/base_paths_posix.cc
+            # We try our best guess by using which against the first
+            # cmdline arg (may return None).
+            cmdline = self.cmdline()
+            if cmdline:
+                return which(cmdline[0])
+            else:
+                return ""
+
+    @wrap_exceptions
+    def cmdline(self):
+        if OPENBSD and self.pid == 0:
+            return []  # ...else it crashes
+        elif NETBSD:
+            # XXX - most of the times the underlying sysctl() call on Net
+            # and Open BSD returns a truncated string.
+            # Also /proc/pid/cmdline behaves the same so it looks
+            # like this is a kernel bug.
+            try:
+                return cext.proc_cmdline(self.pid)
+            except OSError as err:
+                if err.errno == errno.EINVAL:
+                    if not pid_exists(self.pid):
+                        raise NoSuchProcess(self.pid, self._name)
+                    else:
+                        raise ZombieProcess(self.pid, self._name, self._ppid)
+                else:
+                    raise
+        else:
+            return cext.proc_cmdline(self.pid)
+
+    @wrap_exceptions
+    def terminal(self):
+        tty_nr = self.oneshot()[kinfo_proc_map['ttynr']]
+        tmap = _psposix.get_terminal_map()
+        try:
+            return tmap[tty_nr]
+        except KeyError:
+            return None
+
+    @wrap_exceptions
+    def ppid(self):
+        self._ppid = self.oneshot()[kinfo_proc_map['ppid']]
+        return self._ppid
+
+    @wrap_exceptions
+    def uids(self):
+        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):
+        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):
+        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']])
+
+    if FREEBSD:
+        @wrap_exceptions
+        def cpu_num(self):
+            return self.oneshot()[kinfo_proc_map['cpunum']]
+
+    @wrap_exceptions
+    def memory_info(self):
+        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 self.oneshot()[kinfo_proc_map['create_time']]
+
+    @wrap_exceptions
+    def num_threads(self):
+        if hasattr(cext, "proc_num_threads"):
+            # FreeBSD
+            return cext.proc_num_threads(self.pid)
+        else:
+            return len(self.threads())
+
+    @wrap_exceptions
+    def num_ctx_switches(self):
+        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):
+        # Note: on OpenSBD this (/dev/mem) requires root access.
+        rawlist = cext.proc_threads(self.pid)
+        retlist = []
+        for thread_id, utime, stime in rawlist:
+            ntuple = _common.pthread(thread_id, utime, stime)
+            retlist.append(ntuple)
+        if OPENBSD:
+            # On OpenBSD the underlying C function does not raise NSP
+            # in case the process is gone (and the returned list may
+            # incomplete).
+            self.name()  # raise NSP if the process disappeared on us
+        return retlist
+
+    @wrap_exceptions
+    def connections(self, kind='inet'):
+        if kind not in conn_tmap:
+            raise ValueError("invalid %r kind argument; choose between %s"
+                             % (kind, ', '.join([repr(x) for x in conn_tmap])))
+
+        if NETBSD:
+            families, types = conn_tmap[kind]
+            ret = set()
+            rawlist = cext.net_connections(self.pid)
+            for item in rawlist:
+                fd, fam, type, laddr, raddr, status, pid = item
+                assert pid == self.pid
+                if fam in families and type in types:
+                    try:
+                        status = TCP_STATUSES[status]
+                    except KeyError:
+                        status = TCP_STATUSES[cext.PSUTIL_CONN_NONE]
+                    if fam in (AF_INET, AF_INET6):
+                        if laddr:
+                            laddr = _common.addr(*laddr)
+                        if raddr:
+                            raddr = _common.addr(*raddr)
+                    fam = sockfam_to_enum(fam)
+                    type = socktype_to_enum(type)
+                    nt = _common.pconn(fd, fam, type, laddr, raddr, status)
+                    ret.add(nt)
+            # On NetBSD the underlying C function does not raise NSP
+            # in case the process is gone (and the returned list may
+            # incomplete).
+            self.name()  # raise NSP if the process disappeared on us
+            return list(ret)
+
+        families, types = conn_tmap[kind]
+        rawlist = cext.proc_connections(self.pid, families, types)
+        ret = []
+        for item in rawlist:
+            fd, fam, type, laddr, raddr, status = item
+            if fam in (AF_INET, AF_INET6):
+                if laddr:
+                    laddr = _common.addr(*laddr)
+                if raddr:
+                    raddr = _common.addr(*raddr)
+            fam = sockfam_to_enum(fam)
+            type = socktype_to_enum(type)
+            status = TCP_STATUSES[status]
+            nt = _common.pconn(fd, fam, type, laddr, raddr, status)
+            ret.append(nt)
+        if OPENBSD:
+            # On OpenBSD the underlying C function does not raise NSP
+            # in case the process is gone (and the returned list may
+            # incomplete).
+            self.name()  # raise NSP if the process disappeared on us
+        return ret
+
+    @wrap_exceptions
+    def wait(self, timeout=None):
+        return _psposix.wait_pid(self.pid, timeout, self._name)
+
+    @wrap_exceptions
+    def nice_get(self):
+        return cext_posix.getpriority(self.pid)
+
+    @wrap_exceptions
+    def nice_set(self, value):
+        return cext_posix.setpriority(self.pid, value)
+
+    @wrap_exceptions
+    def status(self):
+        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):
+        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):
+        """Return process current working directory."""
+        # sometimes we get an empty string, in which case we turn
+        # it into None
+        if OPENBSD and self.pid == 0:
+            return None  # ...else it would raise EINVAL
+        elif NETBSD:
+            with wrap_exceptions_procfs(self):
+                return os.readlink("/proc/%s/cwd" % self.pid)
+        elif hasattr(cext, 'proc_open_files'):
+            # FreeBSD < 8 does not support functions based on
+            # kinfo_getfile() and kinfo_getvmmap()
+            return cext.proc_cwd(self.pid) or None
+        else:
+            raise NotImplementedError(
+                "supported only starting from FreeBSD 8" if
+                FREEBSD else "")
+
+    nt_mmap_grouped = namedtuple(
+        'mmap', 'path rss, private, ref_count, shadow_count')
+    nt_mmap_ext = namedtuple(
+        'mmap', 'addr, perms path rss, private, ref_count, shadow_count')
+
+    def _not_implemented(self):
+        raise NotImplementedError
+
+    # FreeBSD < 8 does not support functions based on kinfo_getfile()
+    # and kinfo_getvmmap()
+    if hasattr(cext, 'proc_open_files'):
+        @wrap_exceptions
+        def open_files(self):
+            """Return files opened by process as a list of namedtuples."""
+            rawlist = cext.proc_open_files(self.pid)
+            return [_common.popenfile(path, fd) for path, fd in rawlist]
+    else:
+        open_files = _not_implemented
+
+    # FreeBSD < 8 does not support functions based on kinfo_getfile()
+    # and kinfo_getvmmap()
+    if hasattr(cext, 'proc_num_fds'):
+        @wrap_exceptions
+        def num_fds(self):
+            """Return the number of file descriptors opened by this process."""
+            ret = cext.proc_num_fds(self.pid)
+            if NETBSD:
+                # On NetBSD the underlying C function does not raise NSP
+                # in case the process is gone.
+                self.name()  # raise NSP if the process disappeared on us
+            return ret
+    else:
+        num_fds = _not_implemented
+
+    # --- FreeBSD only APIs
+
+    if FREEBSD:
+
+        @wrap_exceptions
+        def cpu_affinity_get(self):
+            return cext.proc_cpu_affinity_get(self.pid)
+
+        @wrap_exceptions
+        def cpu_affinity_set(self, cpus):
+            # Pre-emptively check if CPUs are valid because the C
+            # function has a weird behavior in case of invalid CPUs,
+            # see: https://github.com/giampaolo/psutil/issues/586
+            allcpus = tuple(range(len(per_cpu_times())))
+            for cpu in cpus:
+                if cpu not in allcpus:
+                    raise ValueError("invalid CPU #%i (choose between %s)"
+                                     % (cpu, allcpus))
+            try:
+                cext.proc_cpu_affinity_set(self.pid, cpus)
+            except OSError as err:
+                # 'man cpuset_setaffinity' about EDEADLK:
+                # <<the call would leave a thread without a valid CPU to run
+                # on because the set does not overlap with the thread's
+                # anonymous mask>>
+                if err.errno in (errno.EINVAL, errno.EDEADLK):
+                    for cpu in cpus:
+                        if cpu not in allcpus:
+                            raise ValueError(
+                                "invalid CPU #%i (choose between %s)" % (
+                                    cpu, allcpus))
+                raise
+
+        @wrap_exceptions
+        def memory_maps(self):
+            return cext.proc_memory_maps(self.pid)
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/_pslinux.py
@@ -0,0 +1,1973 @@
+# 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.
+
+"""Linux platform implementation."""
+
+from __future__ import division
+
+import base64
+import collections
+import errno
+import functools
+import glob
+import os
+import re
+import socket
+import struct
+import sys
+import traceback
+import warnings
+from collections import defaultdict
+from collections import namedtuple
+
+from . import _common
+from . import _psposix
+from . import _psutil_linux as cext
+from . import _psutil_posix as cext_posix
+from ._common import ENCODING
+from ._common import ENCODING_ERRS
+from ._common import isfile_strict
+from ._common import memoize
+from ._common import memoize_when_activated
+from ._common import NIC_DUPLEX_FULL
+from ._common import NIC_DUPLEX_HALF
+from ._common import NIC_DUPLEX_UNKNOWN
+from ._common import parse_environ_block
+from ._common import path_exists_strict
+from ._common import supports_ipv6
+from ._common import usage_percent
+from ._compat import b
+from ._compat import basestring
+from ._compat import long
+from ._compat import PY3
+from ._exceptions import AccessDenied
+from ._exceptions import NoSuchProcess
+from ._exceptions import ZombieProcess
+
+if sys.version_info >= (3, 4):
+    import enum
+else:
+    enum = None
+
+
+__extra__all__ = [
+    #
+    'PROCFS_PATH',
+    # io prio constants
+    "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE",
+    "IOPRIO_CLASS_IDLE",
+    # connection status constants
+    "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
+    "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
+    "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ]
+
+
+# =====================================================================
+# --- globals
+# =====================================================================
+
+
+POWER_SUPPLY_PATH = "/sys/class/power_supply"
+HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
+HAS_PRLIMIT = hasattr(cext, "linux_prlimit")
+_DEFAULT = object()
+
+# RLIMIT_* constants, not guaranteed to be present on all kernels
+if HAS_PRLIMIT:
+    for name in dir(cext):
+        if name.startswith('RLIM'):
+            __extra__all__.append(name)
+
+# Number of clock ticks per second
+CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
+PAGESIZE = os.sysconf("SC_PAGE_SIZE")
+BOOT_TIME = None  # set later
+# Used when reading "big" files, namely /proc/{pid}/smaps and /proc/net/*.
+# On Python 2, using a buffer with open() for such files may result in a
+# speedup, see: https://github.com/giampaolo/psutil/issues/708
+BIGFILE_BUFFERING = -1 if PY3 else 8192
+LITTLE_ENDIAN = sys.byteorder == 'little'
+SECTOR_SIZE_FALLBACK = 512
+if enum is None:
+    AF_LINK = socket.AF_PACKET
+else:
+    AddressFamily = enum.IntEnum('AddressFamily',
+                                 {'AF_LINK': int(socket.AF_PACKET)})
+    AF_LINK = AddressFamily.AF_LINK
+
+# ioprio_* constants http://linux.die.net/man/2/ioprio_get
+if enum is None:
+    IOPRIO_CLASS_NONE = 0
+    IOPRIO_CLASS_RT = 1
+    IOPRIO_CLASS_BE = 2
+    IOPRIO_CLASS_IDLE = 3
+else:
+    class IOPriority(enum.IntEnum):
+        IOPRIO_CLASS_NONE = 0
+        IOPRIO_CLASS_RT = 1
+        IOPRIO_CLASS_BE = 2
+        IOPRIO_CLASS_IDLE = 3
+
+    globals().update(IOPriority.__members__)
+
+# taken from /fs/proc/array.c
+PROC_STATUSES = {
+    "R": _common.STATUS_RUNNING,
+    "S": _common.STATUS_SLEEPING,
+    "D": _common.STATUS_DISK_SLEEP,
+    "T": _common.STATUS_STOPPED,
+    "t": _common.STATUS_TRACING_STOP,
+    "Z": _common.STATUS_ZOMBIE,
+    "X": _common.STATUS_DEAD,
+    "x": _common.STATUS_DEAD,
+    "K": _common.STATUS_WAKE_KILL,
+    "W": _common.STATUS_WAKING
+}
+
+# https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h
+TCP_STATUSES = {
+    "01": _common.CONN_ESTABLISHED,
+    "02": _common.CONN_SYN_SENT,
+    "03": _common.CONN_SYN_RECV,
+    "04": _common.CONN_FIN_WAIT1,
+    "05": _common.CONN_FIN_WAIT2,
+    "06": _common.CONN_TIME_WAIT,
+    "07": _common.CONN_CLOSE,
+    "08": _common.CONN_CLOSE_WAIT,
+    "09": _common.CONN_LAST_ACK,
+    "0A": _common.CONN_LISTEN,
+    "0B": _common.CONN_CLOSING
+}
+
+
+# =====================================================================
+# --- named tuples
+# =====================================================================
+
+
+# psutil.virtual_memory()
+svmem = namedtuple(
+    'svmem', ['total', 'available', 'percent', 'used', 'free',
+              'active', 'inactive', 'buffers', 'cached', 'shared'])
+# psutil.disk_io_counters()
+sdiskio = namedtuple(
+    'sdiskio', ['read_count', 'write_count',
+                'read_bytes', 'write_bytes',
+                'read_time', 'write_time',
+                'read_merged_count', 'write_merged_count',
+                'busy_time'])
+# psutil.Process().open_files()
+popenfile = namedtuple(
+    'popenfile', ['path', 'fd', 'position', 'mode', 'flags'])
+# psutil.Process().memory_info()
+pmem = namedtuple('pmem', 'rss vms shared text lib data dirty')
+# psutil.Process().memory_full_info()
+pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', 'pss', 'swap'))
+# psutil.Process().memory_maps(grouped=True)
+pmmap_grouped = namedtuple(
+    'pmmap_grouped',
+    ['path', 'rss', 'size', 'pss', 'shared_clean', 'shared_dirty',
+     'private_clean', 'private_dirty', 'referenced', 'anonymous', 'swap'])
+# psutil.Process().memory_maps(grouped=False)
+pmmap_ext = namedtuple(
+    'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
+# psutil.Process.io_counters()
+pio = namedtuple('pio', ['read_count', 'write_count',
+                         'read_bytes', 'write_bytes',
+                         'read_chars', 'write_chars'])
+
+
+# =====================================================================
+# --- utils
+# =====================================================================
+
+
+def open_binary(fname, **kwargs):
+    return open(fname, "rb", **kwargs)
+
+
+def open_text(fname, **kwargs):
+    """On Python 3 opens a file in text mode by using fs encoding and
+    a proper en/decoding errors handler.
+    On Python 2 this is just an alias for open(name, 'rt').
+    """
+    if PY3:
+        # See:
+        # https://github.com/giampaolo/psutil/issues/675
+        # https://github.com/giampaolo/psutil/pull/733
+        kwargs.setdefault('encoding', ENCODING)
+        kwargs.setdefault('errors', ENCODING_ERRS)
+    return open(fname, "rt", **kwargs)
+
+
+if PY3:
+    def decode(s):
+        return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
+else:
+    def decode(s):
+        return s
+
+
+def get_procfs_path():
+    """Return updated psutil.PROCFS_PATH constant."""
+    return sys.modules['psutil'].PROCFS_PATH
+
+
+def readlink(path):
+    """Wrapper around os.readlink()."""
+    assert isinstance(path, basestring), path
+    path = os.readlink(path)
+    # readlink() might return paths containing null bytes ('\x00')
+    # resulting in "TypeError: must be encoded string without NULL
+    # bytes, not str" errors when the string is passed to other
+    # fs-related functions (os.*, open(), ...).
+    # Apparently everything after '\x00' is garbage (we can have
+    # ' (deleted)', 'new' and possibly others), see:
+    # https://github.com/giampaolo/psutil/issues/717
+    path = path.split('\x00')[0]
+    # Certain paths have ' (deleted)' appended. Usually this is
+    # bogus as the file actually exists. Even if it doesn't we
+    # don't care.
+    if path.endswith(' (deleted)') and not path_exists_strict(path):
+        path = path[:-10]
+    return path
+
+
+def file_flags_to_mode(flags):
+    """Convert file's open() flags into a readable string.
+    Used by Process.open_files().
+    """
+    modes_map = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
+    mode = modes_map[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
+    if flags & os.O_APPEND:
+        mode = mode.replace('w', 'a', 1)
+    mode = mode.replace('w+', 'r+')
+    # possible values: r, w, a, r+, a+
+    return mode
+
+
+def get_sector_size(partition):
+    """Return the sector size of a partition.
+    Used by disk_io_counters().
+    """
+    try:
+        with open("/sys/block/%s/queue/hw_sector_size" % partition, "rt") as f:
+            return int(f.read())
+    except (IOError, ValueError):
+        # man iostat states that sectors are equivalent with blocks and
+        # have a size of 512 bytes since 2.4 kernels.
+        return SECTOR_SIZE_FALLBACK
+
+
+@memoize
+def set_scputimes_ntuple(procfs_path):
+    """Set a namedtuple of variable fields depending on the CPU times
+    available on this Linux kernel version which may be:
+    (user, nice, system, idle, iowait, irq, softirq, [steal, [guest,
+     [guest_nice]]])
+    Used by cpu_times() function.
+    """
+    global scputimes
+    with open_binary('%s/stat' % procfs_path) as f:
+        values = f.readline().split()[1:]
+    fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq']
+    vlen = len(values)
+    if vlen >= 8:
+        # Linux >= 2.6.11
+        fields.append('steal')
+    if vlen >= 9:
+        # Linux >= 2.6.24
+        fields.append('guest')
+    if vlen >= 10:
+        # Linux >= 3.2.0
+        fields.append('guest_nice')
+    scputimes = namedtuple('scputimes', fields)
+
+
+def cat(fname, fallback=_DEFAULT, binary=True):
+    """Return file content.
+    fallback: the value returned in case the file does not exist or
+              cannot be read
+    binary: whether to open the file in binary or text mode.
+    """
+    try:
+        with open_binary(fname) if binary else open_text(fname) as f:
+            return f.read().strip()
+    except IOError:
+        if fallback is not _DEFAULT:
+            return fallback
+        else:
+            raise
+
+
+try:
+    set_scputimes_ntuple("/proc")
+except Exception:
+    # Don't want to crash at import time.
+    traceback.print_exc()
+    scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0)
+
+
+# =====================================================================
+# --- system memory
+# =====================================================================
+
+
+def calculate_avail_vmem(mems):
+    """Fallback for kernels < 3.14 where /proc/meminfo does not provide
+    "MemAvailable:" column, see:
+    https://blog.famzah.net/2014/09/24/
+    This code reimplements the algorithm outlined here:
+    https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
+        commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
+
+    XXX: on recent kernels this calculation differs by ~1.5% than
+    "MemAvailable:" as it's calculated slightly differently, see:
+    https://gitlab.com/procps-ng/procps/issues/42
+    https://github.com/famzah/linux-memavailable-procfs/issues/2
+    It is still way more realistic than doing (free + cached) though.
+    """
+    # Fallback for very old distros. According to
+    # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
+    #     commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
+    # ...long ago "avail" was calculated as (free + cached).
+    # We might fallback in such cases:
+    # "Active(file)" not available: 2.6.28 / Dec 2008
+    # "Inactive(file)" not available: 2.6.28 / Dec 2008
+    # "SReclaimable:" not available: 2.6.19 / Nov 2006
+    # /proc/zoneinfo not available: 2.6.13 / Aug 2005
+    free = mems[b'MemFree:']
+    fallback = free + mems.get(b"Cached:", 0)
+    try:
+        lru_active_file = mems[b'Active(file):']
+        lru_inactive_file = mems[b'Inactive(file):']
+        slab_reclaimable = mems[b'SReclaimable:']
+    except KeyError:
+        return fallback
+    try:
+        f = open_binary('%s/zoneinfo' % get_procfs_path())
+    except IOError:
+        return fallback  # kernel 2.6.13
+
+    watermark_low = 0
+    with f:
+        for line in f:
+            line = line.strip()
+            if line.startswith(b'low'):
+                watermark_low += int(line.split()[1])
+    watermark_low *= PAGESIZE
+    watermark_low = watermark_low
+
+    avail = free - watermark_low
+    pagecache = lru_active_file + lru_inactive_file
+    pagecache -= min(pagecache / 2, watermark_low)
+    avail += pagecache
+    avail += slab_reclaimable - min(slab_reclaimable / 2.0, watermark_low)
+    return int(avail)
+
+
+def virtual_memory():
+    """Report virtual memory stats.
+    This implementation matches "free" and "vmstat -s" cmdline
+    utility values and procps-ng-3.3.12 source was used as a reference
+    (2016-09-18):
+    https://gitlab.com/procps-ng/procps/blob/
+        24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c
+    For reference, procps-ng-3.3.10 is the version available on Ubuntu
+    16.04.
+
+    Note about "available" memory: up until psutil 4.3 it was
+    calculated as "avail = (free + buffers + cached)". Now
+    "MemAvailable:" column (kernel 3.14) from /proc/meminfo is used as
+    it's more accurate.
+    That matches "available" column in newer versions of "free".
+    """
+    missing_fields = []
+    mems = {}
+    with open_binary('%s/meminfo' % get_procfs_path()) as f:
+        for line in f:
+            fields = line.split()
+            mems[fields[0]] = int(fields[1]) * 1024
+
+    # /proc doc states that the available fields in /proc/meminfo vary
+    # by architecture and compile options, but these 3 values are also
+    # returned by sysinfo(2); as such we assume they are always there.
+    total = mems[b'MemTotal:']
+    free = mems[b'MemFree:']
+    try:
+        buffers = mems[b'Buffers:']
+    except KeyError:
+        # https://github.com/giampaolo/psutil/issues/1010
+        buffers = 0
+        missing_fields.append('buffers')
+    try:
+        cached = mems[b"Cached:"]
+    except KeyError:
+        cached = 0
+        missing_fields.append('cached')
+    else:
+        # "free" cmdline utility sums reclaimable to cached.
+        # Older versions of procps used to add slab memory instead.
+        # This got changed in:
+        # https://gitlab.com/procps-ng/procps/commit/
+        #     05d751c4f076a2f0118b914c5e51cfbb4762ad8e
+        cached += mems.get(b"SReclaimable:", 0)  # since kernel 2.6.19
+
+    try:
+        shared = mems[b'Shmem:']  # since kernel 2.6.32
+    except KeyError:
+        try:
+            shared = mems[b'MemShared:']  # kernels 2.4
+        except KeyError:
+            shared = 0
+            missing_fields.append('shared')
+
+    try:
+        active = mems[b"Active:"]
+    except KeyError:
+        active = 0
+        missing_fields.append('active')
+
+    try:
+        inactive = mems[b"Inactive:"]
+    except KeyError:
+        try:
+            inactive = \
+                mems[b"Inact_dirty:"] + \
+                mems[b"Inact_clean:"] + \
+                mems[b"Inact_laundry:"]
+        except KeyError:
+            inactive = 0
+            missing_fields.append('inactive')
+
+    used = total - free - cached - buffers
+    if used < 0:
+        # May be symptomatic of running within a LCX container where such
+        # values will be dramatically distorted over those of the host.
+        used = total - free
+
+    # - starting from 4.4.0 we match free's "available" column.
+    #   Before 4.4.0 we calculated it as (free + buffers + cached)
+    #   which matched htop.
+    # - free and htop available memory differs as per:
+    #   http://askubuntu.com/a/369589
+    #   http://unix.stackexchange.com/a/65852/168884
+    # - MemAvailable has been introduced in kernel 3.14
+    try:
+        avail = mems[b'MemAvailable:']
+    except KeyError:
+        avail = calculate_avail_vmem(mems)
+
+    if avail < 0:
+        avail = 0
+        missing_fields.append('available')
+
+    # If avail is greater than total or our calculation overflows,
+    # that's symptomatic of running within a LCX container where such
+    # values will be dramatically distorted over those of the host.
+    # https://gitlab.com/procps-ng/procps/blob/
+    #     24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764
+    if avail > total:
+        avail = free
+
+    percent = usage_percent((total - avail), total, _round=1)
+
+    # Warn about missing metrics which are set to 0.
+    if missing_fields:
+        msg = "%s memory stats couldn't be determined and %s set to 0" % (
+            ", ".join(missing_fields),
+            "was" if len(missing_fields) == 1 else "were")
+        warnings.warn(msg, RuntimeWarning)
+
+    return svmem(total, avail, percent, used, free,
+                 active, inactive, buffers, cached, shared)
+
+
+def swap_memory():
+    """Return swap memory metrics."""
+    mems = {}
+    with open_binary('%s/meminfo' % get_procfs_path()) as f:
+        for line in f:
+            fields = line.split()
+            mems[fields[0]] = int(fields[1]) * 1024
+    # We prefer /proc/meminfo over sysinfo() syscall so that
+    # psutil.PROCFS_PATH can be used in order to allow retrieval
+    # for linux containers, see:
+    # https://github.com/giampaolo/psutil/issues/1015
+    try:
+        total = mems[b'SwapTotal:']
+        free = mems[b'SwapFree:']
+    except KeyError:
+        _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo()
+        total *= unit_multiplier
+        free *= unit_multiplier
+
+    used = total - free
+    percent = usage_percent(used, total, _round=1)
+    # get pgin/pgouts
+    try:
+        f = open_binary("%s/vmstat" % get_procfs_path())
+    except IOError as err:
+        # see https://github.com/giampaolo/psutil/issues/722
+        msg = "'sin' and 'sout' swap memory stats couldn't " \
+              "be determined and were set to 0 (%s)" % str(err)
+        warnings.warn(msg, RuntimeWarning)
+        sin = sout = 0
+    else:
+        with f:
+            sin = sout = None
+            for line in f:
+                # values are expressed in 4 kilo bytes, we want
+                # bytes instead
+                if line.startswith(b'pswpin'):
+                    sin = int(line.split(b' ')[1]) * 4 * 1024
+                elif line.startswith(b'pswpout'):
+                    sout = int(line.split(b' ')[1]) * 4 * 1024
+                if sin is not None and sout is not None:
+                    break
+            else:
+                # we might get here when dealing with exotic Linux
+                # flavors, see:
+                # https://github.com/giampaolo/psutil/issues/313
+                msg = "'sin' and 'sout' swap memory stats couldn't " \
+                      "be determined and were set to 0"
+                warnings.warn(msg, RuntimeWarning)
+                sin = sout = 0
+    return _common.sswap(total, used, free, percent, sin, sout)
+
+
+# =====================================================================
+# --- CPU
+# =====================================================================
+
+
+def cpu_times():
+    """Return a named tuple representing the following system-wide
+    CPU times:
+    (user, nice, system, idle, iowait, irq, softirq [steal, [guest,
+     [guest_nice]]])
+    Last 3 fields may not be available on all Linux kernel versions.
+    """
+    procfs_path = get_procfs_path()
+    set_scputimes_ntuple(procfs_path)
+    with open_binary('%s/stat' % procfs_path) as f:
+        values = f.readline().split()
+    fields = values[1:len(scputimes._fields) + 1]
+    fields = [float(x) / CLOCK_TICKS for x in fields]
+    return scputimes(*fields)
+
+
+def per_cpu_times():
+    """Return a list of namedtuple representing the CPU times
+    for every CPU available on the system.
+    """
+    procfs_path = get_procfs_path()
+    set_scputimes_ntuple(procfs_path)
+    cpus = []
+    with open_binary('%s/stat' % procfs_path) as f:
+        # get rid of the first line which refers to system wide CPU stats
+        f.readline()
+        for line in f:
+            if line.startswith(b'cpu'):
+                values = line.split()
+                fields = values[1:len(scputimes._fields) + 1]
+                fields = [float(x) / CLOCK_TICKS for x in fields]
+                entry = scputimes(*fields)
+                cpus.append(entry)
+        return cpus
+
+
+def cpu_count_logical():
+    """Return the number of logical CPUs in the system."""
+    try:
+        return os.sysconf("SC_NPROCESSORS_ONLN")
+    except ValueError:
+        # as a second fallback we try to parse /proc/cpuinfo
+        num = 0
+        with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
+            for line in f:
+                if line.lower().startswith(b'processor'):
+                    num += 1
+
+        # unknown format (e.g. amrel/sparc architectures), see:
+        # https://github.com/giampaolo/psutil/issues/200
+        # try to parse /proc/stat as a last resort
+        if num == 0:
+            search = re.compile(r'cpu\d')
+            with open_text('%s/stat' % get_procfs_path()) as f:
+                for line in f:
+                    line = line.split(' ')[0]
+                    if search.match(line):
+                        num += 1
+
+        if num == 0:
+            # mimic os.cpu_count()
+            return None
+        return num
+
+
+def cpu_count_physical():
+    """Return the number of physical cores in the system."""
+    mapping = {}
+    current_info = {}
+    with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
+        for line in f:
+            line = line.strip().lower()
+            if not line:
+                # new section
+                if (b'physical id' in current_info and
+                        b'cpu cores' in current_info):
+                    mapping[current_info[b'physical id']] = \
+                        current_info[b'cpu cores']
+                current_info = {}
+            else:
+                # ongoing section
+                if (line.startswith(b'physical id') or
+                        line.startswith(b'cpu cores')):
+                    key, value = line.split(b'\t:', 1)
+                    current_info[key] = int(value)
+
+    # mimic os.cpu_count()
+    return sum(mapping.values()) or None
+
+
+def cpu_stats():
+    """Return various CPU stats as a named tuple."""
+    with open_binary('%s/stat' % get_procfs_path()) as f:
+        ctx_switches = None
+        interrupts = None
+        soft_interrupts = None
+        for line in f:
+            if line.startswith(b'ctxt'):
+                ctx_switches = int(line.split()[1])
+            elif line.startswith(b'intr'):
+                interrupts = int(line.split()[1])
+            elif line.startswith(b'softirq'):
+                soft_interrupts = int(line.split()[1])
+            if ctx_switches is not None and soft_interrupts is not None \
+                    and interrupts is not None:
+                break
+    syscalls = 0
+    return _common.scpustats(
+        ctx_switches, interrupts, soft_interrupts, syscalls)
+
+
+if os.path.exists("/sys/devices/system/cpu/cpufreq") or \
+        os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq"):
+    def cpu_freq():
+        """Return frequency metrics for all CPUs.
+        Contrarily to other OSes, Linux updates these values in
+        real-time.
+        """
+        # scaling_* files seem preferable to cpuinfo_*, see:
+        # http://unix.stackexchange.com/a/87537/168884
+        ret = []
+        ls = glob.glob("/sys/devices/system/cpu/cpufreq/policy*")
+        if ls:
+            # Sort the list so that '10' comes after '2'. This should
+            # ensure the CPU order is consistent with other CPU functions
+            # having a 'percpu' argument and returning results for multiple
+            # CPUs (cpu_times(), cpu_percent(), cpu_times_percent()).
+            ls.sort(key=lambda x: int(os.path.basename(x)[6:]))
+        else:
+            # https://github.com/giampaolo/psutil/issues/981
+            ls = glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq")
+            ls.sort(key=lambda x: int(re.search('[0-9]+', x).group(0)))
+
+        pjoin = os.path.join
+        for path in ls:
+            curr = cat(pjoin(path, "scaling_cur_freq"), fallback=None)
+            if curr is None:
+                # Likely an old RedHat, see:
+                # https://github.com/giampaolo/psutil/issues/1071
+                curr = cat(pjoin(path, "cpuinfo_cur_freq"), fallback=None)
+                if curr is None:
+                    raise NotImplementedError(
+                        "can't find current frequency file")
+            curr = int(curr) / 1000
+            max_ = int(cat(pjoin(path, "scaling_max_freq"))) / 1000
+            min_ = int(cat(pjoin(path, "scaling_min_freq"))) / 1000
+            ret.append(_common.scpufreq(curr, min_, max_))
+        return ret
+
+
+# =====================================================================
+# --- network
+# =====================================================================
+
+
+net_if_addrs = cext_posix.net_if_addrs
+
+
+class _Ipv6UnsupportedError(Exception):
+    pass
+
+
+class Connections:
+    """A wrapper on top of /proc/net/* files, retrieving per-process
+    and system-wide open connections (TCP, UDP, UNIX) similarly to
+    "netstat -an".
+
+    Note: in case of UNIX sockets we're only able to determine the
+    local endpoint/path, not the one it's connected to.
+    According to [1] it would be possible but not easily.
+
+    [1] http://serverfault.com/a/417946
+    """
+
+    def __init__(self):
+        tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM)
+        tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM)
+        udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM)
+        udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM)
+        unix = ("unix", socket.AF_UNIX, None)
+        self.tmap = {
+            "all": (tcp4, tcp6, udp4, udp6, unix),
+            "tcp": (tcp4, tcp6),
+            "tcp4": (tcp4,),
+            "tcp6": (tcp6,),
+            "udp": (udp4, udp6),
+            "udp4": (udp4,),
+            "udp6": (udp6,),
+            "unix": (unix,),
+            "inet": (tcp4, tcp6, udp4, udp6),
+            "inet4": (tcp4, udp4),
+            "inet6": (tcp6, udp6),
+        }
+        self._procfs_path = None
+
+    def get_proc_inodes(self, pid):
+        inodes = defaultdict(list)
+        for fd in os.listdir("%s/%s/fd" % (self._procfs_path, pid)):
+            try:
+                inode = readlink("%s/%s/fd/%s" % (self._procfs_path, pid, fd))
+            except OSError as err:
+                # ENOENT == file which is gone in the meantime;
+                # os.stat('/proc/%s' % self.pid) will be done later
+                # to force NSP (if it's the case)
+                if err.errno in (errno.ENOENT, errno.ESRCH):
+                    continue
+                elif err.errno == errno.EINVAL:
+                    # not a link
+                    continue
+                else:
+                    raise
+            else:
+                if inode.startswith('socket:['):
+                    # the process is using a socket
+                    inode = inode[8:][:-1]
+                    inodes[inode].append((pid, int(fd)))
+        return inodes
+
+    def get_all_inodes(self):
+        inodes = {}
+        for pid in pids():
+            try:
+                inodes.update(self.get_proc_inodes(pid))
+            except OSError as err:
+                # os.listdir() is gonna raise a lot of access denied
+                # exceptions in case of unprivileged user; that's fine
+                # as we'll just end up returning a connection with PID
+                # and fd set to None anyway.
+                # Both netstat -an and lsof does the same so it's
+                # unlikely we can do any better.
+                # ENOENT just means a PID disappeared on us.
+                if err.errno not in (
+                        errno.ENOENT, errno.ESRCH, errno.EPERM, errno.EACCES):
+                    raise
+        return inodes
+
+    @staticmethod
+    def decode_address(addr, family):
+        """Accept an "ip:port" address as displayed in /proc/net/*
+        and convert it into a human readable form, like:
+
+        "0500000A:0016" -> ("10.0.0.5", 22)
+        "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521)
+
+        The IP address portion is a little or big endian four-byte
+        hexadecimal number; that is, the least significant byte is listed
+        first, so we need to reverse the order of the bytes to convert it
+        to an IP address.
+        The port is represented as a two-byte hexadecimal number.
+
+        Reference:
+        http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html
+        """
+        ip, port = addr.split(':')
+        port = int(port, 16)
+        # this usually refers to a local socket in listen mode with
+        # no end-points connected
+        if not port:
+            return ()
+        if PY3:
+            ip = ip.encode('ascii')
+        if family == socket.AF_INET:
+            # see: https://github.com/giampaolo/psutil/issues/201
+            if LITTLE_ENDIAN:
+                ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1])
+            else:
+                ip = socket.inet_ntop(family, base64.b16decode(ip))
+        else:  # IPv6
+            # old version - let's keep it, just in case...
+            # ip = ip.decode('hex')
+            # return socket.inet_ntop(socket.AF_INET6,
+            #          ''.join(ip[i:i+4][::-1] for i in xrange(0, 16, 4)))
+            ip = base64.b16decode(ip)
+            try:
+                # see: https://github.com/giampaolo/psutil/issues/201
+                if LITTLE_ENDIAN:
+                    ip = socket.inet_ntop(
+                        socket.AF_INET6,
+                        struct.pack('>4I', *struct.unpack('<4I', ip)))
+                else:
+                    ip = socket.inet_ntop(
+                        socket.AF_INET6,
+                        struct.pack('<4I', *struct.unpack('<4I', ip)))
+            except ValueError:
+                # see: https://github.com/giampaolo/psutil/issues/623
+                if not supports_ipv6():
+                    raise _Ipv6UnsupportedError
+                else:
+                    raise
+        return _common.addr(ip, port)
+
+    @staticmethod
+    def process_inet(file, family, type_, inodes, filter_pid=None):
+        """Parse /proc/net/tcp* and /proc/net/udp* files."""
+        if file.endswith('6') and not os.path.exists(file):
+            # IPv6 not supported
+            return
+        with open_text(file, buffering=BIGFILE_BUFFERING) as f:
+            f.readline()  # skip the first line
+            for lineno, line in enumerate(f, 1):
+                try:
+                    _, laddr, raddr, status, _, _, _, _, _, inode = \
+                        line.split()[:10]
+                except ValueError:
+                    raise RuntimeError(
+                        "error while parsing %s; malformed line %s %r" % (
+                            file, lineno, line))
+                if inode in inodes:
+                    # # We assume inet sockets are unique, so we error
+                    # # out if there are multiple references to the
+                    # # same inode. We won't do this for UNIX sockets.
+                    # if len(inodes[inode]) > 1 and family != socket.AF_UNIX:
+                    #     raise ValueError("ambiguos inode with multiple "
+                    #                      "PIDs references")
+                    pid, fd = inodes[inode][0]
+                else:
+                    pid, fd = None, -1
+                if filter_pid is not None and filter_pid != pid:
+                    continue
+                else:
+                    if type_ == socket.SOCK_STREAM:
+                        status = TCP_STATUSES[status]
+                    else:
+                        status = _common.CONN_NONE
+                    try:
+                        laddr = Connections.decode_address(laddr, family)
+                        raddr = Connections.decode_address(raddr, family)
+                    except _Ipv6UnsupportedError:
+                        continue
+                    yield (fd, family, type_, laddr, raddr, status, pid)
+
+    @staticmethod
+    def process_unix(file, family, inodes, filter_pid=None):
+        """Parse /proc/net/unix files."""
+        with open_text(file, buffering=BIGFILE_BUFFERING) as f:
+            f.readline()  # skip the first line
+            for line in f:
+                tokens = line.split()
+                try:
+                    _, _, _, _, type_, _, inode = tokens[0:7]
+                except ValueError:
+                    if ' ' not in line:
+                        # see: https://github.com/giampaolo/psutil/issues/766
+                        continue
+                    raise RuntimeError(
+                        "error while parsing %s; malformed line %r" % (
+                            file, line))
+                if inode in inodes:
+                    # With UNIX sockets we can have a single inode
+                    # referencing many file descriptors.
+                    pairs = inodes[inode]
+                else:
+                    pairs = [(None, -1)]
+                for pid, fd in pairs:
+                    if filter_pid is not None and filter_pid != pid:
+                        continue
+                    else:
+                        if len(tokens) == 8:
+                            path = tokens[-1]
+                        else:
+                            path = ""
+                        type_ = int(type_)
+                        # XXX: determining the remote endpoint of a
+                        # UNIX socket on Linux is not possible, see:
+                        # https://serverfault.com/questions/252723/
+                        raddr = ""
+                        status = _common.CONN_NONE
+                        yield (fd, family, type_, path, raddr, status, pid)
+
+    def retrieve(self, kind, pid=None):
+        if kind not in self.tmap:
+            raise ValueError("invalid %r kind argument; choose between %s"
+                             % (kind, ', '.join([repr(x) for x in self.tmap])))
+        self._procfs_path = get_procfs_path()
+        if pid is not None:
+            inodes = self.get_proc_inodes(pid)
+            if not inodes:
+                # no connections for this process
+                return []
+        else:
+            inodes = self.get_all_inodes()
+        ret = set()
+        for f, family, type_ in self.tmap[kind]:
+            if family in (socket.AF_INET, socket.AF_INET6):
+                ls = self.process_inet(
+                    "%s/net/%s" % (self._procfs_path, f),
+                    family, type_, inodes, filter_pid=pid)
+            else:
+                ls = self.process_unix(
+                    "%s/net/%s" % (self._procfs_path, f),
+                    family, inodes, filter_pid=pid)
+            for fd, family, type_, laddr, raddr, status, bound_pid in ls:
+                if pid:
+                    conn = _common.pconn(fd, family, type_, laddr, raddr,
+                                         status)
+                else:
+                    conn = _common.sconn(fd, family, type_, laddr, raddr,
+                                         status, bound_pid)
+                ret.add(conn)
+        return list(ret)
+
+
+_connections = Connections()
+
+
+def net_connections(kind='inet'):
+    """Return system-wide open connections."""
+    return _connections.retrieve(kind)
+
+
+def net_io_counters():
+    """Return network I/O statistics for every network interface
+    installed on the system as a dict of raw tuples.
+    """
+    with open_text("%s/net/dev" % get_procfs_path()) as f:
+        lines = f.readlines()
+    retdict = {}
+    for line in lines[2:]:
+        colon = line.rfind(':')
+        assert colon > 0, repr(line)
+        name = line[:colon].strip()
+        fields = line[colon + 1:].strip().split()
+
+        # in
+        (bytes_recv,
+         packets_recv,
+         errin,
+         dropin,
+         fifoin,  # unused
+         framein,  # unused
+         compressedin,  # unused
+         multicastin,  # unused
+         # out
+         bytes_sent,
+         packets_sent,
+         errout,
+         dropout,
+         fifoout,  # unused
+         collisionsout,  # unused
+         carrierout,  # unused
+         compressedout) = map(int, fields)
+
+        retdict[name] = (bytes_sent, bytes_recv, packets_sent, packets_recv,
+                         errin, errout, dropin, dropout)
+    return retdict
+
+
+def net_if_stats():
+    """Get NIC stats (isup, duplex, speed, mtu)."""
+    duplex_map = {cext.DUPLEX_FULL: NIC_DUPLEX_FULL,
+                  cext.DUPLEX_HALF: NIC_DUPLEX_HALF,
+                  cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN}
+    names = net_io_counters().keys()
+    ret = {}
+    for name in names:
+        mtu = cext_posix.net_if_mtu(name)
+        isup = cext_posix.net_if_flags(name)
+        duplex, speed = cext.net_if_duplex_speed(name)
+        ret[name] = _common.snicstats(isup, duplex_map[duplex], speed, mtu)
+    return ret
+
+
+# =====================================================================
+# --- disks
+# =====================================================================
+
+
+disk_usage = _psposix.disk_usage
+
+
+def disk_io_counters():
+    """Return disk I/O statistics for every disk installed on the
+    system as a dict of raw tuples.
+    """
+    # determine partitions we want to look for
+    def get_partitions():
+        partitions = []
+        with open_text("%s/partitions" % get_procfs_path()) as f:
+            lines = f.readlines()[2:]
+        for line in reversed(lines):
+            _, _, _, name = line.split()
+            if name[-1].isdigit():
+                # we're dealing with a partition (e.g. 'sda1'); 'sda' will
+                # also be around but we want to omit it
+                partitions.append(name)
+            else:
+                if not partitions or not partitions[-1].startswith(name):
+                    # we're dealing with a disk entity for which no
+                    # partitions have been defined (e.g. 'sda' but
+                    # 'sda1' was not around), see:
+                    # https://github.com/giampaolo/psutil/issues/338
+                    partitions.append(name)
+        return partitions
+
+    retdict = {}
+    partitions = get_partitions()
+    with open_text("%s/diskstats" % get_procfs_path()) as f:
+        lines = f.readlines()
+    for line in lines:
+        # OK, this is a bit confusing. The format of /proc/diskstats can
+        # have 3 variations.
+        # On Linux 2.4 each line has always 15 fields, e.g.:
+        # "3     0   8 hda 8 8 8 8 8 8 8 8 8 8 8"
+        # On Linux 2.6+ each line *usually* has 14 fields, and the disk
+        # name is in another position, like this:
+        # "3    0   hda 8 8 8 8 8 8 8 8 8 8 8"
+        # ...unless (Linux 2.6) the line refers to a partition instead
+        # of a disk, in which case the line has less fields (7):
+        # "3    1   hda1 8 8 8 8"
+        # See:
+        # https://www.kernel.org/doc/Documentation/iostats.txt
+        # https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
+        fields = line.split()
+        fields_len = len(fields)
+        if fields_len == 15:
+            # Linux 2.4
+            name = fields[3]
+            reads = int(fields[2])
+            (reads_merged, rbytes, rtime, writes, writes_merged,
+                wbytes, wtime, _, busy_time, _) = map(int, fields[4:14])
+        elif fields_len == 14:
+            # Linux 2.6+, line referring to a disk
+            name = fields[2]
+            (reads, reads_merged, rbytes, rtime, writes, writes_merged,
+                wbytes, wtime, _, busy_time, _) = map(int, fields[3:14])
+        elif fields_len == 7:
+            # Linux 2.6+, line referring to a partition
+            name = fields[2]
+            reads, rbytes, writes, wbytes = map(int, fields[3:])
+            rtime = wtime = reads_merged = writes_merged = busy_time = 0
+        else:
+            raise ValueError("not sure how to interpret line %r" % line)
+
+        if name in partitions:
+            ssize = get_sector_size(name)
+            rbytes *= ssize
+            wbytes *= ssize
+            retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime,
+                             reads_merged, writes_merged, busy_time)
+    return retdict
+
+
+def disk_partitions(all=False):
+    """Return mounted disk partitions as a list of namedtuples."""
+    fstypes = set()
+    with open_text("%s/filesystems" % get_procfs_path()) as f:
+        for line in f:
+            line = line.strip()
+            if not line.startswith("nodev"):
+                fstypes.add(line.strip())
+            else:
+                # ignore all lines starting with "nodev" except "nodev zfs"
+                fstype = line.split("\t")[1]
+                if fstype == "zfs":
+                    fstypes.add("zfs")
+
+    retlist = []
+    partitions = cext.disk_partitions()
+    for partition in partitions:
+        device, mountpoint, fstype, opts = partition
+        if device == 'none':
+            device = ''
+        if not all:
+            if device == '' or fstype not in fstypes:
+                continue
+        ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+        retlist.append(ntuple)
+    return retlist
+
+
+# =====================================================================
+# --- sensors
+# =====================================================================
+
+
+def sensors_temperatures():
+    """Return hardware (CPU and others) temperatures as a dict
+    including hardware name, label, current, max and critical
+    temperatures.
+
+    Implementation notes:
+    - /sys/class/hwmon looks like the most recent interface to
+      retrieve this info, and this implementation relies on it
+      only (old distros will probably use something else)
+    - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon
+    - /sys/class/thermal/thermal_zone* is another one but it's more
+      difficult to parse
+    """
+    ret = collections.defaultdict(list)
+    basenames = glob.glob('/sys/class/hwmon/hwmon*/temp*_*')
+    # CentOS has an intermediate /device directory:
+    # https://github.com/giampaolo/psutil/issues/971
+    # https://github.com/nicolargo/glances/issues/1060
+    basenames.extend(glob.glob('/sys/class/hwmon/hwmon*/device/temp*_*'))
+    basenames = sorted(set([x.split('_')[0] for x in basenames]))
+
+    for base in basenames:
+        try:
+            current = float(cat(base + '_input')) / 1000.0
+        except (IOError, OSError) as err:
+            # A lot of things can go wrong here, so let's just skip the
+            # whole entry.
+            # https://github.com/giampaolo/psutil/issues/1009
+            # https://github.com/giampaolo/psutil/issues/1101
+            # https://github.com/giampaolo/psutil/issues/1129
+            warnings.warn("ignoring %r" % err, RuntimeWarning)
+            continue
+
+        unit_name = cat(os.path.join(os.path.dirname(base), 'name'),
+                        binary=False)
+        high = cat(base + '_max', fallback=None)
+        critical = cat(base + '_crit', fallback=None)
+        label = cat(base + '_label', fallback='', binary=False)
+
+        if high is not None:
+            high = float(high) / 1000.0
+        if critical is not None:
+            critical = float(critical) / 1000.0
+
+        ret[unit_name].append((label, current, high, critical))
+
+    return ret
+
+
+def sensors_fans():
+    """Return hardware fans info (for CPU and other peripherals) as a
+    dict including hardware label and current speed.
+
+    Implementation notes:
+    - /sys/class/hwmon looks like the most recent interface to
+      retrieve this info, and this implementation relies on it
+      only (old distros will probably use something else)
+    - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon
+    """
+    ret = collections.defaultdict(list)
+    basenames = glob.glob('/sys/class/hwmon/hwmon*/fan*_*')
+    if not basenames:
+        # CentOS has an intermediate /device directory:
+        # https://github.com/giampaolo/psutil/issues/971
+        basenames = glob.glob('/sys/class/hwmon/hwmon*/device/fan*_*')
+
+    basenames = sorted(set([x.split('_')[0] for x in basenames]))
+    for base in basenames:
+        try:
+            current = int(cat(base + '_input'))
+        except (IOError, OSError) as err:
+            warnings.warn("ignoring %r" % err, RuntimeWarning)
+            continue
+        unit_name = cat(os.path.join(os.path.dirname(base), 'name'),
+                        binary=False)
+        label = cat(base + '_label', fallback='', binary=False)
+        ret[unit_name].append(_common.sfan(label, current))
+
+    return dict(ret)
+
+
+def sensors_battery():
+    """Return battery information.
+    Implementation note: it appears /sys/class/power_supply/BAT0/
+    directory structure may vary and provide files with the same
+    meaning but under different names, see:
+    https://github.com/giampaolo/psutil/issues/966
+    """
+    null = object()
+
+    def multi_cat(*paths):
+        """Attempt to read the content of multiple files which may
+        not exist. If none of them exist return None.
+        """
+        for path in paths:
+            ret = cat(path, fallback=null)
+            if ret != null:
+                return int(ret) if ret.isdigit() else ret
+        return None
+
+    root = os.path.join(POWER_SUPPLY_PATH, "BAT0")
+    if not os.path.exists(root):
+        return None
+
+    # Base metrics.
+    energy_now = multi_cat(
+        root + "/energy_now",
+        root + "/charge_now")
+    power_now = multi_cat(
+        root + "/power_now",
+        root + "/current_now")
+    energy_full = multi_cat(
+        root + "/energy_full",
+        root + "/charge_full")
+    if energy_now is None or power_now is None:
+        return None
+
+    # Percent. If we have energy_full the percentage will be more
+    # accurate compared to reading /capacity file (float vs. int).
+    if energy_full is not None:
+        try:
+            percent = 100.0 * energy_now / energy_full
+        except ZeroDivisionError:
+            percent = 0.0
+    else:
+        percent = int(cat(root + "/capacity", fallback=-1))
+        if percent == -1:
+            return None
+
+    # Is AC power cable plugged in?
+    # Note: AC0 is not always available and sometimes (e.g. CentOS7)
+    # it's called "AC".
+    power_plugged = None
+    online = multi_cat(
+        os.path.join(POWER_SUPPLY_PATH, "AC0/online"),
+        os.path.join(POWER_SUPPLY_PATH, "AC/online"))
+    if online is not None:
+        power_plugged = online == 1
+    else:
+        status = cat(root + "/status", fallback="", binary=False).lower()
+        if status == "discharging":
+            power_plugged = False
+        elif status in ("charging", "full"):
+            power_plugged = True
+
+    # Seconds left.
+    # Note to self: we may also calculate the charging ETA as per:
+    # https://github.com/thialfihar/dotfiles/blob/
+    #     013937745fd9050c30146290e8f963d65c0179e6/bin/battery.py#L55
+    if power_plugged:
+        secsleft = _common.POWER_TIME_UNLIMITED
+    else:
+        try:
+            secsleft = int(energy_now / power_now * 3600)
+        except ZeroDivisionError:
+            secsleft = _common.POWER_TIME_UNKNOWN
+
+    return _common.sbattery(percent, secsleft, power_plugged)
+
+
+# =====================================================================
+# --- other system functions
+# =====================================================================
+
+
+def users():
+    """Return currently connected users as a list of namedtuples."""
+    retlist = []
+    rawlist = cext.users()
+    for item in rawlist:
+        user, tty, hostname, tstamp, user_process, pid = item
+        # note: the underlying C function includes entries about
+        # system boot, run level and others.  We might want
+        # to use them in the future.
+        if not user_process:
+            continue
+        if hostname in (':0.0', ':0'):
+            hostname = 'localhost'
+        nt = _common.suser(user, tty or None, hostname, tstamp, pid)
+        retlist.append(nt)
+    return retlist
+
+
+def boot_time():
+    """Return the system boot time expressed in seconds since the epoch."""
+    global BOOT_TIME
+    path = '%s/stat' % get_procfs_path()
+    with open_binary(path) as f:
+        for line in f:
+            if line.startswith(b'btime'):
+                ret = float(line.strip().split()[1])
+                BOOT_TIME = ret
+                return ret
+        raise RuntimeError(
+            "line 'btime' not found in %s" % path)
+
+
+# =====================================================================
+# --- processes
+# =====================================================================
+
+
+def pids():
+    """Returns a list of PIDs currently running on the system."""
+    return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()]
+
+
+def pid_exists(pid):
+    """Check for the existence of a unix PID. Linux TIDs are not
+    supported (always return False).
+    """
+    if not _psposix.pid_exists(pid):
+        return False
+    else:
+        # Linux's apparently does not distinguish between PIDs and TIDs
+        # (thread IDs).
+        # listdir("/proc") won't show any TID (only PIDs) but
+        # os.stat("/proc/{tid}") will succeed if {tid} exists.
+        # os.kill() can also be passed a TID. This is quite confusing.
+        # In here we want to enforce this distinction and support PIDs
+        # only, see:
+        # https://github.com/giampaolo/psutil/issues/687
+        try:
+            # Note: already checked that this is faster than using a
+            # regular expr. Also (a lot) faster than doing
+            # 'return pid in pids()'
+            path = "%s/%s/status" % (get_procfs_path(), pid)
+            with open_binary(path) as f:
+                for line in f:
+                    if line.startswith(b"Tgid:"):
+                        tgid = int(line.split()[1])
+                        # If tgid and pid are the same then we're
+                        # dealing with a process PID.
+                        return tgid == pid
+                raise ValueError("'Tgid' line not found in %s" % path)
+        except (EnvironmentError, ValueError):
+            return pid in pids()
+
+
+def ppid_map():
+    """Obtain a {pid: ppid, ...} dict for all running processes in
+    one shot. Used to speed up Process.children().
+    """
+    ret = {}
+    procfs_path = get_procfs_path()
+    for pid in pids():
+        try:
+            with open_binary("%s/%s/stat" % (procfs_path, pid)) as f:
+                data = f.read()
+        except EnvironmentError as err:
+            # Note: we should be able to access /stat for all processes
+            # so we won't bump into EPERM, which is good.
+            if err.errno not in (errno.ENOENT, errno.ESRCH,
+                                 errno.EPERM, errno.EACCES):
+                raise
+        else:
+            rpar = data.rfind(b')')
+            dset = data[rpar + 2:].split()
+            ppid = int(dset[1])
+            ret[pid] = ppid
+    return ret
+
+
+def wrap_exceptions(fun):
+    """Decorator which translates bare OSError and IOError exceptions
+    into NoSuchProcess and AccessDenied.
+    """
+    @functools.wraps(fun)
+    def wrapper(self, *args, **kwargs):
+        try:
+            return fun(self, *args, **kwargs)
+        except EnvironmentError as err:
+            if err.errno in (errno.EPERM, errno.EACCES):
+                raise AccessDenied(self.pid, self._name)
+            # ESRCH (no such process) can be raised on read() if
+            # process is gone in the meantime.
+            if err.errno == errno.ESRCH:
+                raise NoSuchProcess(self.pid, self._name)
+            # ENOENT (no such file or directory) can be raised on open().
+            if err.errno == errno.ENOENT and not os.path.exists("%s/%s" % (
+                    self._procfs_path, self.pid)):
+                raise NoSuchProcess(self.pid, self._name)
+            # Note: zombies will keep existing under /proc until they're
+            # gone so there's no way to distinguish them in here.
+            raise
+    return wrapper
+
+
+class Process(object):
+    """Linux process implementation."""
+
+    __slots__ = ["pid", "_name", "_ppid", "_procfs_path"]
+
+    def __init__(self, pid):
+        self.pid = pid
+        self._name = None
+        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 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()
+        # Process name is between parentheses. It can contain spaces and
+        # other parentheses. This is taken into account by looking for
+        # the first occurrence of "(" and the last occurence of ")".
+        rpar = data.rfind(b')')
+        name = data[data.find(b'(') + 1:rpar]
+        others = data[rpar + 2:].split()
+        return [name] + others
+
+    @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=BIGFILE_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]
+        if PY3:
+            name = decode(name)
+        # XXX - gets changed later and probably needs refactoring
+        return name
+
+    def exe(self):
+        try:
+            return readlink("%s/%s/exe" % (self._procfs_path, self.pid))
+        except OSError as err:
+            if err.errno in (errno.ENOENT, errno.ESRCH):
+                # no such file error; might be raised also if the
+                # path actually exists for system processes with
+                # low pids (about 0-20)
+                if os.path.lexists("%s/%s" % (self._procfs_path, self.pid)):
+                    return ""
+                else:
+                    if not pid_exists(self.pid):
+                        raise NoSuchProcess(self.pid, self._name)
+                    else:
+                        raise ZombieProcess(self.pid, self._name, self._ppid)
+            if err.errno in (errno.EPERM, errno.EACCES):
+                raise AccessDenied(self.pid, self._name)
+            raise
+
+    @wrap_exceptions
+    def cmdline(self):
+        with open_text("%s/%s/cmdline" % (self._procfs_path, self.pid)) as f:
+            data = f.read()
+        if not data:
+            # may happen in case of zombie process
+            return []
+        # 'man proc' states that args are separated by null bytes '\0'
+        # and last char is supposed to be a null byte. Nevertheless
+        # some processes may change their cmdline after being started
+        # (via setproctitle() or similar), they are usually not
+        # compliant with this rule and use spaces instead. Google
+        # Chrome process is an example. See:
+        # https://github.com/giampaolo/psutil/issues/1179
+        sep = '\x00' if data.endswith('\x00') else ' '
+        if data.endswith(sep):
+            data = data[:-1]
+        return [x for x in data.split(sep)]
+
+    @wrap_exceptions
+    def environ(self):
+        with open_text("%s/%s/environ" % (self._procfs_path, self.pid)) as f:
+            data = f.read()
+        return parse_environ_block(data)
+
+    @wrap_exceptions
+    def terminal(self):
+        tty_nr = int(self._parse_stat_file()[5])
+        tmap = _psposix.get_terminal_map()
+        try:
+            return tmap[tty_nr]
+        except KeyError:
+            return None
+
+    if os.path.exists('/proc/%s/io' % os.getpid()):
+        @wrap_exceptions
+        def io_counters(self):
+            fname = "%s/%s/io" % (self._procfs_path, self.pid)
+            fields = {}
+            with open_binary(fname) as f:
+                for line in f:
+                    # https://github.com/giampaolo/psutil/issues/1004
+                    line = line.strip()
+                    if line:
+                        name, value = line.split(b': ')
+                        fields[name] = int(value)
+            if not fields:
+                raise RuntimeError("%s file was empty" % fname)
+            return pio(
+                fields[b'syscr'],  # read syscalls
+                fields[b'syscw'],  # write syscalls
+                fields[b'read_bytes'],  # read bytes
+                fields[b'write_bytes'],  # write bytes
+                fields[b'rchar'],  # read chars
+                fields[b'wchar'],  # write chars
+            )
+    else:
+        def io_counters(self):
+            raise NotImplementedError("couldn't find /proc/%s/io (kernel "
+                                      "too old?)" % self.pid)
+
+    @wrap_exceptions
+    def cpu_times(self):
+        values = self._parse_stat_file()
+        utime = float(values[12]) / CLOCK_TICKS
+        stime = float(values[13]) / CLOCK_TICKS
+        children_utime = float(values[14]) / CLOCK_TICKS
+        children_stime = float(values[15]) / CLOCK_TICKS
+        return _common.pcputimes(utime, stime, children_utime, children_stime)
+
+    @wrap_exceptions
+    def cpu_num(self):
+        """What CPU the process is on."""
+        return int(self._parse_stat_file()[37])
+
+    @wrap_exceptions
+    def wait(self, timeout=None):
+        return _psposix.wait_pid(self.pid, timeout, self._name)
+
+    @wrap_exceptions
+    def create_time(self):
+        values = self._parse_stat_file()
+        # According to documentation, starttime is in field 21 and the
+        # unit is jiffies (clock ticks).
+        # We first divide it for clock ticks and then add uptime returning
+        # seconds since the epoch, in UTC.
+        # Also use cached value if available.
+        bt = BOOT_TIME or boot_time()
+        return (float(values[20]) / CLOCK_TICKS) + bt
+
+    @wrap_exceptions
+    def memory_info(self):
+        #  ============================================================
+        # | FIELD  | DESCRIPTION                         | AKA  | TOP  |
+        #  ============================================================
+        # | rss    | resident set size                   |      | RES  |
+        # | vms    | total program size                  | size | VIRT |
+        # | shared | shared pages (from shared mappings) |      | SHR  |
+        # | text   | text ('code')                       | trs  | CODE |
+        # | lib    | library (unused in Linux 2.6)       | lrs  |      |
+        # | data   | data + stack                        | drs  | DATA |
+        # | dirty  | dirty pages (unused in Linux 2.6)   | dt   |      |
+        #  ============================================================
+        with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f:
+            vms, rss, shared, text, lib, data, dirty = \
+                [int(x) * PAGESIZE for x in f.readline().split()[:7]]
+        return pmem(rss, vms, shared, text, lib, data, dirty)
+
+    # /proc/pid/smaps does not exist on kernels < 2.6.14 or if
+    # CONFIG_MMU kernel configuration option is not enabled.
+    if HAS_SMAPS:
+
+        @wrap_exceptions
+        def memory_full_info(
+                self,
+                _private_re=re.compile(br"Private.*:\s+(\d+)"),
+                _pss_re=re.compile(br"Pss.*:\s+(\d+)"),
+                _swap_re=re.compile(br"Swap.*:\s+(\d+)")):
+            basic_mem = self.memory_info()
+            # Note: using 3 regexes is faster than reading the file
+            # line by line.
+            # XXX: on Python 3 the 2 regexes are 30% slower than on
+            # Python 2 though. Figure out why.
+            #
+            # You might be tempted to calculate USS by subtracting
+            # the "shared" value from the "resident" value in
+            # /proc/<pid>/statm. But at least on Linux, statm's "shared"
+            # value actually counts pages backed by files, which has
+            # little to do with whether the pages are actually shared.
+            # /proc/self/smaps on the other hand appears to give us the
+            # correct information.
+            smaps_data = self._read_smaps_file()
+            # Note: smaps file can be empty for certain processes.
+            # The code below will not crash though and will result to 0.
+            uss = sum(map(int, _private_re.findall(smaps_data))) * 1024
+            pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024
+            swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024
+            return pfullmem(*basic_mem + (uss, pss, swap))
+
+    else:
+        memory_full_info = memory_info
+
+    if HAS_SMAPS:
+
+        @wrap_exceptions
+        def memory_maps(self):
+            """Return process's mapped memory regions as a list of named
+            tuples. Fields are explained in 'man proc'; here is an updated
+            (Apr 2012) version: http://goo.gl/fmebo
+            """
+            def get_blocks(lines, current_block):
+                data = {}
+                for line in lines:
+                    fields = line.split(None, 5)
+                    if not fields[0].endswith(b':'):
+                        # new block section
+                        yield (current_block.pop(), data)
+                        current_block.append(line)
+                    else:
+                        try:
+                            data[fields[0]] = int(fields[1]) * 1024
+                        except ValueError:
+                            if fields[0].startswith(b'VmFlags:'):
+                                # see issue #369
+                                continue
+                            else:
+                                raise ValueError("don't know how to inte"
+                                                 "rpret line %r" % line)
+                yield (current_block.pop(), data)
+
+            data = self._read_smaps_file()
+            # Note: smaps file can be empty for certain processes.
+            if not data:
+                return []
+            lines = data.split(b'\n')
+            ls = []
+            first_line = lines.pop(0)
+            current_block = [first_line]
+            for header, data in get_blocks(lines, current_block):
+                hfields = header.split(None, 5)
+                try:
+                    addr, perms, offset, dev, inode, path = hfields
+                except ValueError:
+                    addr, perms, offset, dev, inode, path = \
+                        hfields + ['']
+                if not path:
+                    path = '[anon]'
+                else:
+                    if PY3:
+                        path = decode(path)
+                    path = path.strip()
+                    if (path.endswith(' (deleted)') and not
+                            path_exists_strict(path)):
+                        path = path[:-10]
+                ls.append((
+                    decode(addr), decode(perms), path,
+                    data[b'Rss:'],
+                    data.get(b'Size:', 0),
+                    data.get(b'Pss:', 0),
+                    data.get(b'Shared_Clean:', 0),
+                    data.get(b'Shared_Dirty:', 0),
+                    data.get(b'Private_Clean:', 0),
+                    data.get(b'Private_Dirty:', 0),
+                    data.get(b'Referenced:', 0),
+                    data.get(b'Anonymous:', 0),
+                    data.get(b'Swap:', 0)
+                ))
+            return ls
+
+    else:  # pragma: no cover
+        def memory_maps(self):
+            raise NotImplementedError(
+                "/proc/%s/smaps does not exist on kernels < 2.6.14 or "
+                "if CONFIG_MMU kernel configuration option is not "
+                "enabled." % self.pid)
+
+    @wrap_exceptions
+    def cwd(self):
+        try:
+            return readlink("%s/%s/cwd" % (self._procfs_path, self.pid))
+        except OSError as err:
+            # https://github.com/giampaolo/psutil/issues/986
+            if err.errno in (errno.ENOENT, errno.ESRCH):
+                if not pid_exists(self.pid):
+                    raise NoSuchProcess(self.pid, self._name)
+                else:
+                    raise ZombieProcess(self.pid, self._name, self._ppid)
+            raise
+
+    @wrap_exceptions
+    def num_ctx_switches(self,
+                         _ctxsw_re=re.compile(br'ctxt_switches:\t(\d+)')):
+        data = self._read_status_file()
+        ctxsw = _ctxsw_re.findall(data)
+        if not ctxsw:
+            raise NotImplementedError(
+                "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'"
+                "lines were not found in %s/%s/status; the kernel is "
+                "probably older than 2.6.23" % (
+                    self._procfs_path, self.pid))
+        else:
+            return _common.pctxsw(int(ctxsw[0]), int(ctxsw[1]))
+
+    @wrap_exceptions
+    def num_threads(self, _num_threads_re=re.compile(br'Threads:\t(\d+)')):
+        # Note: on Python 3 using a re is faster than iterating over file
+        # line by line. On Python 2 is the exact opposite, and iterating
+        # over a file on Python 3 is slower than on Python 2.
+        data = self._read_status_file()
+        return int(_num_threads_re.findall(data)[0])
+
+    @wrap_exceptions
+    def threads(self):
+        thread_ids = os.listdir("%s/%s/task" % (self._procfs_path, self.pid))
+        thread_ids.sort()
+        retlist = []
+        hit_enoent = False
+        for thread_id in thread_ids:
+            fname = "%s/%s/task/%s/stat" % (
+                self._procfs_path, self.pid, thread_id)
+            try:
+                with open_binary(fname) as f:
+                    st = f.read().strip()
+            except IOError as err:
+                if err.errno == errno.ENOENT:
+                    # no such file or directory; it means thread
+                    # disappeared on us
+                    hit_enoent = True
+                    continue
+                raise
+            # ignore the first two values ("pid (exe)")
+            st = st[st.find(b')') + 2:]
+            values = st.split(b' ')
+            utime = float(values[11]) / CLOCK_TICKS
+            stime = float(values[12]) / CLOCK_TICKS
+            ntuple = _common.pthread(int(thread_id), utime, stime)
+            retlist.append(ntuple)
+        if hit_enoent:
+            # raise NSP if the process disappeared on us
+            os.stat('%s/%s' % (self._procfs_path, self.pid))
+        return retlist
+
+    @wrap_exceptions
+    def nice_get(self):
+        # with open_text('%s/%s/stat' % (self._procfs_path, self.pid)) as f:
+        #   data = f.read()
+        #   return int(data.split()[18])
+
+        # Use C implementation
+        return cext_posix.getpriority(self.pid)
+
+    @wrap_exceptions
+    def nice_set(self, value):
+        return cext_posix.setpriority(self.pid, value)
+
+    @wrap_exceptions
+    def cpu_affinity_get(self):
+        return cext.proc_cpu_affinity_get(self.pid)
+
+    def _get_eligible_cpus(
+            self, _re=re.compile(br"Cpus_allowed_list:\t(\d+)-(\d+)")):
+        # See: https://github.com/giampaolo/psutil/issues/956
+        data = self._read_status_file()
+        match = _re.findall(data)
+        if match:
+            return list(range(int(match[0][0]), int(match[0][1]) + 1))
+        else:
+            return list(range(len(per_cpu_times())))
+
+    @wrap_exceptions
+    def cpu_affinity_set(self, cpus):
+        try:
+            cext.proc_cpu_affinity_set(self.pid, cpus)
+        except (OSError, ValueError) as err:
+            if isinstance(err, ValueError) or err.errno == errno.EINVAL:
+                eligible_cpus = self._get_eligible_cpus()
+                all_cpus = tuple(range(len(per_cpu_times())))
+                for cpu in cpus:
+                    if cpu not in all_cpus:
+                        raise ValueError(
+                            "invalid CPU number %r; choose between %s" % (
+                                cpu, eligible_cpus))
+                    if cpu not in eligible_cpus:
+                        raise ValueError(
+                            "CPU number %r is not eligible; choose "
+                            "between %s" % (cpu, eligible_cpus))
+            raise
+
+    # only starting from kernel 2.6.13
+    if hasattr(cext, "proc_ioprio_get"):
+
+        @wrap_exceptions
+        def ionice_get(self):
+            ioclass, value = cext.proc_ioprio_get(self.pid)
+            if enum is not None:
+                ioclass = IOPriority(ioclass)
+            return _common.pionice(ioclass, value)
+
+        @wrap_exceptions
+        def ionice_set(self, ioclass, value):
+            if value is not None:
+                if not PY3 and not isinstance(value, (int, long)):
+                    msg = "value argument is not an integer (gor %r)" % value
+                    raise TypeError(msg)
+                if not 0 <= value <= 7:
+                    raise ValueError(
+                        "value argument range expected is between 0 and 7")
+
+            if ioclass in (IOPRIO_CLASS_NONE, None):
+                if value:
+                    msg = "can't specify value with IOPRIO_CLASS_NONE " \
+                          "(got %r)" % value
+                    raise ValueError(msg)
+                ioclass = IOPRIO_CLASS_NONE
+                value = 0
+            elif ioclass == IOPRIO_CLASS_IDLE:
+                if value:
+                    msg = "can't specify value with IOPRIO_CLASS_IDLE " \
+                          "(got %r)" % value
+                    raise ValueError(msg)
+                value = 0
+            elif ioclass in (IOPRIO_CLASS_RT, IOPRIO_CLASS_BE):
+                if value is None:
+                    # TODO: add comment explaining why this is 4 (?)
+                    value = 4
+            else:
+                # otherwise we would get OSError(EVINAL)
+                raise ValueError("invalid ioclass argument %r" % ioclass)
+
+            return cext.proc_ioprio_set(self.pid, ioclass, value)
+
+    if HAS_PRLIMIT:
+        @wrap_exceptions
+        def rlimit(self, resource, limits=None):
+            # If pid is 0 prlimit() applies to the calling process and
+            # we don't want that. We should never get here though as
+            # PID 0 is not supported on Linux.
+            if self.pid == 0:
+                raise ValueError("can't use prlimit() against PID 0 process")
+            try:
+                if limits is None:
+                    # get
+                    return cext.linux_prlimit(self.pid, resource)
+                else:
+                    # set
+                    if len(limits) != 2:
+                        raise ValueError(
+                            "second argument must be a (soft, hard) tuple, "
+                            "got %s" % repr(limits))
+                    soft, hard = limits
+                    cext.linux_prlimit(self.pid, resource, soft, hard)
+            except OSError as err:
+                if err.errno == errno.ENOSYS and pid_exists(self.pid):
+                    # I saw this happening on Travis:
+                    # https://travis-ci.org/giampaolo/psutil/jobs/51368273
+                    raise ZombieProcess(self.pid, self._name, self._ppid)
+                else:
+                    raise
+
+    @wrap_exceptions
+    def status(self):
+        letter = self._parse_stat_file()[1]
+        if PY3:
+            letter = letter.decode()
+        # XXX is '?' legit? (we're not supposed to return it anyway)
+        return PROC_STATUSES.get(letter, '?')
+
+    @wrap_exceptions
+    def open_files(self):
+        retlist = []
+        files = os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))
+        hit_enoent = False
+        for fd in files:
+            file = "%s/%s/fd/%s" % (self._procfs_path, self.pid, fd)
+            try:
+                path = readlink(file)
+            except OSError as err:
+                # ENOENT == file which is gone in the meantime
+                if err.errno in (errno.ENOENT, errno.ESRCH):
+                    hit_enoent = True
+                    continue
+                elif err.errno == errno.EINVAL:
+                    # not a link
+                    continue
+                else:
+                    raise
+            else:
+                # If path is not an absolute there's no way to tell
+                # whether it's a regular file or not, so we skip it.
+                # A regular file is always supposed to be have an
+                # absolute path though.
+                if path.startswith('/') and isfile_strict(path):
+                    # Get file position and flags.
+                    file = "%s/%s/fdinfo/%s" % (
+                        self._procfs_path, self.pid, fd)
+                    try:
+                        with open_binary(file) as f:
+                            pos = int(f.readline().split()[1])
+                            flags = int(f.readline().split()[1], 8)
+                    except IOError as err:
+                        if err.errno == errno.ENOENT:
+                            # fd gone in the meantime; does not
+                            # necessarily mean the process disappeared
+                            # on us.
+                            hit_enoent = True
+                        else:
+                            raise
+                    else:
+                        mode = file_flags_to_mode(flags)
+                        ntuple = popenfile(
+                            path, int(fd), int(pos), mode, flags)
+                        retlist.append(ntuple)
+        if hit_enoent:
+            # raise NSP if the process disappeared on us
+            os.stat('%s/%s' % (self._procfs_path, self.pid))
+        return retlist
+
+    @wrap_exceptions
+    def connections(self, kind='inet'):
+        ret = _connections.retrieve(kind, self.pid)
+        # raise NSP if the process disappeared on us
+        os.stat('%s/%s' % (self._procfs_path, self.pid))
+        return ret
+
+    @wrap_exceptions
+    def num_fds(self):
+        return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)))
+
+    @wrap_exceptions
+    def ppid(self):
+        return int(self._parse_stat_file()[2])
+
+    @wrap_exceptions
+    def uids(self, _uids_re=re.compile(br'Uid:\t(\d+)\t(\d+)\t(\d+)')):
+        data = self._read_status_file()
+        real, effective, saved = _uids_re.findall(data)[0]
+        return _common.puids(int(real), int(effective), int(saved))
+
+    @wrap_exceptions
+    def gids(self, _gids_re=re.compile(br'Gid:\t(\d+)\t(\d+)\t(\d+)')):
+        data = self._read_status_file()
+        real, effective, saved = _gids_re.findall(data)[0]
+        return _common.pgids(int(real), int(effective), int(saved))
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/_psosx.py
@@ -0,0 +1,571 @@
+# 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.
+
+"""OSX platform implementation."""
+
+import contextlib
+import errno
+import functools
+import os
+from socket import AF_INET
+from collections import namedtuple
+
+from . import _common
+from . import _psposix
+from . import _psutil_osx as cext
+from . import _psutil_posix as cext_posix
+from ._common import AF_INET6
+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
+from ._common import usage_percent
+from ._exceptions import AccessDenied
+from ._exceptions import NoSuchProcess
+from ._exceptions import ZombieProcess
+
+
+__extra__all__ = []
+
+
+# =====================================================================
+# --- globals
+# =====================================================================
+
+
+PAGESIZE = os.sysconf("SC_PAGE_SIZE")
+AF_LINK = cext_posix.AF_LINK
+
+TCP_STATUSES = {
+    cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
+    cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
+    cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
+    cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
+    cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
+    cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
+    cext.TCPS_CLOSED: _common.CONN_CLOSE,
+    cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
+    cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
+    cext.TCPS_LISTEN: _common.CONN_LISTEN,
+    cext.TCPS_CLOSING: _common.CONN_CLOSING,
+    cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
+}
+
+PROC_STATUSES = {
+    cext.SIDL: _common.STATUS_IDLE,
+    cext.SRUN: _common.STATUS_RUNNING,
+    cext.SSLEEP: _common.STATUS_SLEEPING,
+    cext.SSTOP: _common.STATUS_STOPPED,
+    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,
+)
+
+
+# =====================================================================
+# --- named tuples
+# =====================================================================
+
+
+# psutil.cpu_times()
+scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle'])
+# psutil.virtual_memory()
+svmem = namedtuple(
+    'svmem', ['total', 'available', 'percent', 'used', 'free',
+              'active', 'inactive', 'wired'])
+# psutil.Process.memory_info()
+pmem = namedtuple('pmem', ['rss', 'vms', 'pfaults', 'pageins'])
+# psutil.Process.memory_full_info()
+pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', ))
+# psutil.Process.memory_maps(grouped=True)
+pmmap_grouped = namedtuple(
+    'pmmap_grouped',
+    'path rss private swapped dirtied ref_count shadow_depth')
+# psutil.Process.memory_maps(grouped=False)
+pmmap_ext = namedtuple(
+    'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
+
+
+# =====================================================================
+# --- memory
+# =====================================================================
+
+
+def virtual_memory():
+    """System virtual memory as a namedtuple."""
+    total, active, inactive, wired, free = cext.virtual_mem()
+    avail = inactive + free
+    used = active + inactive + wired
+    percent = usage_percent((total - avail), total, _round=1)
+    return svmem(total, avail, percent, used, free,
+                 active, inactive, wired)
+
+
+def swap_memory():
+    """Swap system memory as a (total, used, free, sin, sout) tuple."""
+    total, used, free, sin, sout = cext.swap_mem()
+    percent = usage_percent(used, total, _round=1)
+    return _common.sswap(total, used, free, percent, sin, sout)
+
+
+# =====================================================================
+# --- CPU
+# =====================================================================
+
+
+def cpu_times():
+    """Return system CPU times as a namedtuple."""
+    user, nice, system, idle = cext.cpu_times()
+    return scputimes(user, nice, system, idle)
+
+
+def per_cpu_times():
+    """Return system CPU times as a named tuple"""
+    ret = []
+    for cpu_t in cext.per_cpu_times():
+        user, nice, system, idle = cpu_t
+        item = scputimes(user, nice, system, idle)
+        ret.append(item)
+    return ret
+
+
+def cpu_count_logical():
+    """Return the number of logical CPUs in the system."""
+    return cext.cpu_count_logical()
+
+
+def cpu_count_physical():
+    """Return the number of physical CPUs in the system."""
+    return cext.cpu_count_phys()
+
+
+def cpu_stats():
+    ctx_switches, interrupts, soft_interrupts, syscalls, traps = \
+        cext.cpu_stats()
+    return _common.scpustats(
+        ctx_switches, interrupts, soft_interrupts, syscalls)
+
+
+def cpu_freq():
+    """Return CPU frequency.
+    On OSX per-cpu frequency is not supported.
+    Also, the returned frequency never changes, see:
+    https://arstechnica.com/civis/viewtopic.php?f=19&t=465002
+    """
+    curr, min_, max_ = cext.cpu_freq()
+    return [_common.scpufreq(curr, min_, max_)]
+
+
+# =====================================================================
+# --- disks
+# =====================================================================
+
+
+disk_usage = _psposix.disk_usage
+disk_io_counters = cext.disk_io_counters
+
+
+def disk_partitions(all=False):
+    """Return mounted disk partitions as a list of namedtuples."""
+    retlist = []
+    partitions = cext.disk_partitions()
+    for partition in partitions:
+        device, mountpoint, fstype, opts = partition
+        if device == 'none':
+            device = ''
+        if not all:
+            if not os.path.isabs(device) or not os.path.exists(device):
+                continue
+        ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+        retlist.append(ntuple)
+    return retlist
+
+
+# =====================================================================
+# --- sensors
+# =====================================================================
+
+
+def sensors_battery():
+    """Return battery information.
+    """
+    try:
+        percent, minsleft, power_plugged = cext.sensors_battery()
+    except NotImplementedError:
+        # no power source - return None according to interface
+        return None
+    power_plugged = power_plugged == 1
+    if power_plugged:
+        secsleft = _common.POWER_TIME_UNLIMITED
+    elif minsleft == -1:
+        secsleft = _common.POWER_TIME_UNKNOWN
+    else:
+        secsleft = minsleft * 60
+    return _common.sbattery(percent, secsleft, power_plugged)
+
+
+# =====================================================================
+# --- network
+# =====================================================================
+
+
+net_io_counters = cext.net_io_counters
+net_if_addrs = cext_posix.net_if_addrs
+
+
+def net_connections(kind='inet'):
+    """System-wide network connections."""
+    # Note: on OSX this will fail with AccessDenied unless
+    # the process is owned by root.
+    ret = []
+    for pid in pids():
+        try:
+            cons = Process(pid).connections(kind)
+        except NoSuchProcess:
+            continue
+        else:
+            if cons:
+                for c in cons:
+                    c = list(c) + [pid]
+                    ret.append(_common.sconn(*c))
+    return ret
+
+
+def net_if_stats():
+    """Get NIC stats (isup, duplex, speed, mtu)."""
+    names = net_io_counters().keys()
+    ret = {}
+    for name in names:
+        mtu = cext_posix.net_if_mtu(name)
+        isup = cext_posix.net_if_flags(name)
+        duplex, speed = cext_posix.net_if_duplex_speed(name)
+        if hasattr(_common, 'NicDuplex'):
+            duplex = _common.NicDuplex(duplex)
+        ret[name] = _common.snicstats(isup, duplex, speed, mtu)
+    return ret
+
+
+# =====================================================================
+# --- other system functions
+# =====================================================================
+
+
+def boot_time():
+    """The system boot time expressed in seconds since the epoch."""
+    return cext.boot_time()
+
+
+def users():
+    """Return currently connected users as a list of namedtuples."""
+    retlist = []
+    rawlist = cext.users()
+    for item in rawlist:
+        user, tty, hostname, tstamp, pid = item
+        if tty == '~':
+            continue  # reboot or shutdown
+        if not tstamp:
+            continue
+        nt = _common.suser(user, tty or None, hostname or None, tstamp, pid)
+        retlist.append(nt)
+    return retlist
+
+
+# =====================================================================
+# --- processes
+# =====================================================================
+
+
+def pids():
+    ls = cext.pids()
+    if 0 not in ls:
+        # On certain OSX versions pids() C doesn't return PID 0 but
+        # "ps" does and the process is querable via sysctl():
+        # https://travis-ci.org/giampaolo/psutil/jobs/309619941
+        try:
+            Process(0).create_time()
+            ls.append(0)
+        except NoSuchProcess:
+            pass
+        except AccessDenied:
+            ls.append(0)
+    return ls
+
+
+pid_exists = _psposix.pid_exists
+
+
+def wrap_exceptions(fun):
+    """Decorator which translates bare OSError exceptions into
+    NoSuchProcess and AccessDenied.
+    """
+    @functools.wraps(fun)
+    def wrapper(self, *args, **kwargs):
+        try:
+            return fun(self, *args, **kwargs)
+        except OSError as err:
+            if err.errno == errno.ESRCH:
+                raise NoSuchProcess(self.pid, self._name)
+            if err.errno in (errno.EPERM, errno.EACCES):
+                raise AccessDenied(self.pid, self._name)
+            raise
+    return wrapper
+
+
+@contextlib.contextmanager
+def catch_zombie(proc):
+    """There are some poor C APIs which incorrectly raise ESRCH when
+    the process is still alive or it's a zombie, or even RuntimeError
+    (those who don't set errno). This is here in order to solve:
+    https://github.com/giampaolo/psutil/issues/1044
+    """
+    try:
+        yield
+    except (OSError, RuntimeError) as err:
+        if isinstance(err, RuntimeError) or err.errno == errno.ESRCH:
+            try:
+                # status() is not supposed to lie and correctly detect
+                # zombies so if it raises ESRCH it's true.
+                status = proc.status()
+            except NoSuchProcess:
+                raise err
+            else:
+                if status == _common.STATUS_ZOMBIE:
+                    raise ZombieProcess(proc.pid, proc._name, proc._ppid)
+                else:
+                    raise AccessDenied(proc.pid, proc._name)
+        else:
+            raise
+
+
+class Process(object):
+    """Wrapper class around underlying C implementation."""
+
+    __slots__ = ["pid", "_name", "_ppid"]
+
+    def __init__(self, pid):
+        self.pid = pid
+        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.
+        with catch_zombie(self):
+            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):
+        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):
+        with catch_zombie(self):
+            return cext.proc_exe(self.pid)
+
+    @wrap_exceptions
+    def cmdline(self):
+        with catch_zombie(self):
+            return cext.proc_cmdline(self.pid)
+
+    @wrap_exceptions
+    def environ(self):
+        with catch_zombie(self):
+            return parse_environ_block(cext.proc_environ(self.pid))
+
+    @wrap_exceptions
+    def ppid(self):
+        self._ppid = self._get_kinfo_proc()[kinfo_proc_map['ppid']]
+        return self._ppid
+
+    @wrap_exceptions
+    def cwd(self):
+        with catch_zombie(self):
+            return cext.proc_cwd(self.pid)
+
+    @wrap_exceptions
+    def uids(self):
+        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):
+        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 = self._get_kinfo_proc()[kinfo_proc_map['ttynr']]
+        tmap = _psposix.get_terminal_map()
+        try:
+            return tmap[tty_nr]
+        except KeyError:
+            return None
+
+    @wrap_exceptions
+    def memory_info(self):
+        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):
+        basic_mem = self.memory_info()
+        uss = cext.proc_memory_uss(self.pid)
+        return pfullmem(*basic_mem + (uss, ))
+
+    @wrap_exceptions
+    def cpu_times(self):
+        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, 0.0)
+
+    @wrap_exceptions
+    def create_time(self):
+        return self._get_kinfo_proc()[kinfo_proc_map['ctime']]
+
+    @wrap_exceptions
+    def num_ctx_switches(self):
+        # 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 self._get_pidtaskinfo()[pidtaskinfo_map['numthreads']]
+
+    @wrap_exceptions
+    def open_files(self):
+        if self.pid == 0:
+            return []
+        files = []
+        with catch_zombie(self):
+            rawlist = cext.proc_open_files(self.pid)
+        for path, fd in rawlist:
+            if isfile_strict(path):
+                ntuple = _common.popenfile(path, fd)
+                files.append(ntuple)
+        return files
+
+    @wrap_exceptions
+    def connections(self, kind='inet'):
+        if kind not in conn_tmap:
+            raise ValueError("invalid %r kind argument; choose between %s"
+                             % (kind, ', '.join([repr(x) for x in conn_tmap])))
+        families, types = conn_tmap[kind]
+        with catch_zombie(self):
+            rawlist = cext.proc_connections(self.pid, families, types)
+        ret = []
+        for item in rawlist:
+            fd, fam, type, laddr, raddr, status = item
+            status = TCP_STATUSES[status]
+            fam = sockfam_to_enum(fam)
+            type = socktype_to_enum(type)
+            if fam in (AF_INET, AF_INET6):
+                if laddr:
+                    laddr = _common.addr(*laddr)
+                if raddr:
+                    raddr = _common.addr(*raddr)
+            nt = _common.pconn(fd, fam, type, laddr, raddr, status)
+            ret.append(nt)
+        return ret
+
+    @wrap_exceptions
+    def num_fds(self):
+        if self.pid == 0:
+            return 0
+        with catch_zombie(self):
+            return cext.proc_num_fds(self.pid)
+
+    @wrap_exceptions
+    def wait(self, timeout=None):
+        return _psposix.wait_pid(self.pid, timeout, self._name)
+
+    @wrap_exceptions
+    def nice_get(self):
+        with catch_zombie(self):
+            return cext_posix.getpriority(self.pid)
+
+    @wrap_exceptions
+    def nice_set(self, value):
+        with catch_zombie(self):
+            return cext_posix.setpriority(self.pid, value)
+
+    @wrap_exceptions
+    def status(self):
+        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, '?')
+
+    @wrap_exceptions
+    def threads(self):
+        with catch_zombie(self):
+            rawlist = cext.proc_threads(self.pid)
+        retlist = []
+        for thread_id, utime, stime in rawlist:
+            ntuple = _common.pthread(thread_id, utime, stime)
+            retlist.append(ntuple)
+        return retlist
+
+    @wrap_exceptions
+    def memory_maps(self):
+        with catch_zombie(self):
+            return cext.proc_memory_maps(self.pid)
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/_psposix.py
@@ -0,0 +1,182 @@
+# 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.
+
+"""Routines common to all posix systems."""
+
+import errno
+import glob
+import os
+import sys
+import time
+
+from ._common import memoize
+from ._common import sdiskusage
+from ._common import usage_percent
+from ._compat import PY3
+from ._compat import unicode
+from ._exceptions import TimeoutExpired
+
+
+__all__ = ['pid_exists', 'wait_pid', 'disk_usage', 'get_terminal_map']
+
+
+def pid_exists(pid):
+    """Check whether pid exists in the current process table."""
+    if pid == 0:
+        # According to "man 2 kill" PID 0 has a special meaning:
+        # it refers to <<every process in the process group of the
+        # calling process>> so we don't want to go any further.
+        # If we get here it means this UNIX platform *does* have
+        # a process with id 0.
+        return True
+    try:
+        os.kill(pid, 0)
+    except OSError as err:
+        if err.errno == errno.ESRCH:
+            # ESRCH == No such process
+            return False
+        elif err.errno == errno.EPERM:
+            # EPERM clearly means there's a process to deny access to
+            return True
+        else:
+            # According to "man 2 kill" possible error values are
+            # (EINVAL, EPERM, ESRCH) therefore we should never get
+            # here. If we do let's be explicit in considering this
+            # an error.
+            raise err
+    else:
+        return True
+
+
+def wait_pid(pid, timeout=None, proc_name=None):
+    """Wait for process with pid 'pid' to terminate and return its
+    exit status code as an integer.
+
+    If pid is not a children of os.getpid() (current process) just
+    waits until the process disappears and return None.
+
+    If pid does not exist at all return None immediately.
+
+    Raise TimeoutExpired on timeout expired.
+    """
+    def check_timeout(delay):
+        if timeout is not None:
+            if timer() >= stop_at:
+                raise TimeoutExpired(timeout, pid=pid, name=proc_name)
+        time.sleep(delay)
+        return min(delay * 2, 0.04)
+
+    timer = getattr(time, 'monotonic', time.time)
+    if timeout is not None:
+        def waitcall():
+            return os.waitpid(pid, os.WNOHANG)
+        stop_at = timer() + timeout
+    else:
+        def waitcall():
+            return os.waitpid(pid, 0)
+
+    delay = 0.0001
+    while True:
+        try:
+            retpid, status = waitcall()
+        except OSError as err:
+            if err.errno == errno.EINTR:
+                delay = check_timeout(delay)
+                continue
+            elif err.errno == errno.ECHILD:
+                # This has two meanings:
+                # - pid is not a child of os.getpid() in which case
+                #   we keep polling until it's gone
+                # - pid never existed in the first place
+                # In both cases we'll eventually return None as we
+                # can't determine its exit status code.
+                while True:
+                    if pid_exists(pid):
+                        delay = check_timeout(delay)
+                    else:
+                        return
+            else:
+                raise
+        else:
+            if retpid == 0:
+                # WNOHANG was used, pid is still running
+                delay = check_timeout(delay)
+                continue
+            # process exited due to a signal; return the integer of
+            # that signal
+            if os.WIFSIGNALED(status):
+                return -os.WTERMSIG(status)
+            # process exited using exit(2) system call; return the
+            # integer exit(2) system call has been called with
+            elif os.WIFEXITED(status):
+                return os.WEXITSTATUS(status)
+            else:
+                # should never happen
+                raise ValueError("unknown process exit status %r" % status)
+
+
+def disk_usage(path):
+    """Return disk usage associated with path.
+    Note: UNIX usually reserves 5% disk space which is not accessible
+    by user. In this function "total" and "used" values reflect the
+    total and used disk space whereas "free" and "percent" represent
+    the "free" and "used percent" user disk space.
+    """
+    if PY3:
+        st = os.statvfs(path)
+    else:
+        # os.statvfs() does not support unicode on Python 2:
+        # - https://github.com/giampaolo/psutil/issues/416
+        # - http://bugs.python.org/issue18695
+        try:
+            st = os.statvfs(path)
+        except UnicodeEncodeError:
+            if isinstance(path, unicode):
+                try:
+                    path = path.encode(sys.getfilesystemencoding())
+                except UnicodeEncodeError:
+                    pass
+                st = os.statvfs(path)
+            else:
+                raise
+
+    # Total space which is only available to root (unless changed
+    # at system level).
+    total = (st.f_blocks * st.f_frsize)
+    # Remaining free space usable by root.
+    avail_to_root = (st.f_bfree * st.f_frsize)
+    # Remaining free space usable by user.
+    avail_to_user = (st.f_bavail * st.f_frsize)
+    # Total space being used in general.
+    used = (total - avail_to_root)
+    # Total space which is available to user (same as 'total' but
+    # for the user).
+    total_user = used + avail_to_user
+    # User usage percent compared to the total amount of space
+    # the user can use. This number would be higher if compared
+    # to root's because the user has less space (usually -5%).
+    usage_percent_user = usage_percent(used, total_user, _round=1)
+
+    # NB: the percentage is -5% than what shown by df due to
+    # reserved blocks that we are currently not considering:
+    # https://github.com/giampaolo/psutil/issues/829#issuecomment-223750462
+    return sdiskusage(
+        total=total, used=used, free=avail_to_user, percent=usage_percent_user)
+
+
+@memoize
+def get_terminal_map():
+    """Get a map of device-id -> path as a dict.
+    Used by Process.terminal()
+    """
+    ret = {}
+    ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*')
+    for name in ls:
+        assert name not in ret, name
+        try:
+            ret[os.stat(name).st_rdev] = name
+        except OSError as err:
+            if err.errno != errno.ENOENT:
+                raise
+    return ret
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil-cp27-none-win_amd64/psutil/_pssunos.py
@@ -0,0 +1,725 @@
+# 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.
+
+"""Sun OS Solaris platform implementation."""
+
+import errno
+import os
+import socket
+import subprocess
+import sys
+from collections import namedtuple
+from socket import AF_INET
+
+from . import _common
+from . import _psposix
+from . import _psutil_posix as cext_posix
+from . import _psutil_sunos as cext
+from ._common import AF_INET6
+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
+from ._compat import b
+from ._compat import PY3
+from ._exceptions import AccessDenied
+from ._exceptions import NoSuchProcess
+from ._exceptions import ZombieProcess
+
+
+__extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"]
+
+
+# =====================================================================
+# --- globals
+# =====================================================================
+
+
+PAGE_SIZE = os.sysconf('SC_PAGE_SIZE')
+AF_LINK = cext_posix.AF_LINK
+IS_64_BIT = sys.maxsize > 2**32
+
+CONN_IDLE = "IDLE"
+CONN_BOUND = "BOUND"
+
+PROC_STATUSES = {
+    cext.SSLEEP: _common.STATUS_SLEEPING,
+    cext.SRUN: _common.STATUS_RUNNING,
+    cext.SZOMB: _common.STATUS_ZOMBIE,
+    cext.SSTOP: _common.STATUS_STOPPED,
+    cext.SIDL: _common.STATUS_IDLE,
+    cext.SONPROC: _common.STATUS_RUNNING,  # same as run
+    cext.SWAIT: _common.STATUS_WAITING,
+}
+
+TCP_STATUSES = {
+    cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
+    cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
+    cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
+    cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
+    cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
+    cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
+    cext.TCPS_CLOSED: _common.CONN_CLOSE,
+    cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
+    cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
+    cext.TCPS_LISTEN: _common.CONN_LISTEN,
+    cext.TCPS_CLOSING: _common.CONN_CLOSING,
+    cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
+    cext.TCPS_IDLE: CONN_IDLE,  # sunos specific
+    cext.TCPS_BOUND: CONN_BOUND,  # sunos specific
+}
+
+proc_info_map = dict(
+    ppid=0,
+    rss=1,
+    vms=2,
+    create_time=3,
+    nice=4,
+    num_threads=5,
+    status=6,
+    ttynr=7)
+
+
+# =====================================================================
+# --- named tuples
+# =====================================================================
+
+
+# psutil.cpu_times()
+scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
+# psutil.cpu_times(percpu=True)
+pcputimes = namedtuple('pcputimes',
+                       ['user', 'system', 'children_user', 'children_system'])
+# psutil.virtual_memory()
+svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
+# psutil.Process.memory_info()
+pmem = namedtuple('pmem', ['rss', 'vms'])
+pfullmem = pmem
+# psutil.Process.memory_maps(grouped=True)
+pmmap_grouped = namedtuple('pmmap_grouped',
+                           ['path', 'rss', 'anonymous', 'locked'])
+# psutil.Process.memory_maps(grouped=False)
+pmmap_ext = namedtuple(
+    'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
+
+
+# =====================================================================
+# --- utils
+# =====================================================================
+
+
+def get_procfs_path():
+    """Return updated psutil.PROCFS_PATH constant."""
+    return sys.modules['psutil'].PROCFS_PATH
+
+
+# =====================================================================
+# --- memory
+# =====================================================================
+
+
+def virtual_memory():
+    """Report virtual memory metrics."""
+    # we could have done this with kstat, but IMHO this is good enough
+    total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE
+    # note: there's no difference on Solaris
+    free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE
+    used = total - free
+    percent = usage_percent(used, total, _round=1)
+    return svmem(total, avail, percent, used, free)
+
+
+def swap_memory():
+    """Report swap memory metrics."""
+    sin, sout = cext.swap_mem()
+    # XXX
+    # we are supposed to get total/free by doing so:
+    # http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/
+    #     usr/src/cmd/swap/swap.c
+    # ...nevertheless I can't manage to obtain the same numbers as 'swap'
+    # cmdline utility, so let's parse its output (sigh!)
+    p = subprocess.Popen(['/usr/bin/env', 'PATH=/usr/sbin:/sbin:%s' %
+                          os.environ['PATH'], 'swap', '-l'],
+                         stdout=subprocess.PIPE)
+    stdout, stderr = p.communicate()
+    if PY3:
+        stdout = stdout.decode(sys.stdout.encoding)
+    if p.returncode != 0:
+        raise RuntimeError("'swap -l' failed (retcode=%s)" % p.returncode)
+
+    lines = stdout.strip().split('\n')[1:]
+    if not lines:
+        raise RuntimeError('no swap device(s) configured')
+    total = free = 0
+    for line in lines:
+        line = line.split()
+        t, f = line[-2:]
+        total += int(int(t) * 512)
+        free += int(int(f) * 512)
+    used = total - free
+    percent = usage_percent(used, total, _round=1)
+    return _common.sswap(total, used, free, percent,
+                         sin * PAGE_SIZE, sout * PAGE_SIZE)
+
+
+# =====================================================================
+# --- CPU
+# =====================================================================
+
+
+def cpu_times():
+    """Return system-wide CPU times as a named tuple"""
+    ret = cext.per_cpu_times()
+    return scputimes(*[sum(x) for x in zip(*ret)])
+
+
+def per_cpu_times():
+    """Return system per-CPU times as a list of named tuples"""
+    ret = cext.per_cpu_times()
+    return [scputimes(*x) for x in ret]
+
+
+def cpu_count_logical():
+    """Return the number of logical CPUs in the system."""
+    try:
+        return os.sysconf("SC_NPROCESSORS_ONLN")
+    except ValueError:
+        # mimic os.cpu_count() behavior
+        return None
+
+
+def cpu_count_physical():
+    """Return the number of physical CPUs in the system."""
+    return cext.cpu_count_phys()
+
+
+def cpu_stats():
+    """Return various CPU stats as a named tuple."""
+    ctx_switches, interrupts, syscalls, traps = cext.cpu_stats()
+    soft_interrupts = 0
+    return _common.scpustats(ctx_switches, interrupts, soft_interrupts,
+                             syscalls)
+
+
+# =====================================================================
+# --- disks
+# =====================================================================
+
+
+disk_io_counters = cext.disk_io_counters
+disk_usage = _psposix.disk_usage
+
+
+def disk_partitions(all=False):
+    """Return system disk partitions."""
+    # TODO - the filtering logic should be better checked so that
+    # it tries to reflect 'df' as much as possible
+    retlist = []
+    partitions = cext.disk_partitions()
+    for partition in partitions:
+        device, mountpoint, fstype, opts = partition
+        if device == 'none':
+            device = ''
+        if not all:
+            # Differently from, say, Linux, we don't have a list of
+            # common fs types so the best we can do, AFAIK, is to
+            # filter by filesystem having a total size > 0.
+            if not disk_usage(mountpoint).total:
+                continue
+        ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
+        retlist.append(ntuple)
+    return retlist
+
+
+# =====================================================================
+# --- network
+# =====================================================================
+
+
+net_io_counters = cext.net_io_counters
+net_if_addrs = cext_posix.net_if_addrs
+
+
+def net_connections(kind, _pid=-1):
+    """Return socket connections.  If pid == -1 return system-wide
+    connections (as opposed to connections opened by one process only).
+    Only INET sockets are returned (UNIX are not).
+    """
+    cmap = _common.conn_tmap.copy()
+    if _pid == -1:
+        cmap.pop('unix', 0)
+    if kind not in cmap:
+        raise ValueError("invalid %r kind argument; choose between %s"
+                         % (kind, ', '.join([repr(x) for x in cmap])))
+    families, types = _common.conn_tmap[kind]
+    rawlist = cext.net_connections(_pid)
+    ret = set()
+    for item in rawlist:
+        fd, fam, type_, laddr, raddr, status, pid = item
+        if fam not in families:
+            continue
+        if type_ not in types:
+            continue
+        if fam in (AF_INET, AF_INET6):
+            if laddr:
+                laddr = _common.addr(*laddr)
+            if raddr:
+                raddr = _common.addr(*raddr)
+        status = TCP_STATUSES[status]
+        fam = sockfam_to_enum(fam)
+        type_ = socktype_to_enum(type_)
+        if _pid == -1:
+            nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid)
+        else:
+            nt = _common.pconn(fd, fam, type_, laddr, raddr, status)
+        ret.add(nt)
+    return list(ret)
+
+
+def net_if_stats():
+    """Get NIC stats (isup, duplex, speed, mtu)."""
+    ret = cext.net_if_stats()
+    for name, items in ret.items():
+        isup, duplex, speed, mtu = items
+        if hasattr(_common, 'NicDuplex'):
+            duplex = _common.NicDuplex(duplex)
+        ret[name] = _common.snicstats(isup, duplex, speed, mtu)
+    return ret
+
+
+# =====================================================================
+# --- other system functions
+# =====================================================================
+
+
+def boot_time():
+    """The system boot time expressed in seconds since the epoch."""
+    return cext.boot_time()
+
+
+def users():
+    """Return currently connected users as a list of namedtuples."""
+    retlist = []
+    rawlist = cext.users()
+    localhost = (':0.0', ':0')
+    for item in rawlist:
+        user, tty, hostname, tstamp, user_process, pid = item
+        # note: the underlying C function includes entries about
+        # system boot, run level and others.  We might want
+        # to use them in the future.
+        if not user_process:
+            continue
+        if hostname in localhost:
+            hostname = 'localhost'
+        nt = _common.suser(user, tty, hostname, tstamp, pid)
+        retlist.append(nt)
+    return retlist
+
+
+# =====================================================================
+# --- processes
+# =====================================================================
+
+
+def pids():
+    """Returns a list of PIDs currently running on the system."""
+    return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()]
+
+
+def pid_exists(pid):
+    """Check for the existence of a unix pid."""
+    return _psposix.pid_exists(pid)
+
+
+def wrap_exceptions(fun):
+    """Call callable into a try/except clause and translate ENOENT,
+    EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
+    """
+
+    def wrapper(self, *args, **kwargs):
+        try:
+            return fun(self, *args, **kwargs)
+        except EnvironmentError as err:
+            if self.pid == 0:
+                if 0 in pids():
+                    raise AccessDenied(self.pid, self._name)
+                else:
+                    raise
+            # ENOENT (no such file or directory) gets raised on open().
+            # ESRCH (no such process) can get raised on read() if
+            # process is gone in meantime.
+            if err.errno in (errno.ENOENT, errno.ESRCH):
+                if not pid_exists(self.pid):
+                    raise NoSuchProcess(self.pid, self._name)
+                else:
+                    raise ZombieProcess(self.pid, self._name, self._ppid)
+            if err.errno in (errno.EPERM, errno.EACCES):
+                raise AccessDenied(self.pid, self._name)
+            raise
+    return wrapper
+
+
+class Process(object):
+    """Wrapper class around underlying C implementation."""
+
+    __slots__ = ["pid", "_name", "_ppid", "_procfs_path"]
+
+    def __init__(self, pid):
+        self.pid = pid
+        self._name = None
+        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):
+        ret = cext.proc_basic_info(self.pid, self._procfs_path)
+        assert len(ret) == len(proc_info_map)
+        return ret
+
+    @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 self._proc_name_and_args()[0]
+
+    @wrap_exceptions
+    def exe(self):
+        try:
+            return os.readlink(
+                "%s/%s/path/a.out" % (self._procfs_path, self.pid))
+        except OSError:
+            pass    # continue and guess the exe name from the cmdline
+        # Will be guessed later from cmdline but we want to explicitly
+        # invoke cmdline here in order to get an AccessDenied
+        # exception if the user has not enough privileges.
+        self.cmdline()
+        return ""
+
+    @wrap_exceptions
+    def cmdline(self):
+        return self._proc_name_and_args()[1].split(' ')
+
+    @wrap_exceptions
+    def environ(self):
+        return cext.proc_environ(self.pid, self._procfs_path)
+
+    @wrap_exceptions
+    def create_time(self):
+        return self._proc_basic_info()[proc_info_map['create_time']]
+
+    @wrap_exceptions
+    def num_threads(self):
+        return self._proc_basic_info()[proc_info_map['num_threads']]
+
+    @wrap_exceptions
+    def nice_get(self):
+        # Note #1: for some reason getpriority(3) return ESRCH (no such
+        # process) for certain low-pid processes, no matter what (even
+        # as root).
+        # The process actually exists though, as it has a name,
+        # creation time, etc.
+        # The best thing we can do here appears to be raising AD.
+        # Note: tested on Solaris 11; on Open Solaris 5 everything is
+        # fine.
+        #
+        # Note #2: we also can get niceness from /proc/pid/psinfo
+        # but it's wrong, see:
+        # https://github.com/giampaolo/psutil/issues/1082
+        try:
+            return cext_posix.getpriority(self.pid)
+        except EnvironmentError as err:
+            # 48 is 'operation not supported' but errno does not expose
+            # it. It occurs for low system pids.
+            if err.errno in (errno.ENOENT, errno.ESRCH, 48):
+                if pid_exists(self.pid):
+                    raise AccessDenied(self.pid, self._name)
+            raise
+
+    @wrap_exceptions
+    def nice_set(self, value):
+        if self.pid in (2, 3):
+            # Special case PIDs: internally setpriority(3) return ESRCH
+            # (no such process), no matter what.
+            # The process actually exists though, as it has a name,
+            # creation time, etc.
+            raise AccessDenied(self.pid, self._name)
+        return cext_posix.setpriority(self.pid, value)
+
+    @wrap_exceptions
+    def ppid(self):
+        self._ppid = self._proc_basic_info()[proc_info_map['ppid']]
+        return self._ppid
+
+    @wrap_exceptions
+    def uids(self):
+        real, effective, saved, _, _, _ = self._proc_cred()
+        return _common.puids(real, effective, saved)
+
+    @wrap_exceptions
+    def gids(self):
+        _, _, _, real, effective, saved = self._proc_cred()
+        return _common.puids(real, effective, saved)
+
+    @wrap_exceptions
+    def cpu_times(self):
+        try:
+            times = cext.proc_cpu_times(self.pid, self._procfs_path)
+        except OSError as err:
+            if err.errno == errno.EOVERFLOW and not IS_64_BIT:
+                # We may get here if we attempt to query a 64bit process
+                # with a 32bit python.
+                # Error originates from read() and also tools like "cat"
+                # fail in the same way (!).
+                # Since there simply is no way to determine CPU times we
+                # return 0.0 as a fallback. See:
+                # https://github.com/giampaolo/psutil/issues/857
+                times = (0.0, 0.0, 0.0, 0.0)
+            else:
+                raise
+        return _common.pcputimes(*times)
+
+    @wrap_exceptions
+    def cpu_num(self):
+        return cext.proc_cpu_num(self.pid, self._procfs_path)
+
+    @wrap_exceptions
+    def terminal(self):
+        procfs_path = self._procfs_path
+        hit_enoent = False
+        tty = wrap_exceptions(
+            self._proc_basic_info()[proc_info_map['ttynr']])
+        if tty != cext.PRNODEV:
+            for x in (0, 1, 2, 255):
+                try:
+                    return os.readlink(
+                        '%s/%d/path/%d' % (procfs_path, self.pid, x))
+                except OSError as err:
+                    if err.errno == errno.ENOENT:
+                        hit_enoent = True
+                        continue
+                    raise
+        if hit_enoent:
+            # raise NSP if the process disappeared on us
+            os.stat('%s/%s' % (procfs_path, self.pid))
+
+    @wrap_exceptions
+    def cwd(self):
+        # /proc/PID/path/cwd may not be resolved by readlink() even if
+        # it exists (ls shows it). If that's the case and the process
+        # is still alive return None (we can return None also on BSD).
+        # Reference: http://goo.gl/55XgO
+        procfs_path = self._procfs_path
+        try:
+            return os.readlink("%s/%s/path/cwd" % (procfs_path, self.pid))
+        except OSError as err:
+            if err.errno == errno.ENOENT:
+                os.stat("%s/%s" % (procfs_path, self.pid))  # raise NSP or AD
+                return None
+            raise
+
+    @wrap_exceptions
+    def memory_info(self):
+        ret = self._proc_basic_info()
+        rss = ret[proc_info_map['rss']] * 1024
+        vms = ret[proc_info_map['vms']] * 1024
+        return pmem(rss, vms)
+
+    memory_full_info = memory_info
+
+    @wrap_exceptions
+    def status(self):
+        code = self._proc_basic_info()[proc_info_map['status']]
+        # XXX is '?' legit? (we're not supposed to return it anyway)
+        return PROC_STATUSES.get(code, '?')
+
+    @wrap_exceptions
+    def threads(self):
+        procfs_path = self._procfs_path
+        ret = []
+        tids = os.listdir('%s/%d/lwp' % (procfs_path, self.pid))
+        hit_enoent = False
+        for tid in tids:
+            tid = int(tid)
+            try:
+                utime, stime = cext.query_process_thread(
+                    self.pid, tid, procfs_path)
+            except EnvironmentError as err:
+                if err.errno == errno.EOVERFLOW and not IS_64_BIT:
+                    # We may get here if we attempt to query a 64bit process
+                    # with a 32bit python.
+                    # Error originates from read() and also tools like "cat"
+                    # fail in the same way (!).
+                    # Since there simply is no way to determine CPU times we
+                    # return 0.0 as a fallback. See:
+                    # https://github.com/giampaolo/psutil/issues/857
+                    continue
+                # ENOENT == thread gone in meantime
+                if err.errno == errno.ENOENT:
+                    hit_enoent = True
+                    continue
+                raise
+            else:
+                nt = _common.pthread(tid, utime, stime)
+                ret.append(nt)
+        if hit_enoent:
+            # raise NSP if the process disappeared on us
+            os.stat('%s/%s' % (procfs_path, self.pid))
+        return ret
+
+    @wrap_exceptions
+    def open_files(self):
+        retlist = []
+        hit_enoent = False
+        procfs_path = self._procfs_path
+        pathdir = '%s/%d/path' % (procfs_path, self.pid)
+        for fd in os.listdir('%s/%d/fd' % (procfs_path, self.pid)):
+            path = os.path.join(pathdir, fd)
+            if os.path.islink(path):
+                try:
+                    file = os.readlink(path)
+                except OSError as err:
+                    # ENOENT == file which is gone in the meantime
+                    if err.errno == errno.ENOENT:
+                        hit_enoent = True
+                        continue
+                    raise
+                else:
+                    if isfile_strict(file):
+                        retlist.append(_common.popenfile(file, int(fd)))
+        if hit_enoent:
+            # raise NSP if the process disappeared on us
+            os.stat('%s/%s' % (procfs_path, self.pid))
+        return retlist
+
+    def _get_unix_sockets(self, pid):
+        """Get UNIX sockets used by process by parsing 'pfiles' output."""
+        # TODO: rewrite this in C (...but the damn netstat source code
+        # does not include this part! Argh!!)
+        cmd = "pfiles %s" % pid
+        p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        stdout, stderr = p.communicate()
+        if PY3:
+            stdout, stderr = [x.decode(sys.stdout.encoding)
+                              for x in (stdout, stderr)]
+        if p.returncode != 0:
+            if 'permission denied' in stderr.lower():
+                raise AccessDenied(self.pid, self._name)
+            if 'no such process' in stderr.lower():
+                raise NoSuchProcess(self.pid, self._name)
+            raise RuntimeError("%r command error\n%s" % (cmd, stderr))
+
+        lines = stdout.split('\n')[2:]
+        for i, line in enumerate(lines):
+            line = line.lstrip()
+            if line.startswith('sockname: AF_UNIX'):
+                path = line.split(' ', 2)[2]
+                type = lines[i - 2].strip()
+                if type == 'SOCK_STREAM':
+                    type = socket.SOCK_STREAM
+                elif type == 'SOCK_DGRAM':
+                    type = socket.SOCK_DGRAM
+                else:
+                    type = -1
+                yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE)
+
+    @wrap_exceptions
+    def connections(self, kind='inet'):
+        ret = net_connections(kind, _pid=self.pid)
+        # The underlying C implementation retrieves all OS connections
+        # and filters them by PID.  At this point we can't tell whether
+        # an empty list means there were no connections for process or
+        # process is no longer active so we force NSP in case the PID
+        # is no longer there.
+        if not ret:
+            # will raise NSP if process is gone
+            os.stat('%s/%s' % (self._procfs_path, self.pid))
+