bug 1481612 - Re-vendor psutil 5.4.3 using `mach vendor python`. r=chmanchester,gps
authorTed Mielczarek <ted@mielczarek.org>
Wed, 10 Oct 2018 19:47:58 +0000
changeset 499054 aafdbf2213ec4463e830f810dbb71016f5e49c5f
parent 499053 161743a69d9a74f2cc5ea7cfc2e8f65c56a4ec2a
child 499055 60407ad1392236779a537033965c59e3170416f3
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschmanchester, gps
bugs1481612
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1481612 - Re-vendor psutil 5.4.3 using `mach vendor python`. r=chmanchester,gps psutil was previously vendored manually, so re-vendor it using our new tooling to make for smaller diffs in follow-up patches. This mostly just winds up adding some extra files that must have been left out of the manual vendoring, but it also updates the third_party/python/requirements.{in,txt}. Differential Revision: https://phabricator.services.mozilla.com/D3434
third_party/python/psutil/.coveragerc
third_party/python/psutil/.git-pre-commit
third_party/python/psutil/.gitignore
third_party/python/psutil/psutil/_psutil_aix.c
third_party/python/psutil/psutil/tests/__main__.py
third_party/python/psutil/psutil/tests/test_aix.py
third_party/python/psutil/psutil/tests/test_bsd.py
third_party/python/psutil/psutil/tests/test_connections.py
third_party/python/psutil/psutil/tests/test_contracts.py
third_party/python/psutil/psutil/tests/test_linux.py
third_party/python/psutil/psutil/tests/test_memory_leaks.py
third_party/python/psutil/psutil/tests/test_misc.py
third_party/python/psutil/psutil/tests/test_osx.py
third_party/python/psutil/psutil/tests/test_posix.py
third_party/python/psutil/psutil/tests/test_process.py
third_party/python/psutil/psutil/tests/test_sunos.py
third_party/python/psutil/psutil/tests/test_system.py
third_party/python/psutil/psutil/tests/test_unicode.py
third_party/python/psutil/psutil/tests/test_windows.py
third_party/python/psutil/scripts/battery.py
third_party/python/psutil/scripts/cpu_distribution.py
third_party/python/psutil/scripts/disk_usage.py
third_party/python/psutil/scripts/fans.py
third_party/python/psutil/scripts/free.py
third_party/python/psutil/scripts/ifconfig.py
third_party/python/psutil/scripts/internal/bench_oneshot.py
third_party/python/psutil/scripts/internal/check_broken_links.py
third_party/python/psutil/scripts/internal/download_exes.py
third_party/python/psutil/scripts/internal/generate_manifest.py
third_party/python/psutil/scripts/internal/print_announce.py
third_party/python/psutil/scripts/internal/winmake.py
third_party/python/psutil/scripts/iotop.py
third_party/python/psutil/scripts/killall.py
third_party/python/psutil/scripts/meminfo.py
third_party/python/psutil/scripts/netstat.py
third_party/python/psutil/scripts/nettop.py
third_party/python/psutil/scripts/pidof.py
third_party/python/psutil/scripts/pmap.py
third_party/python/psutil/scripts/procinfo.py
third_party/python/psutil/scripts/procsmem.py
third_party/python/psutil/scripts/ps.py
third_party/python/psutil/scripts/pstree.py
third_party/python/psutil/scripts/sensors.py
third_party/python/psutil/scripts/temperatures.py
third_party/python/psutil/scripts/top.py
third_party/python/psutil/scripts/who.py
third_party/python/psutil/scripts/winservices.py
third_party/python/requirements.in
third_party/python/requirements.txt
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil/.coveragerc
@@ -0,0 +1,32 @@
+[report]
+
+include =
+    *psutil*
+omit =
+    psutil/_compat.py
+    psutil/tests/*
+    setup.py
+exclude_lines =
+    enum.IntEnum
+    except ImportError:
+    globals().update
+    if __name__ == .__main__.:
+    if _WINDOWS:
+    if BSD
+    if enum is None:
+    if enum is not None:
+    if FREEBSD
+    if has_enums:
+    if LINUX
+    if LITTLE_ENDIAN:
+    if NETBSD
+    if OPENBSD
+    if OSX
+    if ppid_map is None:
+    if PY3:
+    if SUNOS
+    if sys.platform.startswith
+    if WINDOWS
+    import enum
+    pragma: no cover
+    raise NotImplementedError
new file mode 100755
--- /dev/null
+++ b/third_party/python/psutil/.git-pre-commit
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+This gets executed on 'git commit' and rejects the commit in case the
+submitted code does not pass validation. Validation is run only against
+the *.py files which were modified in the commit. Checks:
+
+- assert no space at EOLs
+- assert not pdb.set_trace in code
+- assert no bare except clause ("except:") in code
+- assert "flake8" returns no warnings
+
+Install this with "make install-git-hooks".
+"""
+
+from __future__ import print_function
+import os
+import subprocess
+import sys
+
+
+def term_supports_colors():
+    try:
+        import curses
+        assert sys.stderr.isatty()
+        curses.setupterm()
+        assert curses.tigetnum("colors") > 0
+    except Exception:
+        return False
+    else:
+        return True
+
+
+def hilite(s, ok=True, bold=False):
+    """Return an highlighted version of 'string'."""
+    if not term_supports_colors():
+        return s
+    attr = []
+    if ok is None:  # no color
+        pass
+    elif ok:   # green
+        attr.append('32')
+    else:   # red
+        attr.append('31')
+    if bold:
+        attr.append('1')
+    return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
+
+
+def exit(msg):
+    msg = hilite(msg, ok=False)
+    print(msg, file=sys.stderr)
+    sys.exit(1)
+
+
+def sh(cmd):
+    """run cmd in a subprocess and return its output.
+    raises RuntimeError on error.
+    """
+    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
+                         stderr=subprocess.PIPE, universal_newlines=True)
+    stdout, stderr = p.communicate()
+    if p.returncode != 0:
+        raise RuntimeError(stderr)
+    if stderr:
+        print(stderr, file=sys.stderr)
+    if stdout.endswith('\n'):
+        stdout = stdout[:-1]
+    return stdout
+
+
+def main():
+    out = sh("git diff --cached --name-only")
+    py_files = [x for x in out.split('\n') if x.endswith('.py') and
+                os.path.exists(x)]
+
+    lineno = 0
+    for path in py_files:
+        with open(path) as f:
+            for line in f:
+                lineno += 1
+                # space at end of line
+                if line.endswith(' '):
+                    print("%s:%s %r" % (path, lineno, line))
+                    return exit(
+                        "commit aborted: space at end of line")
+                line = line.rstrip()
+                # pdb
+                if "pdb.set_trace" in line:
+                    print("%s:%s %s" % (path, lineno, line))
+                    return exit(
+                        "commit aborted: you forgot a pdb in your python code")
+                # bare except clause
+                if "except:" in line and not line.endswith("# NOQA"):
+                    print("%s:%s %s" % (path, lineno, line))
+                    return exit("commit aborted: bare except clause")
+
+    # flake8
+    if py_files:
+        try:
+            import flake8  # NOQA
+        except ImportError:
+            return exit("commit aborted: flake8 is not installed; "
+                        "run 'make setup-dev-env'")
+
+        # XXX: we should scape spaces and possibly other amenities here
+        ret = subprocess.call(
+            "%s -m flake8 %s" % (sys.executable, " ".join(py_files)),
+            shell=True)
+        if ret != 0:
+            return exit("commit aborted: python code is not flake8 compliant")
+
+
+main()
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil/.gitignore
@@ -0,0 +1,18 @@
+syntax: glob
+*.al
+*.bak
+*.egg-info
+*.la
+*.lo
+*.o
+*.orig
+*.pyc
+*.pyd
+*.rej
+*.so
+*.swp
+.cache/
+.idea/
+.tox/
+build/
+dist/
new file mode 100644
--- /dev/null
+++ b/third_party/python/psutil/psutil/_psutil_aix.c
@@ -0,0 +1,988 @@
+/*
+ * 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 support is experimental at this time.
+ * The following functions and methods are unsupported on the AIX platform:
+ * - psutil.Process.memory_maps
+ *
+ * Known limitations:
+ * - psutil.Process.io_counters read count is always 0
+ * - psutil.Process.threads may not be available on older AIX versions
+ * - reading basic process info may fail or return incorrect values when
+ *   process is starting (see IBM APAR IV58499 - fixed in newer AIX versions)
+ * - sockets and pipes may not be counted in num_fds (fixed in newer AIX
+ *    versions)
+ *
+ * Useful resources:
+ * - proc filesystem: http://www-01.ibm.com/support/knowledgecenter/
+ *       ssw_aix_61/com.ibm.aix.files/proc.htm
+ * - libperfstat: http://www-01.ibm.com/support/knowledgecenter/
+ *       ssw_aix_61/com.ibm.aix.files/libperfstat.h.htm
+ */
+
+#include <Python.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/proc.h>
+#include <sys/sysinfo.h>
+#include <sys/procfs.h>
+#include <sys/socket.h>
+#include <sys/thread.h>
+#include <fcntl.h>
+#include <utmp.h>
+#include <utmpx.h>
+#include <mntent.h>
+#include <sys/ioctl.h>
+#include <sys/tihdr.h>
+#include <stropts.h>
+#include <netinet/tcp_fsm.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <libperfstat.h>
+
+#include "arch/aix/ifaddrs.h"
+#include "arch/aix/net_connections.h"
+#include "arch/aix/common.h"
+#include "_psutil_common.h"
+#include "_psutil_posix.h"
+
+
+#define TV2DOUBLE(t)   (((t).tv_nsec * 0.000000001) + (t).tv_sec)
+
+/*
+ * Read a file content and fills a C structure with it.
+ */
+int
+psutil_file_to_struct(char *path, void *fstruct, size_t size) {
+    int fd;
+    size_t nbytes;
+    fd = open(path, O_RDONLY);
+    if (fd == -1) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
+        return 0;
+    }
+    nbytes = read(fd, fstruct, size);
+    if (nbytes <= 0) {
+        close(fd);
+        PyErr_SetFromErrno(PyExc_OSError);
+        return 0;
+    }
+    if (nbytes != size) {
+        close(fd);
+        PyErr_SetString(PyExc_RuntimeError, "structure size mismatch");
+        return 0;
+    }
+    close(fd);
+    return nbytes;
+}
+
+
+/*
+ * Return process ppid, rss, vms, ctime, nice, nthreads, status and tty
+ * as a Python tuple.
+ */
+static PyObject *
+psutil_proc_basic_info(PyObject *self, PyObject *args) {
+    int pid;
+    char path[100];
+    psinfo_t info;
+    pstatus_t status;
+    const char *procfs_path;
+
+    if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path))
+        return NULL;
+
+    sprintf(path, "%s/%i/psinfo", procfs_path, pid);
+    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
+        return NULL;
+
+    if (info.pr_nlwp == 0 && info.pr_lwp.pr_lwpid == 0) {
+        // From the /proc docs: "If the process is a zombie, the pr_nlwp
+        // and pr_lwp.pr_lwpid flags are zero."
+        status.pr_stat = SZOMB;
+    } else if (info.pr_flag & SEXIT) {
+        // "exiting" processes don't have /proc/<pid>/status
+        // There are other "exiting" processes that 'ps' shows as "active"
+        status.pr_stat = SACTIVE;
+    } else {
+        sprintf(path, "%s/%i/status", procfs_path, pid);
+        if (! psutil_file_to_struct(path, (void *)&status, sizeof(status)))
+            return NULL;
+    }
+
+    return Py_BuildValue("KKKdiiiK",
+        (unsigned long long) info.pr_ppid,      // parent pid
+        (unsigned long long) info.pr_rssize,    // rss
+        (unsigned long long) info.pr_size,      // vms
+        TV2DOUBLE(info.pr_start),               // create time
+        (int) info.pr_lwp.pr_nice,              // nice
+        (int) info.pr_nlwp,                     // no. of threads
+        (int) status.pr_stat,                   // status code
+        (unsigned long long)info.pr_ttydev      // tty nr
+        );
+}
+
+
+/*
+ * Return process name and args as a Python tuple.
+ */
+static PyObject *
+psutil_proc_name_and_args(PyObject *self, PyObject *args) {
+    int pid;
+    char path[100];
+    psinfo_t info;
+    const char *procfs_path;
+    PyObject *py_name = NULL;
+    PyObject *py_args = NULL;
+    PyObject *py_retlist = NULL;
+
+    if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path))
+        return NULL;
+    sprintf(path, "%s/%i/psinfo", procfs_path, pid);
+    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
+        return NULL;
+
+    py_name = PyUnicode_DecodeFSDefault(info.pr_fname);
+    if (!py_name)
+        goto error;
+    py_args = PyUnicode_DecodeFSDefault(info.pr_psargs);
+    if (!py_args)
+        goto error;
+    py_retlist = Py_BuildValue("OO", py_name, py_args);
+    if (!py_retlist)
+        goto error;
+    Py_DECREF(py_name);
+    Py_DECREF(py_args);
+    return py_retlist;
+
+error:
+    Py_XDECREF(py_name);
+    Py_XDECREF(py_args);
+    Py_XDECREF(py_retlist);
+    return NULL;
+}
+
+
+#ifdef CURR_VERSION_THREAD
+/*
+ * Retrieves all threads used by process returning a list of tuples
+ * including thread id, user time and system time.
+ */
+static PyObject *
+psutil_proc_threads(PyObject *self, PyObject *args) {
+    long pid;
+    PyObject *py_retlist = PyList_New(0);
+    PyObject *py_tuple = NULL;
+    perfstat_thread_t *threadt = NULL;
+    perfstat_id_t id;
+    int i, rc, thread_count;
+
+    if (py_retlist == NULL)
+        return NULL;
+    if (! PyArg_ParseTuple(args, "l", &pid))
+        goto error;
+
+    /* Get the count of threads */
+    thread_count = perfstat_thread(NULL, NULL, sizeof(perfstat_thread_t), 0);
+    if (thread_count <= 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+
+    /* Allocate enough memory */
+    threadt = (perfstat_thread_t *)calloc(thread_count,
+        sizeof(perfstat_thread_t));
+    if (threadt == NULL) {
+        PyErr_NoMemory();
+        goto error;
+    }
+
+    strcpy(id.name, "");
+    rc = perfstat_thread(&id, threadt, sizeof(perfstat_thread_t),
+        thread_count);
+    if (rc <= 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+
+    for (i = 0; i < thread_count; i++) {
+        if (threadt[i].pid != pid)
+            continue;
+
+        py_tuple = Py_BuildValue("Idd",
+                                 threadt[i].tid,
+                                 threadt[i].ucpu_time,
+                                 threadt[i].scpu_time);
+        if (py_tuple == NULL)
+            goto error;
+        if (PyList_Append(py_retlist, py_tuple))
+            goto error;
+        Py_DECREF(py_tuple);
+    }
+    free(threadt);
+    return py_retlist;
+
+error:
+    Py_XDECREF(py_tuple);
+    Py_DECREF(py_retlist);
+    if (threadt != NULL)
+        free(threadt);
+    return NULL;
+}
+#endif
+
+
+static PyObject *
+psutil_proc_io_counters(PyObject *self, PyObject *args) {
+    long pid;
+    int rc;
+    perfstat_process_t procinfo;
+    perfstat_id_t id;
+    if (! PyArg_ParseTuple(args, "l", &pid))
+        return NULL;
+
+    snprintf(id.name, sizeof(id.name), "%ld", pid);
+    rc = perfstat_process(&id, &procinfo, sizeof(perfstat_process_t), 1);
+    if (rc <= 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+
+    return Py_BuildValue("(KKKK)",
+                         procinfo.inOps,      // XXX always 0
+                         procinfo.outOps,
+                         procinfo.inBytes,    // XXX always 0
+                         procinfo.outBytes);
+}
+
+
+/*
+ * Return process user and system CPU times as a Python tuple.
+ */
+static PyObject *
+psutil_proc_cpu_times(PyObject *self, PyObject *args) {
+    int pid;
+    char path[100];
+    pstatus_t info;
+    const char *procfs_path;
+
+    if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path))
+        return NULL;
+    sprintf(path, "%s/%i/status", procfs_path, pid);
+    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
+        return NULL;
+    // results are more precise than os.times()
+    return Py_BuildValue("dddd",
+                         TV2DOUBLE(info.pr_utime),
+                         TV2DOUBLE(info.pr_stime),
+                         TV2DOUBLE(info.pr_cutime),
+                         TV2DOUBLE(info.pr_cstime));
+}
+
+
+/*
+ * Return process uids/gids as a Python tuple.
+ */
+static PyObject *
+psutil_proc_cred(PyObject *self, PyObject *args) {
+    int pid;
+    char path[100];
+    prcred_t info;
+    const char *procfs_path;
+
+    if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path))
+        return NULL;
+    sprintf(path, "%s/%i/cred", procfs_path, pid);
+    if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
+        return NULL;
+    return Py_BuildValue("iiiiii",
+                         info.pr_ruid, info.pr_euid, info.pr_suid,
+                         info.pr_rgid, info.pr_egid, info.pr_sgid);
+}
+
+
+/*
+ * Return process voluntary and involuntary context switches as a Python tuple.
+ */
+static PyObject *
+psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) {
+    PyObject *py_tuple = NULL;
+    pid32_t requested_pid;
+    pid32_t pid = 0;
+    int np = 0;
+    struct procentry64 *processes = (struct procentry64 *)NULL;
+    struct procentry64 *p;
+
+    if (! PyArg_ParseTuple(args, "i", &requested_pid))
+        return NULL;
+
+    processes = psutil_read_process_table(&np);
+    if (!processes)
+        return NULL;
+
+    /* Loop through processes */
+    for (p = processes; np > 0; np--, p++) {
+        pid = p->pi_pid;
+        if (requested_pid != pid)
+            continue;
+        py_tuple = Py_BuildValue("LL",
+            (long long) p->pi_ru.ru_nvcsw,    /* voluntary context switches */
+            (long long) p->pi_ru.ru_nivcsw);  /* involuntary */
+        free(processes);
+        return py_tuple;
+    }
+
+    /* finished iteration without finding requested pid */
+    free(processes);
+    return NoSuchProcess("");
+}
+
+
+/*
+ * Return users currently connected on the system.
+ */
+static PyObject *
+psutil_users(PyObject *self, PyObject *args) {
+    struct utmpx *ut;
+    PyObject *py_retlist = PyList_New(0);
+    PyObject *py_tuple = NULL;
+    PyObject *py_username = NULL;
+    PyObject *py_tty = NULL;
+    PyObject *py_hostname = NULL;
+    PyObject *py_user_proc = NULL;
+
+    if (py_retlist == NULL)
+        return NULL;
+
+    setutxent();
+    while (NULL != (ut = getutxent())) {
+        if (ut->ut_type == USER_PROCESS)
+            py_user_proc = Py_True;
+        else
+            py_user_proc = Py_False;
+        py_username = PyUnicode_DecodeFSDefault(ut->ut_user);
+        if (! py_username)
+            goto error;
+        py_tty = PyUnicode_DecodeFSDefault(ut->ut_line);
+        if (! py_tty)
+            goto error;
+        py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host);
+        if (! py_hostname)
+            goto error;
+        py_tuple = Py_BuildValue(
+            "(OOOfOi)",
+            py_username,              // username
+            py_tty,                   // tty
+            py_hostname,              // hostname
+            (float)ut->ut_tv.tv_sec,  // tstamp
+            py_user_proc,             // (bool) user process
+            ut->ut_pid                // process id
+        );
+        if (py_tuple == NULL)
+            goto error;
+        if (PyList_Append(py_retlist, py_tuple))
+            goto error;
+        Py_DECREF(py_username);
+        Py_DECREF(py_tty);
+        Py_DECREF(py_hostname);
+        Py_DECREF(py_tuple);
+    }
+    endutxent();
+
+    return py_retlist;
+
+error:
+    Py_XDECREF(py_username);
+    Py_XDECREF(py_tty);
+    Py_XDECREF(py_hostname);
+    Py_XDECREF(py_tuple);
+    Py_DECREF(py_retlist);
+    if (ut != NULL)
+        endutxent();
+    return NULL;
+}
+
+
+/*
+ * Return disk mounted partitions as a list of tuples including device,
+ * mount point and filesystem type.
+ */
+static PyObject *
+psutil_disk_partitions(PyObject *self, PyObject *args) {
+    FILE *file = NULL;
+    struct mntent * mt = NULL;
+    PyObject *py_dev = NULL;
+    PyObject *py_mountp = NULL;
+    PyObject *py_tuple = NULL;
+    PyObject *py_retlist = PyList_New(0);
+
+    if (py_retlist == NULL)
+        return NULL;
+
+    file = setmntent(MNTTAB, "rb");
+    if (file == NULL) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+    mt = getmntent(file);
+    while (mt != NULL) {
+        py_dev = PyUnicode_DecodeFSDefault(mt->mnt_fsname);
+        if (! py_dev)
+            goto error;
+        py_mountp = PyUnicode_DecodeFSDefault(mt->mnt_dir);
+        if (! py_mountp)
+            goto error;
+        py_tuple = Py_BuildValue(
+            "(OOss)",
+            py_dev,         // device
+            py_mountp,      // mount point
+            mt->mnt_type,   // fs type
+            mt->mnt_opts);  // options
+        if (py_tuple == NULL)
+            goto error;
+        if (PyList_Append(py_retlist, py_tuple))
+            goto error;
+        Py_DECREF(py_dev);
+        Py_DECREF(py_mountp);
+        Py_DECREF(py_tuple);
+        mt = getmntent(file);
+    }
+    endmntent(file);
+    return py_retlist;
+
+error:
+    Py_XDECREF(py_dev);
+    Py_XDECREF(py_mountp);
+    Py_XDECREF(py_tuple);
+    Py_DECREF(py_retlist);
+    if (file != NULL)
+        endmntent(file);
+    return NULL;
+}
+
+
+/*
+ * Return a list of tuples for network I/O statistics.
+ */
+static PyObject *
+psutil_net_io_counters(PyObject *self, PyObject *args) {
+    perfstat_netinterface_t *statp = NULL;
+    int tot, i;
+    perfstat_id_t first;
+
+    PyObject *py_retdict = PyDict_New();
+    PyObject *py_ifc_info = NULL;
+
+    if (py_retdict == NULL)
+        return NULL;
+
+    /* check how many perfstat_netinterface_t structures are available */
+    tot = perfstat_netinterface(
+        NULL, NULL, sizeof(perfstat_netinterface_t), 0);
+    if (tot == 0) {
+        // no network interfaces - return empty dict
+        return py_retdict;
+    }
+    if (tot < 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+    statp = (perfstat_netinterface_t *)
+        malloc(tot * sizeof(perfstat_netinterface_t));
+    if (statp == NULL) {
+        PyErr_NoMemory();
+        goto error;
+    }
+    strcpy(first.name, FIRST_NETINTERFACE);
+    tot = perfstat_netinterface(&first, statp,
+        sizeof(perfstat_netinterface_t), tot);
+    if (tot < 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+
+    for (i = 0; i < tot; i++) {
+        py_ifc_info = Py_BuildValue("(KKKKKKKK)",
+            statp[i].obytes,      /* number of bytes sent on interface */
+            statp[i].ibytes,      /* number of bytes received on interface */
+            statp[i].opackets,    /* number of packets sent on interface */
+            statp[i].ipackets,    /* number of packets received on interface */
+            statp[i].ierrors,     /* number of input errors on interface */
+            statp[i].oerrors,     /* number of output errors on interface */
+            statp[i].if_iqdrops,  /* Dropped on input, this interface */
+            statp[i].xmitdrops    /* number of packets not transmitted */
+           );
+        if (!py_ifc_info)
+            goto error;
+        if (PyDict_SetItemString(py_retdict, statp[i].name, py_ifc_info))
+            goto error;
+        Py_DECREF(py_ifc_info);
+    }
+
+    free(statp);
+    return py_retdict;
+
+error:
+    if (statp != NULL)
+        free(statp);
+    Py_XDECREF(py_ifc_info);
+    Py_DECREF(py_retdict);
+    return NULL;
+}
+
+
+static PyObject*
+psutil_net_if_stats(PyObject* self, PyObject* args) {
+    char *nic_name;
+    int sock = 0;
+    int ret;
+    int mtu;
+    struct ifreq ifr;
+    PyObject *py_is_up = NULL;
+    PyObject *py_retlist = NULL;
+
+    if (! PyArg_ParseTuple(args, "s", &nic_name))
+        return NULL;
+
+    sock = socket(AF_INET, SOCK_DGRAM, 0);
+    if (sock == -1)
+        goto error;
+
+    strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
+
+    // is up?
+    ret = ioctl(sock, SIOCGIFFLAGS, &ifr);
+    if (ret == -1)
+        goto error;
+    if ((ifr.ifr_flags & IFF_UP) != 0)
+        py_is_up = Py_True;
+    else
+        py_is_up = Py_False;
+    Py_INCREF(py_is_up);
+
+    // MTU
+    ret = ioctl(sock, SIOCGIFMTU, &ifr);
+    if (ret == -1)
+        goto error;
+    mtu = ifr.ifr_mtu;
+
+    close(sock);
+    py_retlist = Py_BuildValue("[Oi]", py_is_up, mtu);
+    if (!py_retlist)
+        goto error;
+    Py_DECREF(py_is_up);
+    return py_retlist;
+
+error:
+    Py_XDECREF(py_is_up);
+    if (sock != 0)
+        close(sock);
+    PyErr_SetFromErrno(PyExc_OSError);
+    return NULL;
+}
+
+
+static PyObject *
+psutil_boot_time(PyObject *self, PyObject *args) {
+    float boot_time = 0.0;
+    struct utmpx *ut;
+
+    setutxent();
+    while (NULL != (ut = getutxent())) {
+        if (ut->ut_type == BOOT_TIME) {
+            boot_time = (float)ut->ut_tv.tv_sec;
+            break;
+        }
+    }
+    endutxent();
+    if (boot_time == 0.0) {
+        /* could not find BOOT_TIME in getutxent loop */
+        PyErr_SetString(PyExc_RuntimeError, "can't determine boot time");
+        return NULL;
+    }
+    return Py_BuildValue("f", boot_time);
+}
+
+
+/*
+ * Return a Python list of tuple representing per-cpu times
+ */
+static PyObject *
+psutil_per_cpu_times(PyObject *self, PyObject *args) {
+    int ncpu, rc, i;
+    perfstat_cpu_t *cpu = NULL;
+    perfstat_id_t id;
+    PyObject *py_retlist = PyList_New(0);
+    PyObject *py_cputime = NULL;
+
+    if (py_retlist == NULL)
+        return NULL;
+
+    /* get the number of cpus in ncpu */
+    ncpu = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0);
+    if (ncpu <= 0){
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+
+    /* allocate enough memory to hold the ncpu structures */
+    cpu = (perfstat_cpu_t *) malloc(ncpu * sizeof(perfstat_cpu_t));
+    if (cpu == NULL) {
+        PyErr_NoMemory();
+        goto error;
+    }
+
+    strcpy(id.name, "");
+    rc = perfstat_cpu(&id, cpu, sizeof(perfstat_cpu_t), ncpu);
+
+    if (rc <= 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+
+    for (i = 0; i < ncpu; i++) {
+        py_cputime = Py_BuildValue(
+            "(dddd)",
+            (double)cpu[i].user,
+            (double)cpu[i].sys,
+            (double)cpu[i].idle,
+            (double)cpu[i].wait);
+        if (!py_cputime)
+            goto error;
+        if (PyList_Append(py_retlist, py_cputime))
+            goto error;
+        Py_DECREF(py_cputime);
+    }
+    free(cpu);
+    return py_retlist;
+
+error:
+    Py_XDECREF(py_cputime);
+    Py_DECREF(py_retlist);
+    if (cpu != NULL)
+        free(cpu);
+    return NULL;
+}
+
+
+/*
+ * Return disk IO statistics.
+ */
+static PyObject *
+psutil_disk_io_counters(PyObject *self, PyObject *args) {
+    PyObject *py_retdict = PyDict_New();
+    PyObject *py_disk_info = NULL;
+    perfstat_disk_t *diskt = NULL;
+    perfstat_id_t id;
+    int i, rc, disk_count;
+
+    if (py_retdict == NULL)
+        return NULL;
+
+    /* Get the count of disks */
+    disk_count = perfstat_disk(NULL, NULL, sizeof(perfstat_disk_t), 0);
+    if (disk_count <= 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+
+    /* Allocate enough memory */
+    diskt = (perfstat_disk_t *)calloc(disk_count,
+        sizeof(perfstat_disk_t));
+    if (diskt == NULL) {
+        PyErr_NoMemory();
+        goto error;
+    }
+
+    strcpy(id.name, FIRST_DISK);
+    rc = perfstat_disk(&id, diskt, sizeof(perfstat_disk_t),
+        disk_count);
+    if (rc <= 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+
+    for (i = 0; i < disk_count; i++) {
+        py_disk_info = Py_BuildValue(
+            "KKKKKK",
+            diskt[i].__rxfers,
+            diskt[i].xfers - diskt[i].__rxfers,
+            diskt[i].rblks * diskt[i].bsize,
+            diskt[i].wblks * diskt[i].bsize,
+            diskt[i].rserv / 1000 / 1000,  // from nano to milli secs
+            diskt[i].wserv / 1000 / 1000   // from nano to milli secs
+        );
+        if (py_disk_info == NULL)
+            goto error;
+        if (PyDict_SetItemString(py_retdict, diskt[i].name,
+                                 py_disk_info))
+            goto error;
+        Py_DECREF(py_disk_info);
+    }
+    free(diskt);
+    return py_retdict;
+
+error:
+    Py_XDECREF(py_disk_info);
+    Py_DECREF(py_retdict);
+    if (diskt != NULL)
+        free(diskt);
+    return NULL;
+}
+
+
+/*
+ * Return virtual memory usage statistics.
+ */
+static PyObject *
+psutil_virtual_mem(PyObject *self, PyObject *args) {
+    int rc;
+    int pagesize = getpagesize();
+    perfstat_memory_total_t memory;
+
+    rc = perfstat_memory_total(
+        NULL, &memory, sizeof(perfstat_memory_total_t), 1);
+    if (rc <= 0){
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+
+    return Py_BuildValue("KKKKK",
+        (unsigned long long) memory.real_total * pagesize,
+        (unsigned long long) memory.real_avail * pagesize,
+        (unsigned long long) memory.real_free * pagesize,
+        (unsigned long long) memory.real_pinned * pagesize,
+        (unsigned long long) memory.real_inuse * pagesize
+    );
+}
+
+
+/*
+ * Return stats about swap memory.
+ */
+static PyObject *
+psutil_swap_mem(PyObject *self, PyObject *args) {
+    int rc;
+    int pagesize = getpagesize();
+    perfstat_memory_total_t memory;
+
+    rc = perfstat_memory_total(
+        NULL, &memory, sizeof(perfstat_memory_total_t), 1);
+    if (rc <= 0){
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+
+    return Py_BuildValue("KKKK",
+        (unsigned long long) memory.pgsp_total * pagesize,
+        (unsigned long long) memory.pgsp_free * pagesize,
+        (unsigned long long) memory.pgins * pagesize,
+        (unsigned long long) memory.pgouts * pagesize
+    );
+}
+
+
+/*
+ * Return CPU statistics.
+ */
+static PyObject *
+psutil_cpu_stats(PyObject *self, PyObject *args) {
+    int ncpu, rc, i;
+    // perfstat_cpu_total_t doesn't have invol/vol cswitch, only pswitch
+    // which is apparently something else. We have to sum over all cpus
+    perfstat_cpu_t *cpu = NULL;
+    perfstat_id_t id;
+    u_longlong_t cswitches = 0;
+    u_longlong_t devintrs = 0;
+    u_longlong_t softintrs = 0;
+    u_longlong_t syscall = 0;
+
+    /* get the number of cpus in ncpu */
+    ncpu = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0);
+    if (ncpu <= 0){
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+
+    /* allocate enough memory to hold the ncpu structures */
+    cpu = (perfstat_cpu_t *) malloc(ncpu * sizeof(perfstat_cpu_t));
+    if (cpu == NULL) {
+        PyErr_NoMemory();
+        goto error;
+    }
+
+    strcpy(id.name, "");
+    rc = perfstat_cpu(&id, cpu, sizeof(perfstat_cpu_t), ncpu);
+
+    if (rc <= 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        goto error;
+    }
+
+    for (i = 0; i < ncpu; i++) {
+        cswitches += cpu[i].invol_cswitch + cpu[i].vol_cswitch;
+        devintrs += cpu[i].devintrs;
+        softintrs += cpu[i].softintrs;
+        syscall += cpu[i].syscall;
+    }
+
+    free(cpu);
+
+    return Py_BuildValue(
+        "KKKK",
+        cswitches,
+        devintrs,
+        softintrs,
+        syscall
+    );
+
+error:
+    if (cpu != NULL)
+        free(cpu);
+    return NULL;
+}
+
+
+/*
+ * define the psutil C module methods and initialize the module.
+ */
+static PyMethodDef
+PsutilMethods[] =
+{
+    // --- process-related functions
+    {"proc_basic_info", psutil_proc_basic_info, METH_VARARGS,
+     "Return process ppid, rss, vms, ctime, nice, nthreads, status and tty"},
+    {"proc_name_and_args", psutil_proc_name_and_args, METH_VARARGS,
+     "Return process name and args."},
+    {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS,
+     "Return process user and system CPU times."},
+    {"proc_cred", psutil_proc_cred, METH_VARARGS,
+     "Return process uids/gids."},
+#ifdef CURR_VERSION_THREAD
+    {"proc_threads", psutil_proc_threads, METH_VARARGS,
+     "Return process threads"},
+#endif
+    {"proc_io_counters", psutil_proc_io_counters, METH_VARARGS,
+     "Get process I/O counters."},
+    {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS,
+     "Get process I/O counters."},
+
+    // --- system-related functions
+    {"users", psutil_users, METH_VARARGS,
+     "Return currently connected users."},
+    {"disk_partitions", psutil_disk_partitions, METH_VARARGS,
+     "Return disk partitions."},
+    {"boot_time", psutil_boot_time, METH_VARARGS,
+     "Return system boot time in seconds since the EPOCH."},
+    {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS,
+     "Return system per-cpu times as a list of tuples"},
+    {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS,
+     "Return a Python dict of tuples for disk I/O statistics."},
+    {"virtual_mem", psutil_virtual_mem, METH_VARARGS,
+     "Return system virtual memory usage statistics"},
+    {"swap_mem", psutil_swap_mem, METH_VARARGS,
+     "Return stats about swap memory, in bytes"},
+    {"net_io_counters", psutil_net_io_counters, METH_VARARGS,
+     "Return a Python dict of tuples for network I/O statistics."},
+    {"net_connections", psutil_net_connections, METH_VARARGS,
+     "Return system-wide connections"},
+    {"net_if_stats", psutil_net_if_stats, METH_VARARGS,
+     "Return NIC stats (isup, mtu)"},
+    {"cpu_stats", psutil_cpu_stats, METH_VARARGS,
+     "Return CPU statistics"},
+
+    // --- others
+    {"set_testing", psutil_set_testing, METH_NOARGS,
+     "Set psutil in testing mode"},
+
+    {NULL, NULL, 0, NULL}
+};
+
+
+struct module_state {
+    PyObject *error;
+};
+
+#if PY_MAJOR_VERSION >= 3
+#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
+#else
+#define GETSTATE(m) (&_state)
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+
+static int
+psutil_aix_traverse(PyObject *m, visitproc visit, void *arg) {
+    Py_VISIT(GETSTATE(m)->error);
+    return 0;
+}
+
+static int
+psutil_aix_clear(PyObject *m) {
+    Py_CLEAR(GETSTATE(m)->error);
+    return 0;
+}
+
+static struct PyModuleDef moduledef = {
+    PyModuleDef_HEAD_INIT,
+    "psutil_aix",
+    NULL,
+    sizeof(struct module_state),
+    PsutilMethods,
+    NULL,
+    psutil_aix_traverse,
+    psutil_aix_clear,
+    NULL
+};
+
+#define INITERROR return NULL
+
+PyMODINIT_FUNC PyInit__psutil_aix(void)
+
+#else
+#define INITERROR return
+
+void init_psutil_aix(void)
+#endif
+{
+#if PY_MAJOR_VERSION >= 3
+    PyObject *module = PyModule_Create(&moduledef);
+#else
+    PyObject *module = Py_InitModule("_psutil_aix", PsutilMethods);
+#endif
+    PyModule_AddIntConstant(module, "version", PSUTIL_VERSION);
+
+    PyModule_AddIntConstant(module, "SIDL", SIDL);
+    PyModule_AddIntConstant(module, "SZOMB", SZOMB);
+    PyModule_AddIntConstant(module, "SACTIVE", SACTIVE);
+    PyModule_AddIntConstant(module, "SSWAP", SSWAP);
+    PyModule_AddIntConstant(module, "SSTOP", SSTOP);
+
+    PyModule_AddIntConstant(module, "TCPS_CLOSED", TCPS_CLOSED);
+    PyModule_AddIntConstant(module, "TCPS_CLOSING", TCPS_CLOSING);
+    PyModule_AddIntConstant(module, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT);
+    PyModule_AddIntConstant(module, "TCPS_LISTEN", TCPS_LISTEN);
+    PyModule_AddIntConstant(module, "TCPS_ESTABLISHED", TCPS_ESTABLISHED);
+    PyModule_AddIntConstant(module, "TCPS_SYN_SENT", TCPS_SYN_SENT);
+    PyModule_AddIntConstant(module, "TCPS_SYN_RCVD", TCPS_SYN_RECEIVED);
+    PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1);
+    PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2);
+    PyModule_AddIntConstant(module, "TCPS_LAST_ACK", TCPS_LAST_ACK);
+    PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT);
+    PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE);
+
+    psutil_setup();
+
+    if (module == NULL)
+        INITERROR;
+#if PY_MAJOR_VERSION >= 3
+    return module;
+#endif
+}
old mode 100644
new mode 100755
new file mode 100755
--- /dev/null
+++ b/third_party/python/psutil/psutil/tests/test_aix.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+
+# 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 specific tests."""
+
+import re
+
+from psutil import AIX
+from psutil.tests import run_test_module_by_name
+from psutil.tests import sh
+from psutil.tests import unittest
+import psutil
+
+
+@unittest.skipIf(not AIX, "AIX only")
+class AIXSpecificTestCase(unittest.TestCase):
+
+    def test_virtual_memory(self):
+        out = sh('/usr/bin/svmon -O unit=KB')
+        re_pattern = "memory\s*"
+        for field in ("size inuse free pin virtual available mmode").split():
+            re_pattern += "(?P<%s>\S+)\s+" % (field,)
+        matchobj = re.search(re_pattern, out)
+
+        self.assertIsNotNone(
+            matchobj, "svmon command returned unexpected output")
+
+        KB = 1024
+        total = int(matchobj.group("size")) * KB
+        available = int(matchobj.group("available")) * KB
+        used = int(matchobj.group("inuse")) * KB
+        free = int(matchobj.group("free")) * KB
+
+        psutil_result = psutil.virtual_memory()
+
+        # MEMORY_TOLERANCE from psutil.tests is not enough. For some reason
+        # we're seeing differences of ~1.2 MB. 2 MB is still a good tolerance
+        # when compared to GBs.
+        MEMORY_TOLERANCE = 2 * KB * KB   # 2 MB
+        self.assertEqual(psutil_result.total, total)
+        self.assertAlmostEqual(
+            psutil_result.used, used, delta=MEMORY_TOLERANCE)
+        self.assertAlmostEqual(
+            psutil_result.available, available, delta=MEMORY_TOLERANCE)
+        self.assertAlmostEqual(
+            psutil_result.free, free, delta=MEMORY_TOLERANCE)
+
+    def test_swap_memory(self):
+        out = sh('/usr/sbin/lsps -a')
+        # From the man page, "The size is given in megabytes" so we assume
+        # we'll always have 'MB' in the result
+        # TODO maybe try to use "swap -l" to check "used" too, but its units
+        # are not guaranteed to be "MB" so parsing may not be consistent
+        matchobj = re.search("(?P<space>\S+)\s+"
+                             "(?P<vol>\S+)\s+"
+                             "(?P<vg>\S+)\s+"
+                             "(?P<size>\d+)MB", out)
+
+        self.assertIsNotNone(
+            matchobj, "lsps command returned unexpected output")
+
+        total_mb = int(matchobj.group("size"))
+        MB = 1024 ** 2
+        psutil_result = psutil.swap_memory()
+        # we divide our result by MB instead of multiplying the lsps value by
+        # MB because lsps may round down, so we round down too
+        self.assertEqual(int(psutil_result.total / MB), total_mb)
+
+    def test_cpu_stats(self):
+        out = sh('/usr/bin/mpstat -a')
+
+        re_pattern = "ALL\s*"
+        for field in ("min maj mpcs mpcr dev soft dec ph cs ics bound rq "
+                      "push S3pull S3grd S0rd S1rd S2rd S3rd S4rd S5rd "
+                      "sysc").split():
+            re_pattern += "(?P<%s>\S+)\s+" % (field,)
+        matchobj = re.search(re_pattern, out)
+
+        self.assertIsNotNone(
+            matchobj, "mpstat command returned unexpected output")
+
+        # numbers are usually in the millions so 1000 is ok for tolerance
+        CPU_STATS_TOLERANCE = 1000
+        psutil_result = psutil.cpu_stats()
+        self.assertAlmostEqual(
+            psutil_result.ctx_switches,
+            int(matchobj.group("cs")),
+            delta=CPU_STATS_TOLERANCE)
+        self.assertAlmostEqual(
+            psutil_result.syscalls,
+            int(matchobj.group("sysc")),
+            delta=CPU_STATS_TOLERANCE)
+        self.assertAlmostEqual(
+            psutil_result.interrupts,
+            int(matchobj.group("dev")),
+            delta=CPU_STATS_TOLERANCE)
+        self.assertAlmostEqual(
+            psutil_result.soft_interrupts,
+            int(matchobj.group("soft")),
+            delta=CPU_STATS_TOLERANCE)
+
+    def test_cpu_count_logical(self):
+        out = sh('/usr/bin/mpstat -a')
+        mpstat_lcpu = int(re.search("lcpu=(\d+)", out).group(1))
+        psutil_lcpu = psutil.cpu_count(logical=True)
+        self.assertEqual(mpstat_lcpu, psutil_lcpu)
+
+    def test_net_if_addrs_names(self):
+        out = sh('/etc/ifconfig -l')
+        ifconfig_names = set(out.split())
+        psutil_names = set(psutil.net_if_addrs().keys())
+        self.assertSetEqual(ifconfig_names, psutil_names)
+
+
+if __name__ == '__main__':
+    run_test_module_by_name(__file__)
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
new file mode 100755
--- /dev/null
+++ b/third_party/python/psutil/scripts/internal/print_announce.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Prints release announce based on HISTORY.rst file content.
+"""
+
+import os
+import re
+
+from psutil import __version__ as PRJ_VERSION
+
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+HISTORY = os.path.abspath(os.path.join(HERE, '../../HISTORY.rst'))
+
+PRJ_NAME = 'psutil'
+PRJ_URL_HOME = 'https://github.com/giampaolo/psutil'
+PRJ_URL_DOC = 'http://psutil.readthedocs.io'
+PRJ_URL_DOWNLOAD = 'https://pypi.python.org/pypi/psutil'
+PRJ_URL_WHATSNEW = \
+    'https://github.com/giampaolo/psutil/blob/master/HISTORY.rst'
+
+template = """\
+Hello all,
+I'm glad to announce the release of {prj_name} {prj_version}:
+{prj_urlhome}
+
+About
+=====
+
+psutil (process and system utilities) is a cross-platform library for \
+retrieving information on running processes and system utilization (CPU, \
+memory, disks, network) 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 command \
+line tools such as: ps, top, lsof, netstat, ifconfig, who, df, kill, free, \
+nice, ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap. It \
+currently supports Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD, NetBSD \
+and AIX, both 32-bit and 64-bit architectures, with Python versions from 2.6 \
+to 3.6. PyPy is also known to work.
+
+What's new
+==========
+
+{changes}
+
+Links
+=====
+
+- Home page: {prj_urlhome}
+- Download: {prj_urldownload}
+- Documentation: {prj_urldoc}
+- What's new: {prj_urlwhatsnew}
+
+--
+
+Giampaolo - http://grodola.blogspot.com
+"""
+
+
+def get_changes():
+    """Get the most recent changes for this release by parsing
+    HISTORY.rst file.
+    """
+    with open(HISTORY) as f:
+        lines = f.readlines()
+
+    block = []
+
+    # eliminate the part preceding the first block
+    for i, line in enumerate(lines):
+        line = lines.pop(0)
+        if line.startswith('===='):
+            break
+    lines.pop(0)
+
+    for i, line in enumerate(lines):
+        line = lines.pop(0)
+        line = line.rstrip()
+        if re.match("^- \d+_: ", line):
+            num, _, rest = line.partition(': ')
+            num = ''.join([x for x in num if x.isdigit()])
+            line = "- #%s: %s" % (num, rest)
+
+        if line.startswith('===='):
+            break
+        block.append(line)
+
+    # eliminate bottom empty lines
+    block.pop(-1)
+    while not block[-1]:
+        block.pop(-1)
+
+    return "\n".join(block)
+
+
+def main():
+    changes = get_changes()
+    print(template.format(
+        prj_name=PRJ_NAME,
+        prj_version=PRJ_VERSION,
+        prj_urlhome=PRJ_URL_HOME,
+        prj_urldownload=PRJ_URL_DOWNLOAD,
+        prj_urldoc=PRJ_URL_DOC,
+        prj_urlwhatsnew=PRJ_URL_WHATSNEW,
+        changes=changes,
+    ))
+
+
+if __name__ == '__main__':
+    main()
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
old mode 100644
new mode 100755
--- a/third_party/python/requirements.in
+++ b/third_party/python/requirements.in
@@ -1,13 +1,14 @@
 attrs==18.1.0
 blessings==1.7
 jsmin==2.1.0
 json-e==2.7.0
 pip-tools==3.0.0
 pipenv==2018.5.18
+psutil==5.4.3
 pytest==3.6.2
 python-hglib==2.4
 requests==2.9.1
 scandir==1.9.0
 six==1.10.0
 virtualenv==15.2.0
 voluptuous==0.11.5
--- a/third_party/python/requirements.txt
+++ b/third_party/python/requirements.txt
@@ -36,16 +36,26 @@ pip-tools==3.0.0 \
 pipenv==2018.5.18 \
     --hash=sha256:04b9a8b02a3ff12a5502b335850cfdb192adcfd1d6bbdb7a7c47cae9ab9ddece \
     --hash=sha256:e96d5bfa6822a17b2200d455aa5f9002c14361c50df1b1e51921479d7c09e741
 pluggy==0.6.0 \
     --hash=sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff \
     --hash=sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c \
     --hash=sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5 \
     # via pytest
+psutil==5.4.3 \
+    --hash=sha256:230eeb3aeb077814f3a2cd036ddb6e0f571960d327298cc914c02385c3e02a63 \
+    --hash=sha256:4152ae231709e3e8b80e26b6da20dc965a1a589959c48af1ed024eca6473f60d \
+    --hash=sha256:779ec7e7621758ca11a8d99a1064996454b3570154277cc21342a01148a49c28 \
+    --hash=sha256:82a06785db8eeb637b349006cc28a92e40cd190fefae9875246d18d0de7ccac8 \
+    --hash=sha256:8a15d773203a1277e57b1d11a7ccdf70804744ef4a9518a87ab8436995c31a4b \
+    --hash=sha256:94d4e63189f2593960e73acaaf96be235dd8a455fe2bcb37d8ad6f0e87f61556 \
+    --hash=sha256:a3286556d4d2f341108db65d8e20d0cd3fcb9a91741cb5eb496832d7daf2a97c \
+    --hash=sha256:c91eee73eea00df5e62c741b380b7e5b6fdd553891bee5669817a3a38d036f13 \
+    --hash=sha256:e2467e9312c2fa191687b89ff4bc2ad8843be4af6fb4dc95a7cc5f7d7a327b18
 py==1.5.4 \
     --hash=sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7 \
     --hash=sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e \
     # via pytest
 pytest==3.6.2 \
     --hash=sha256:8ea01fc4fcc8e1b1e305252b4bc80a1528019ab99fd3b88666c9dc38d754406c \
     --hash=sha256:90898786b3d0b880b47645bae7b51aa9bbf1e9d1e4510c2cfd15dd65c70ea0cd
 python-hglib==2.4 \