Repository: giampaolo/psutil
Pull request #1123 (2017-09-05 08:27:58)

AIX support
Following #605, this is the proposed branch for AIX support.

The following methods and methods are not supported in AIX in this branch:

    psutil.Process.memory_maps
    psutil.Process.num_ctx_switches

Not all unit tests pass on AIX at this time (I didn't make any changes to unit tests yet).

wiggin15: I added a new commit. Still no AIX-specific tests and no unicode string handling, but I fixed some broken functions and started changing the unit tests a bit. I think this looks better.
giampaolo: Good work on addressing the first set of comments! I'm gonna add another "big" request.
Right now "/proc" is hard coded but in the py and the C file. It should be possible to change that via `psutil.PROCFS_PATH`. In order to do that.
Copy this in `_psaix.py`:
https://github.com/Infinidat/psutil/blob/cac89d522d64571a53011cd0914a654a9c5e2869/psutil/_pslinux.py#L215
...then store it in the `Process` class like this:
https://github.com/Infinidat/psutil/blob/cac89d522d64571a53011cd0914a654a9c5e2869/psutil/_pslinux.py#L1392
...and pass that string to the underlying C functions that need it. 

On the C side of things you'll now have to accept 2 args, the PID and /proc path prefix, like this:
https://github.com/Infinidat/psutil/blob/cac89d522d64571a53011cd0914a654a9c5e2869/psutil/_psutil_sunos.c#L96-L101

Not sure if I've been clear (I hope =)).

wiggin15: What's the reason for making `/proc` changeable? Is this configurable on the operating system?
giampaolo: > What's the reason for making /proc changeable?

https://github.com/giampaolo/psutil/issues/558
wiggin15: I think, instead of moving the `get` function to `_common`, we can move this function to the main `__init__.py` file and then we won't have to use the `sys.modules` trick. Something like this in `__init__`:

    if LINUX or SOLARIS or AIX:
        # This is a public writable constant which is read from platform-specific code
        PROCFS_PATH = "/proc"

        def get_procfs_path():
            return PROCFS_PATH

What do you think?
In addition, I noticed that the BSD code is still using hard-coded `/proc`. Is it not relevant in that platform? Maybe we can change to `if POSIX` instead of Linux/Solaris/AIX if the BSD module is changed.
giampaolo: How can you use that from `_ps*.py` modules if it's defined in `__init__.py`? It's `__init__` which imports those module not vice versa. Anyway it looks like something which can be dealt with in another PR so for the time being you can also define it in `_psaix.py` and then we can move it later.

> In addition, I noticed that the BSD code is still using hard-coded /proc. Is it not relevant in that platform? 

It's not, BSD systems do not rely on /proc.

wiggin15: > How can you use that from _ps*.py modules if it's defined in __init__.py?

It's a matter of import order. If you define it in `__init__` before importing the platform-specific modules, they will succeed in importing from `__init__`. Maybe not the best way to do it though.


> BSD systems do not rely on /proc.

`_psbsd.py` has a few `readlink`s and `open`s from `/proc` (the C extension doesn't, but still..)
wiggin15: Added more commits :)
wiggin15: Added a new commit and uploaded the latest test results to https://gist.github.com/wiggin15/1d76d1dc1ad50e6665fe4dc5c6fa898a
please ignore 2 tests that fail with `python2.7.bin`, this is caused by a trick we're using on our environment.
giampaolo: ```
  File "/root/psutil/scripts/procinfo.py", line 228, in run
    print_("ctx-switches", str_ntuple(pinfo['num_ctx_switches']))
KeyError: 'num_ctx_switches'
```

This is easy: just change scripts/procinfo.py so that it won't print this info on AIX

```
  File "/root/psutil/psutil/tests/test_posix.py", line 368, in df
    total = int(fields[1]) * 1024
ValueError: invalid literal for int() with base 10: '-'
```

This means `df -k` output is different on AIX compared to other POSIXes. You can simply skip thi test on AIX.

```
  File "/root/psutil/psutil/tests/test_contracts.py", line 541, in cwd
    self.assertIsInstance(ret, str)
AssertionError: None is not an instance of <type 'str'>
```

Just return an empty string in cwd() method of _psaix.py instead of None.

```
======================================================================
FAIL: psutil.tests.test_misc.TestMisc.test_setup_script
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/psutil/psutil/tests/test_misc.py", line 369, in test_setup_script
    self.assertRaises(SystemExit, module.setup)
AssertionError: SystemExit not raised
```

Just skip this one.

```
======================================================================
FAIL: psutil.tests.test_misc.TestProcessUtils.test_create_zombie_proc
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/psutil/psutil/tests/test_misc.py", line 950, in test_create_zombie_proc
    zpid = create_zombie_proc()
  File "/root/psutil/psutil/tests/__init__.py", line 366, in create_zombie_proc
    call_until(lambda: zproc.status(), "ret == psutil.STATUS_ZOMBIE")
  File "/root/psutil/psutil/tests/__init__.py", line 593, in wrapper
    return fun(*args, **kwargs)
  File "/root/psutil/psutil/tests/__init__.py", line 643, in call_until
    assert eval(expr)
AssertionError
```

OK, you said `create_zombie_proc()` doesn't work on AIX. Have you checked whether `ps` is able to detect it though? I just want to be sure it's not `status()` method which is broken. If we make sure it's not and we don't find a way to correctly create a zombie process via unit tests then let' just skip this. 

```

  File "/root/psutil/psutil/tests/test_process.py", line 720, in test_cmdline
    ' '.join(cmdline))
AssertionError: '/root/python/bin/python2.7 -c' != '/root/python/bin/python2.7 -c import time; time.sleep(60)'
```

I would just change the test and do a comparison of the first 30 chars if you're on AIX, else just do what we're currently doing.

```
  File "/root/psutil/psutil/tests/test_process.py", line 322, in test_io_counters
    self.assertGreater(io2.read_count, io1.read_count)
AssertionError: 0L not greater than 0L
```

Same here. If we know io_counters()'s read_count is always zero just skip this specific test line when on AIX.

```
======================================================================
FAIL: psutil.tests.test_process.TestProcess.test_prog_w_funky_name
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/psutil/psutil/tests/test_process.py", line 755, in test_prog_w_funky_name
    self.assertEqual(p.cmdline(), cmdline)
AssertionError: Lists differ: [''] != ['/root/psutil/$testfnfoo bar ...
```

This is bad and should be fixed. The test may also not pass but we should never have `['']` returned.

```

======================================================================
FAIL: psutil.tests.test_system.TestSystemAPIs.test_cpu_stats
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/psutil/psutil/tests/test_system.py", line 746, in test_cpu_stats
    self.assertGreater(value, 0)
AssertionError: 0 not greater than 0
```

Again, if we know 'ctx_switches' and 'interrupts' are always 0 just skip this test on solaris (just add a comment / note)


```
======================================================================
FAIL: psutil.tests.test_system.TestSystemAPIs.test_pid_exists_2
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/root/psutil/psutil/tests/test_system.py", line 238, in test_pid_exists_2
    self.fail(pid)
AssertionError: 260
```

This is bad. It means pid_exists() is broken. Does AIX have /proc/{pid}/status? Linux uses a specific logic for pid_exists() as it takes the Tid (thread ID) into account. I wonder if AIX does the same.


What about memory leak tests?
giampaolo: Note: it may also be `pids()` returning a PID which does not exist, not necessarily `pid_exists` which is broken. Anyway, it's either one or the other. `ps` is your friend and should tell you who's wrong.
wiggin15: Added a new commit

> Just return an empty string in cwd() method of _psaix.py instead of None.

I noticed that the test for `cwd` already checks whether the return value is `None` (which can also happen on BSD and Solaris), but the test checks if it's `None` after making sure it's a string, which means it's a bug in the test. I fixed the test instead. I also fixed some other failures in `test_fetch_all`

> test_setup_script

I realized why it failed. It's because I used `python setup.py test` instead of `make test` to run the unit tests. Running the former makes argv ok for setup.setup() so it doesn't raise SystemExit with usage. I'll use `make test` from now on.

> OK, you said create_zombie_proc() doesn't work on AIX. Have you checked whether ps is able to detect it though? I just want to be sure it's not status()

OK, so it turns out you *can* create zombie processes and there was indeed a bug in `status`. It's not simple to determine actual process state from /proc, but I think I added the code required.

> I would just change the test and do a comparison of the first 30 chars if you're on AIX

The problem isn't that we cut to 30 characters (according to the struct in `/proc` the max should be 80), but that there is a single argument `import time; time.sleep(60)` which is too long so it's somehow truncated from `psinfo`. If I add another argument to argv after this argument, it will show in psinfo while "skipping" the long argument.

> It means pid_exists() is broken

The PIDs in question do exist, in `ps` and in `/proc`. They have `/proc/<pid>/psinfo`, and even `psutil.Process(pid)` works on them. Yet pid_exists returns False, because the "send a signal" check, even when trying with Python's `os.kill`, raises `OSError: no such process`. I noticed these processes are all named `wait` and it appears that there is 1 per logical CPU. It's an AIX thing...

> The test may also not pass but we should never have [''] returned.

I traced this back to an issue with our environment, again. `create_exe` used to compile C code for the test executable but now it's using `sys.executable` instead (also, there's a bug in that function where the default code is never used). Apparently `sys.executable` is not copy-able on our environment (again the `execve python2.7.bin` trick) so when running it it immediately exits, and that's why we get the empty cmdline. When changing `create_exe` (not committed, of course), those tests pass except `test_prog_w_funky_name` which, again, doesn't pass on Solaris either.

> What about memory leak tests?

Those tests pass now after a few small changes.

New test results in: https://gist.github.com/wiggin15/d1054f0a4a172a572dd610aad649dfa7
giampaolo: > The PIDs in question do exist, in ps and in /proc. They have /proc/<pid>/psinfo, and even psutil.Process(pid) works on them. Yet pid_exists returns False, because the "send a signal" check, even when trying with Python's os.kill, raises OSError: no such process. I noticed these processes are all named wait and it appears that there is 1 per logical CPU. It's an AIX thing...

OK, so I suppose the `os.kill` trick is not reliable. What about `return os.path.exitsts("/proc/{pid}")`? Does it work? Last possibility is doing `return pid in pids()` but that's slow.... :-\
I will reply to other comments later (need to sleep =)).
giampaolo: Also, can you please make sure all C lines are less than 80 chars long?
wiggin15: Added a new commit with AIX-specific tests

Patch:
diff --git a/CREDITS b/CREDITS
index 77bdfcaf..3948189d 100644
--- a/CREDITS
+++ b/CREDITS
@@ -363,9 +363,9 @@ N: maozguttman
 W: https://github.com/maozguttman
 I: 659
 
-N: wiggin15
+N: Arnon Yaari (wiggin15)
 W: https://github.com/wiggin15
-I: 517, 607, 610
+I: 517, 607, 610, 1131, 1123, 1130
 
 N: dasumin
 W: https://github.com/dasumin
diff --git a/HISTORY.rst b/HISTORY.rst
index 3649eafd..8096e82d 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,5 +1,15 @@
 *Bug tracker at https://github.com/giampaolo/psutil/issues*
 
+XXXX-XX-XX
+
+5.3.2
+=====
+
+*Bug fixes*
+
+- 1131_: [SunOS] fix compilation warnings.  (patch by Arnon Yaari)
+
+
 2017-09-10
 
 5.3.1
diff --git a/MANIFEST.in b/MANIFEST.in
index 9f84c4c2..e2f8a319 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -71,6 +71,7 @@ include psutil/arch/windows/services.h
 include psutil/tests/README.rst
 include psutil/tests/__init__.py
 include psutil/tests/__main__.py
+include psutil/tests/test_aix.py
 include psutil/tests/test_bsd.py
 include psutil/tests/test_connections.py
 include psutil/tests/test_contracts.py
diff --git a/docs/index.rst b/docs/index.rst
index 80360046..3ab44461 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -545,7 +545,7 @@ Network
    | ``"all"``      | the sum of all the possible families and protocols  |
    +----------------+-----------------------------------------------------+
 
-  On OSX this function requires root privileges.
+  On OSX and AIX this function requires root privileges.
   To get per-process connections use :meth:`Process.connections`.
   Also, see
   `netstat.py sample script <https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py>`__.
@@ -563,6 +563,10 @@ Network
     (OSX) :class:`psutil.AccessDenied` is always raised unless running as root.
     This is a limitation of the OS and ``lsof`` does the same.
 
+  .. note::
+    (AIX) :class:`psutil.AccessDenied` is always raised unless running as root
+    (lsof does the same).
+
   .. note::
     (Solaris) UNIX sockets are not supported.
 
@@ -1358,7 +1362,7 @@ Process class
     >>> p.io_counters()
     pio(read_count=454556, write_count=3456, read_bytes=110592, write_bytes=0, read_chars=769931, write_chars=203)
 
-    Availability: all platforms except OSX and Solaris
+    Availability: Linux, BSD, Windows, AIX
 
     .. versionchanged:: 5.2.0 added *read_chars* and *write_chars* on Linux;
       added *other_count* and *other_bytes* on Windows.
@@ -1368,6 +1372,8 @@ Process class
     The number voluntary and involuntary context switches performed by
     this process (cumulative).
 
+    Availability: all platforms except AIX
+
   .. method:: num_fds()
 
     The number of file descriptors currently opened by this process
@@ -1503,33 +1509,33 @@ Process class
     The "portable" fields available on all plaforms are `rss` and `vms`.
     All numbers are expressed in bytes.
 
-    +---------+---------+-------+---------+------------------------------+
-    | Linux   | OSX     | BSD   | Solaris | Windows                      |
-    +=========+=========+=======+=========+==============================+
-    | rss     | rss     | rss   | rss     | rss (alias for ``wset``)     |
-    +---------+---------+-------+---------+------------------------------+
-    | vms     | vms     | vms   | vms     | vms (alias for ``pagefile``) |
-    +---------+---------+-------+---------+------------------------------+
-    | shared  | pfaults | text  |         | num_page_faults              |
-    +---------+---------+-------+---------+------------------------------+
-    | text    | pageins | data  |         | peak_wset                    |
-    +---------+---------+-------+---------+------------------------------+
-    | lib     |         | stack |         | wset                         |
-    +---------+---------+-------+---------+------------------------------+
-    | data    |         |       |         | peak_paged_pool              |
-    +---------+---------+-------+---------+------------------------------+
-    | dirty   |         |       |         | paged_pool                   |
-    +---------+---------+-------+---------+------------------------------+
-    |         |         |       |         | peak_nonpaged_pool           |
-    +---------+---------+-------+---------+------------------------------+
-    |         |         |       |         | nonpaged_pool                |
-    +---------+---------+-------+---------+------------------------------+
-    |         |         |       |         | pagefile                     |
-    +---------+---------+-------+---------+------------------------------+
-    |         |         |       |         | peak_pagefile                |
-    +---------+---------+-------+---------+------------------------------+
-    |         |         |       |         | private                      |
-    +---------+---------+-------+---------+------------------------------+
+    +---------+---------+-------+---------+-----+------------------------------+
+    | Linux   | OSX     | BSD   | Solaris | AIX | Windows                      |
+    +=========+=========+=======+=========+=====+==============================+
+    | rss     | rss     | rss   | rss     | rss | rss (alias for ``wset``)     |
+    +---------+---------+-------+---------+-----+------------------------------+
+    | vms     | vms     | vms   | vms     | vms | vms (alias for ``pagefile``) |
+    +---------+---------+-------+---------+-----+------------------------------+
+    | shared  | pfaults | text  |         |     | num_page_faults              |
+    +---------+---------+-------+---------+-----+------------------------------+
+    | text    | pageins | data  |         |     | peak_wset                    |
+    +---------+---------+-------+---------+-----+------------------------------+
+    | lib     |         | stack |         |     | wset                         |
+    +---------+---------+-------+---------+-----+------------------------------+
+    | data    |         |       |         |     | peak_paged_pool              |
+    +---------+---------+-------+---------+-----+------------------------------+
+    | dirty   |         |       |         |     | paged_pool                   |
+    +---------+---------+-------+---------+-----+------------------------------+
+    |         |         |       |         |     | peak_nonpaged_pool           |
+    +---------+---------+-------+---------+-----+------------------------------+
+    |         |         |       |         |     | nonpaged_pool                |
+    +---------+---------+-------+---------+-----+------------------------------+
+    |         |         |       |         |     | pagefile                     |
+    +---------+---------+-------+---------+-----+------------------------------+
+    |         |         |       |         |     | peak_pagefile                |
+    +---------+---------+-------+---------+-----+------------------------------+
+    |         |         |       |         |     | private                      |
+    +---------+---------+-------+---------+-----+------------------------------+
 
     - **rss**: aka "Resident Set Size", this is the non-swapped physical
       memory a process has used.
@@ -1700,7 +1706,7 @@ Process class
        pmmap_ext(addr='02829000-02ccf000', perms='rw-p', path='[heap]', rss=4743168, size=4874240, pss=4743168, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=4743168, referenced=4718592, anonymous=4743168, swap=0),
        ...]
 
-    Availability: All platforms except OpenBSD and NetBSD.
+    Availability: All platforms except OpenBSD, NetBSD and AIX.
 
   .. method:: children(recursive=False)
 
@@ -1864,6 +1870,10 @@ Process class
 
     .. versionchanged:: 5.3.0 : "laddr" and "raddr" are named tuples.
 
+  .. note::
+    (AIX) :class:`psutil.AccessDenied` is always raised unless running
+    as root (lsof does the same).
+
   .. method:: is_running()
 
     Return whether the current process is running in the current process list.
@@ -2101,16 +2111,18 @@ Constants
 .. data:: OPENBSD
 .. data:: BSD
 .. data:: SUNOS
+.. data:: AIX
 
   ``bool`` constants which define what platform you're on. E.g. if on Windows,
   :const:`WINDOWS` constant will be ``True``, all others will be ``False``.
 
   .. versionadded:: 4.0.0
+  .. versionchanged:: 5.4.0 added AIX
 
 .. _const-procfs_path:
 .. data:: PROCFS_PATH
 
-  The path of the /proc filesystem on Linux and Solaris (defaults to
+  The path of the /proc filesystem on Linux, Solaris and AIX (defaults to
   ``"/proc"``).
   You may want to re-set this constant right after importing psutil in case
   your /proc filesystem is mounted elsewhere or if you want to retrieve
diff --git a/psutil/TODO.aix b/psutil/TODO.aix
new file mode 100644
index 00000000..495f4963
--- /dev/null
+++ b/psutil/TODO.aix
@@ -0,0 +1,16 @@
+AIX support is experimental at this time.
+The following functions and methods are unsupported on the AIX platform:
+
+    psutil.Process.memory_maps
+    psutil.Process.num_ctx_switches
+
+Known limitations:
+    psutil.Process.io_counters read count is always 0
+    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)
+
+The following unit tests may fail:
+    test_prog_w_funky_name      funky name tests don't work, name is truncated
+    test_cmdline                long args are cut from cmdline in /proc/pid/psinfo and getargs
+    test_pid_exists_2           there are pids in /proc that don't really exist
diff --git a/psutil/__init__.py b/psutil/__init__.py
index ca9bc239..9c6451e4 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -73,6 +73,7 @@ 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
@@ -158,6 +159,13 @@ elif SUNOS:
     # _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)
 
@@ -185,7 +193,7 @@ __all__ = [
     "POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED",
 
     "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "OSX", "POSIX", "SUNOS",
-    "WINDOWS",
+    "WINDOWS", "AIX",
 
     # classes
     "Process", "Popen",
@@ -785,7 +793,7 @@ class Process(object):
             """
             return self._proc.num_fds()
 
-    # Linux, BSD and Windows only
+    # Linux, BSD, AIX and Windows only
     if hasattr(_psplatform.Process, "io_counters"):
 
         def io_counters(self):
@@ -890,11 +898,13 @@ class Process(object):
             """
             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()
+    if hasattr(_psplatform.Process, "num_ctx_switches"):
+
+        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."""
@@ -1171,7 +1181,6 @@ class Process(object):
 
     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.
diff --git a/psutil/_common.py b/psutil/_common.py
index 7c4af3d8..2d562f93 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -81,6 +81,7 @@ 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")
 
 
 # ===================================================================
diff --git a/psutil/_psaix.py b/psutil/_psaix.py
new file mode 100644
index 00000000..102e0f5f
--- /dev/null
+++ b/psutil/_psaix.py
@@ -0,0 +1,590 @@
+# 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
+
+
+__extra__all__ = ["PROCFS_PATH"]
+
+
+# =====================================================================
+# --- globals
+# =====================================================================
+
+
+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)
+
+# these get overwritten on "import psutil" from the __init__.py file
+NoSuchProcess = None
+ZombieProcess = None
+AccessDenied = None
+TimeoutExpired = None
+
+
+# =====================================================================
+# --- 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 _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:
+            # 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']]
+
+    @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):
+        # 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.
+        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):
+        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 wait(self, timeout=None):
+        try:
+            return _psposix.wait_pid(self.pid, timeout)
+        except _psposix.TimeoutExpired:
+            # support for private module import
+            if TimeoutExpired is None:
+                raise
+            raise TimeoutExpired(timeout, self.pid, 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)
diff --git a/psutil/_psutil_aix.c b/psutil/_psutil_aix.c
new file mode 100644
index 00000000..52a14feb
--- /dev/null
+++ b/psutil/_psutil_aix.c
@@ -0,0 +1,930 @@
+/*
+ * 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-specific module methods for _psutil_aix
+ *
+ */
+
+// 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 "_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;
+}
+
+
+/*
+ * 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;
+}
+
+
+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 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."},
+    {"proc_threads", psutil_proc_threads, METH_VARARGS,
+     "Return process threads"},
+    {"proc_io_counters", psutil_proc_io_counters, 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
+    {"py_psutil_testing", py_psutil_testing, METH_VARARGS,
+     "Return True if PSUTIL_TESTING env var is set"},
+
+    {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);
+
+    if (module == NULL)
+        INITERROR;
+#if PY_MAJOR_VERSION >= 3
+    return module;
+#endif
+}
diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c
index 80c1b8cb..5268b721 100644
--- a/psutil/_psutil_posix.c
+++ b/psutil/_psutil_posix.c
@@ -18,6 +18,8 @@
 
 #ifdef PSUTIL_SUNOS10
     #include "arch/solaris/v10/ifaddrs.h"
+#elif PSUTIL_AIX
+    #include "arch/aix/ifaddrs.h"
 #else
     #include <ifaddrs.h>
 #endif
@@ -35,6 +37,8 @@
 #elif defined(PSUTIL_SUNOS)
     #include <netdb.h>
     #include <sys/sockio.h>
+#elif defined(PSUTIL_AIX)
+    #include <netdb.h>
 #endif
 
 #include "_psutil_common.h"
@@ -688,7 +692,7 @@ void init_psutil_posix(void)
     PyObject *module = Py_InitModule("_psutil_posix", PsutilMethods);
 #endif
 
-#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) || defined(PSUTIL_SUNOS)
+#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) || defined(PSUTIL_SUNOS) || defined(PSUTIL_AIX)
     PyModule_AddIntConstant(module, "AF_LINK", AF_LINK);
 #endif
 
diff --git a/psutil/_psutil_sunos.c b/psutil/_psutil_sunos.c
index 12caaec7..8f977342 100644
--- a/psutil/_psutil_sunos.c
+++ b/psutil/_psutil_sunos.c
@@ -9,13 +9,13 @@
  * this in Cython which I later on translated in C.
  */
 
-// fix compilation issue on SunOS 5.10, see:
-// https://github.com/giampaolo/psutil/issues/421
-// https://github.com/giampaolo/psutil/issues/1077
-// http://us-east.manta.joyent.com/jmc/public/opensolaris/ARChive/PSARC/2010/111/materials/s10ceval.txt
-//
-// Because LEGACY_MIB_SIZE defined in the same file there is no way to make autoconfiguration =\
-//
+/* fix compilation issue on SunOS 5.10, see:
+ * https://github.com/giampaolo/psutil/issues/421
+ * https://github.com/giampaolo/psutil/issues/1077
+ * http://us-east.manta.joyent.com/jmc/public/opensolaris/ARChive/PSARC/2010/111/materials/s10ceval.txt
+ *
+ * Because LEGACY_MIB_SIZE defined in the same file there is no way to make autoconfiguration =\
+*/
 
 #define NEW_MIB_COMPLIANT 1
 #define _STRUCTURED_PROC 1
@@ -126,9 +126,9 @@ psutil_proc_name_and_args(PyObject *self, PyObject *args) {
     char path[1000];
     psinfo_t info;
     const char *procfs_path;
-    PyObject *py_name;
-    PyObject *py_args;
-    PyObject *py_retlist;
+    PyObject *py_name = NULL;
+    PyObject *py_args = NULL;
+    PyObject *py_retlist = NULL;
 
     if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path))
         return NULL;
@@ -328,7 +328,7 @@ psutil_proc_cpu_num(PyObject *self, PyObject *args) {
     return Py_BuildValue("i", proc_num);
 
 error:
-    if (fd != NULL)
+    if (fd != -1)
         close(fd);
     if (ptr != NULL)
         free(ptr);
@@ -548,6 +548,7 @@ psutil_users(PyObject *self, PyObject *args) {
     if (py_retlist == NULL)
         return NULL;
 
+    setutxent();
     while (NULL != (ut = getutxent())) {
         if (ut->ut_type == USER_PROCESS)
             py_user_proc = Py_True;
@@ -580,7 +581,7 @@ psutil_users(PyObject *self, PyObject *args) {
         Py_DECREF(py_hostname);
         Py_DECREF(py_tuple);
     }
-    endutent();
+    endutxent();
 
     return py_retlist;
 
@@ -590,8 +591,7 @@ error:
     Py_XDECREF(py_hostname);
     Py_XDECREF(py_tuple);
     Py_DECREF(py_retlist);
-    if (ut != NULL)
-        endutent();
+    endutxent();
     return NULL;
 }
 
@@ -832,7 +832,7 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) {
         pr_addr_sz = p->pr_vaddr + p->pr_size;
 
         // perms
-        sprintf(perms, "%c%c%c%c%c%c", p->pr_mflags & MA_READ ? 'r' : '-',
+        sprintf(perms, "%c%c%c%c", p->pr_mflags & MA_READ ? 'r' : '-',
                 p->pr_mflags & MA_WRITE ? 'w' : '-',
                 p->pr_mflags & MA_EXEC ? 'x' : '-',
                 p->pr_mflags & MA_SHARED ? 's' : '-');
@@ -1332,20 +1332,20 @@ 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;
         }
     }
-    endutent();
-    if (boot_time != 0.0) {
-        return Py_BuildValue("f", boot_time);
-    }
-    else {
+    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);
 }
 
 
diff --git a/psutil/arch/aix/ifaddrs.c b/psutil/arch/aix/ifaddrs.c
new file mode 100644
index 00000000..1a819365
--- /dev/null
+++ b/psutil/arch/aix/ifaddrs.c
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+/*! Based on code from
+    https://lists.samba.org/archive/samba-technical/2009-February/063079.html
+!*/
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "ifaddrs.h"
+
+#define MAX(x,y) ((x)>(y)?(x):(y))
+#define SIZE(p) MAX((p).sa_len,sizeof(p))
+
+
+static struct sockaddr *
+sa_dup(struct sockaddr *sa1)
+{
+    struct sockaddr *sa2;
+    size_t sz = sa1->sa_len;
+    sa2 = (struct sockaddr *) calloc(1, sz);
+    if (sa2 == NULL)
+        return NULL;
+    memcpy(sa2, sa1, sz);
+    return sa2;
+}
+
+
+void freeifaddrs(struct ifaddrs *ifp)
+{
+    if (NULL == ifp) return;
+    free(ifp->ifa_name);
+    free(ifp->ifa_addr);
+    free(ifp->ifa_netmask);
+    free(ifp->ifa_dstaddr);
+    freeifaddrs(ifp->ifa_next);
+    free(ifp);
+}
+
+
+int getifaddrs(struct ifaddrs **ifap)
+{
+    int sd, ifsize;
+    char *ccp, *ecp;
+    struct ifconf ifc;
+    struct ifreq *ifr;
+    struct ifaddrs *cifa = NULL; /* current */
+    struct ifaddrs *pifa = NULL; /* previous */
+    const size_t IFREQSZ = sizeof(struct ifreq);
+    int fam;
+
+    *ifap = NULL;
+
+    sd = socket(AF_INET, SOCK_DGRAM, 0);
+    if (sd == -1)
+        goto error;
+
+    /* find how much memory to allocate for the SIOCGIFCONF call */
+    if (ioctl(sd, SIOCGSIZIFCONF, (caddr_t)&ifsize) < 0)
+        goto error;
+
+    ifc.ifc_req = (struct ifreq *) calloc(1, ifsize);
+    if (ifc.ifc_req == NULL)
+        goto error;
+    ifc.ifc_len = ifsize;
+
+    if (ioctl(sd, SIOCGIFCONF, &ifc) < 0)
+        goto error;
+
+    ccp = (char *)ifc.ifc_req;
+    ecp = ccp + ifsize;
+
+    while (ccp < ecp) {
+
+        ifr = (struct ifreq *) ccp;
+        ifsize = sizeof(ifr->ifr_name) + SIZE(ifr->ifr_addr);
+        fam = ifr->ifr_addr.sa_family;
+
+        if (fam == AF_INET || fam == AF_INET6) {
+            cifa = (struct ifaddrs *) calloc(1, sizeof(struct ifaddrs));
+            if (cifa == NULL)
+                goto error;
+            cifa->ifa_next = NULL;
+
+            if (pifa == NULL) *ifap = cifa; /* first one */
+            else pifa->ifa_next = cifa;
+
+            cifa->ifa_name = strdup(ifr->ifr_name);
+            if (cifa->ifa_name == NULL)
+                goto error;
+            cifa->ifa_flags = 0;
+            cifa->ifa_dstaddr = NULL;
+
+            cifa->ifa_addr = sa_dup(&ifr->ifr_addr);
+            if (cifa->ifa_addr == NULL)
+                goto error;
+
+            if (fam == AF_INET) {
+                if (ioctl(sd, SIOCGIFNETMASK, ifr, IFREQSZ) < 0)
+                    goto error;
+                cifa->ifa_netmask = sa_dup(&ifr->ifr_addr);
+                if (cifa->ifa_netmask == NULL)
+                    goto error;
+            }
+
+            if (0 == ioctl(sd, SIOCGIFFLAGS, ifr)) /* optional */
+                cifa->ifa_flags = ifr->ifr_flags;
+
+            if (fam == AF_INET) {
+                if (ioctl(sd, SIOCGIFDSTADDR, ifr, IFREQSZ) < 0) {
+                    if (0 == ioctl(sd, SIOCGIFBRDADDR, ifr, IFREQSZ)) {
+                        cifa->ifa_dstaddr = sa_dup(&ifr->ifr_addr);
+                        if (cifa->ifa_dstaddr == NULL)
+                            goto error;
+                    }
+                }
+                else {
+                    cifa->ifa_dstaddr = sa_dup(&ifr->ifr_addr);
+                    if (cifa->ifa_dstaddr == NULL)
+                        goto error;
+                }
+            }
+            pifa = cifa;
+        }
+
+        ccp += ifsize;
+    }
+    free(ifc.ifc_req);
+    close(sd);
+    return 0;
+error:
+    if (ifc.ifc_req != NULL)
+        free(ifc.ifc_req);
+    if (sd != -1)
+        close(sd);
+    freeifaddrs(*ifap);
+    return (-1);
+}
\ No newline at end of file
diff --git a/psutil/arch/aix/ifaddrs.h b/psutil/arch/aix/ifaddrs.h
new file mode 100644
index 00000000..3920c1cc
--- /dev/null
+++ b/psutil/arch/aix/ifaddrs.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+/*! Based on code from
+    https://lists.samba.org/archive/samba-technical/2009-February/063079.html
+!*/
+
+
+#ifndef GENERIC_AIX_IFADDRS_H
+#define GENERIC_AIX_IFADDRS_H
+
+#include <sys/socket.h>
+#include <net/if.h>
+
+#undef  ifa_dstaddr
+#undef  ifa_broadaddr
+#define ifa_broadaddr ifa_dstaddr
+
+struct ifaddrs {
+    struct ifaddrs  *ifa_next;
+    char            *ifa_name;
+    unsigned int     ifa_flags;
+    struct sockaddr *ifa_addr;
+    struct sockaddr *ifa_netmask;
+    struct sockaddr *ifa_dstaddr;
+};
+
+extern int getifaddrs(struct ifaddrs **);
+extern void freeifaddrs(struct ifaddrs *);
+
+#endif
\ No newline at end of file
diff --git a/psutil/arch/aix/net_connections.c b/psutil/arch/aix/net_connections.c
new file mode 100644
index 00000000..364cd1b7
--- /dev/null
+++ b/psutil/arch/aix/net_connections.c
@@ -0,0 +1,346 @@
+/*
+ * 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.
+ */
+
+/* Baded on code from lsof:
+ * http://www.ibm.com/developerworks/aix/library/au-lsof.html
+ * - dialects/aix/dproc.c:gather_proc_info
+ * - lib/prfp.c:process_file
+ * - dialects/aix/dsock.c:process_socket
+ * - dialects/aix/dproc.c:get_kernel_access
+*/
+
+#include "net_connections.h"
+#include <fcntl.h>
+#include <sys/types.h>
+#define _KERNEL 1
+#include <sys/file.h>
+#undef _KERNEL
+#include <stdlib.h>
+#include <sys/core.h>
+#include <sys/domain.h>
+#include <sys/un.h>
+#include <netinet/in_pcb.h>
+#include <arpa/inet.h>
+#include "net_kernel_structs.h"
+
+
+
+#define PROCINFO_INCR   (256)
+#define PROCSIZE        (sizeof(struct procentry64))
+#define FDSINFOSIZE     (sizeof(struct fdsinfo64))
+#define KMEM            "/dev/kmem"
+#define NO_SOCKET       (PyObject *)(-1)
+
+typedef u_longlong_t    KA_T;
+static int PSUTIL_CONN_NONE = 128;
+
+/* psutil_kread() - read from kernel memory */
+static int
+psutil_kread(
+    int Kd,             /* kernel memory file descriptor */
+    KA_T addr,          /* kernel memory address */
+    char *buf,          /* buffer to receive data */
+    size_t len) {       /* length to read */
+    int br;
+
+    if (lseek64(Kd, (off64_t)addr, L_SET) == (off64_t)-1) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return 1;
+    }
+    br = read(Kd, buf, len);
+    if (br == -1) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return 1;
+    }
+    if (br != len) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "size mismatch when reading kernel memory fd");
+        return 1;
+    }
+    return 0;
+}
+
+static int
+read_unp_addr(
+    int Kd,
+    KA_T unp_addr,
+    char *buf,
+    size_t buflen
+) {
+    struct sockaddr_un *ua = (struct sockaddr_un *)NULL;
+    struct sockaddr_un un;
+    struct mbuf64 mb;
+    int uo;
+
+    if (psutil_kread(Kd, unp_addr, (char *)&mb, sizeof(mb))) {
+        return 1;
+    }
+
+    uo = (int)(mb.m_hdr.mh_data - unp_addr);
+    if ((uo + sizeof(struct sockaddr)) <= sizeof(mb))
+        ua = (struct sockaddr_un *)((char *)&mb + uo);
+    else {
+        if (psutil_kread(Kd, (KA_T)mb.m_hdr.mh_data,
+                         (char *)&un, sizeof(un))) {
+            return 1;
+        }
+        ua = &un;
+    }
+    if (ua && ua->sun_path[0]) {
+        if (mb.m_len > sizeof(struct sockaddr_un))
+            mb.m_len = sizeof(struct sockaddr_un);
+        *((char *)ua + mb.m_len - 1) = '\0';
+        snprintf(buf, buflen, "%s", ua->sun_path);
+    }
+    return 0;
+}
+
+static PyObject *
+process_file(int Kd, pid32_t pid, int fd, KA_T fp) {
+    struct file64 f;
+    struct socket64 s;
+    struct protosw64 p;
+    struct domain d;
+    struct inpcb64 inp;
+    int fam;
+    struct tcpcb64 t;
+    int state = PSUTIL_CONN_NONE;
+    unsigned char *laddr = (unsigned char *)NULL;
+    unsigned char *raddr = (unsigned char *)NULL;
+    int rport, lport;
+    char laddr_str[INET6_ADDRSTRLEN];
+    char raddr_str[INET6_ADDRSTRLEN];
+    struct unpcb64 unp;
+    char unix_laddr_str[PATH_MAX] = { 0 };
+    char unix_raddr_str[PATH_MAX] = { 0 };
+
+    /* Read file structure */
+    if (psutil_kread(Kd, fp, (char *)&f, sizeof(f))) {
+        return NULL;
+    }
+    if (!f.f_count || f.f_type != DTYPE_SOCKET) {
+        return NO_SOCKET;
+    }
+
+    if (psutil_kread(Kd, (KA_T) f.f_data, (char *) &s, sizeof(s))) {
+        return NULL;
+    }
+
+    if (!s.so_type) {
+        return NO_SOCKET;
+    }
+
+    if (!s.so_proto) {
+        PyErr_SetString(PyExc_RuntimeError, "invalid socket protocol handle");
+        return NULL;
+    }
+    if (psutil_kread(Kd, (KA_T)s.so_proto, (char *)&p, sizeof(p))) {
+        return NULL;
+    }
+
+    if (!p.pr_domain) {
+        PyErr_SetString(PyExc_RuntimeError, "invalid socket protocol domain");
+        return NULL;
+    }
+    if (psutil_kread(Kd, (KA_T)p.pr_domain, (char *)&d, sizeof(d))) {
+        return NULL;
+    }
+
+    fam = d.dom_family;
+    if (fam == AF_INET || fam == AF_INET6) {
+        /* Read protocol control block */
+        if (!s.so_pcb) {
+            PyErr_SetString(PyExc_RuntimeError, "invalid socket PCB");
+            return NULL;
+        }
+        if (psutil_kread(Kd, (KA_T) s.so_pcb, (char *) &inp, sizeof(inp))) {
+            return NULL;
+        }
+
+        if (p.pr_protocol == IPPROTO_TCP) {
+            /* If this is a TCP socket, read its control block */
+            if (inp.inp_ppcb
+                &&  !psutil_kread(Kd, (KA_T)inp.inp_ppcb,
+                                  (char *)&t, sizeof(t)))
+                state = t.t_state;
+        }
+
+        if (fam == AF_INET6) {
+            laddr = (unsigned char *)&inp.inp_laddr6;
+            if (!IN6_IS_ADDR_UNSPECIFIED(&inp.inp_faddr6)) {
+                raddr = (unsigned char *)&inp.inp_faddr6;
+                rport = (int)ntohs(inp.inp_fport);
+            }
+        }
+        if (fam == AF_INET) {
+            laddr = (unsigned char *)&inp.inp_laddr;
+            if (inp.inp_faddr.s_addr != INADDR_ANY || inp.inp_fport != 0) {
+                raddr =  (unsigned char *)&inp.inp_faddr;
+                rport = (int)ntohs(inp.inp_fport);
+            }
+        }
+        lport = (int)ntohs(inp.inp_lport);
+
+        inet_ntop(fam, laddr, laddr_str, sizeof(laddr_str));
+
+        if (raddr != NULL) {
+            inet_ntop(fam, raddr, raddr_str, sizeof(raddr_str));
+            return Py_BuildValue("(iii(si)(si)ii)", fd, fam,
+                                 s.so_type, laddr_str, lport, raddr_str,
+                                 rport, state, pid);
+        }
+        else {
+            return Py_BuildValue("(iii(si)()ii)", fd, fam,
+                                 s.so_type, laddr_str, lport, state,
+                                 pid);
+        }
+    }
+
+
+    if (fam == AF_UNIX) {
+        if (psutil_kread(Kd, (KA_T) s.so_pcb, (char *)&unp, sizeof(unp))) {
+            return NULL;
+        }
+        if ((KA_T) f.f_data != (KA_T) unp.unp_socket) {
+            PyErr_SetString(PyExc_RuntimeError, "unp_socket mismatch");
+            return NULL;
+        }
+
+        if (unp.unp_addr) {
+            if (read_unp_addr(Kd, unp.unp_addr, unix_laddr_str,
+                              sizeof(unix_laddr_str))) {
+                return NULL;
+            }
+        }
+
+        if (unp.unp_conn) {
+            if (psutil_kread(Kd, (KA_T) unp.unp_conn, (char *)&unp,
+                sizeof(unp))) {
+                return NULL;
+            }
+            if (read_unp_addr(Kd, unp.unp_addr, unix_raddr_str,
+                              sizeof(unix_raddr_str))) {
+                return NULL;
+            }
+        }
+
+        return Py_BuildValue("(iiissii)", fd, d.dom_family,
+                 s.so_type, unix_laddr_str, unix_raddr_str, PSUTIL_CONN_NONE,
+                 pid);
+    }
+    return NO_SOCKET;
+}
+
+PyObject *
+psutil_net_connections(PyObject *self, PyObject *args) {
+    PyObject *py_retlist = PyList_New(0);
+    PyObject *py_tuple = NULL;
+    KA_T fp;
+    int Kd = -1;
+    int i, np;
+    struct procentry64 *p;
+    struct fdsinfo64 *fds = (struct fdsinfo64 *)NULL;
+    size_t msz;
+    pid32_t requested_pid;
+    pid32_t pid;
+    int Np = 0;          /* number of processes */
+    struct procentry64 *processes = (struct procentry64 *)NULL;
+                    /* the process table */
+
+    if (py_retlist == NULL)
+        goto error;
+    if (! PyArg_ParseTuple(args, "i", &requested_pid))
+        goto error;
+
+    Kd = open(KMEM, O_RDONLY, 0);
+    if (Kd < 0) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, KMEM);
+        goto error;
+    }
+
+    /* Read the process table */
+    msz = (size_t)(PROCSIZE * PROCINFO_INCR);
+    processes = (struct procentry64 *)malloc(msz);
+    if (!processes) {
+        PyErr_NoMemory();
+        goto error;
+    }
+    Np = PROCINFO_INCR;
+    np = pid = 0;
+    p = processes;
+    while ((i = getprocs64(p, PROCSIZE, (struct fdsinfo64 *)NULL, 0, &pid,
+                 PROCINFO_INCR))
+    == PROCINFO_INCR) {
+        np += PROCINFO_INCR;
+        if (np >= Np) {
+            msz = (size_t)(PROCSIZE * (Np + PROCINFO_INCR));
+            processes = (struct procentry64 *)realloc((char *)processes, msz);
+            if (!processes) {
+                PyErr_NoMemory();
+                goto error;
+            }
+            Np += PROCINFO_INCR;
+        }
+        p = (struct procentry64 *)((char *)processes + (np * PROCSIZE));
+    }
+
+    if (i > 0)
+        np += i;
+
+    /* Loop through processes */
+    for (p = processes; np > 0; np--, p++) {
+        pid = p->pi_pid;
+        if (requested_pid != -1 && requested_pid != pid)
+            continue;
+        if (p->pi_state == 0 || p->pi_state == SZOMB)
+            continue;
+
+
+        if (!fds) {
+            fds = (struct fdsinfo64 *)malloc((size_t)FDSINFOSIZE);
+            if (!fds) {
+                PyErr_NoMemory();
+                goto error;
+            }
+        }
+        if (getprocs64((struct procentry64 *)NULL, PROCSIZE, fds, FDSINFOSIZE,
+              &pid, 1)
+        != 1)
+            continue;
+
+        /* loop over file descriptors */
+        for (i = 0; i < p->pi_maxofile; i++) {
+            fp = (KA_T)fds->pi_ufd[i].fp;
+            if (fp) {
+                py_tuple = process_file(Kd, p->pi_pid, i, fp);
+                if (py_tuple == NULL)
+                    goto error;
+                if (py_tuple != NO_SOCKET) {
+                    if (PyList_Append(py_retlist, py_tuple))
+                        goto error;
+                    Py_DECREF(py_tuple);
+                }
+            }
+        }
+    }
+    close(Kd);
+    free(processes);
+    if (fds != NULL)
+        free(fds);
+    return py_retlist;
+
+error:
+    Py_XDECREF(py_tuple);
+    Py_DECREF(py_retlist);
+    if (Kd > 0)
+        close(Kd);
+    if (processes != NULL)
+        free(processes);
+    if (fds != NULL)
+        free(fds);
+    return NULL;
+}
diff --git a/psutil/arch/aix/net_connections.h b/psutil/arch/aix/net_connections.h
new file mode 100644
index 00000000..f6a726cb
--- /dev/null
+++ b/psutil/arch/aix/net_connections.h
@@ -0,0 +1,10 @@
+/*
+ * 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.
+ */
+
+#include <Python.h>
+
+PyObject* psutil_net_connections(PyObject *self, PyObject *args);
diff --git a/psutil/arch/aix/net_kernel_structs.h b/psutil/arch/aix/net_kernel_structs.h
new file mode 100644
index 00000000..09f320ff
--- /dev/null
+++ b/psutil/arch/aix/net_kernel_structs.h
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+/* The kernel is always 64 bit but Python is usually compiled as a 32 bit
+ * process. We're reading the kernel memory to get the network connections,
+ * so we need the structs we read to be defined with 64 bit "pointers".
+ * Here are the partial definitions of the structs we use, taken from the
+ * header files, with data type sizes converted to their 64 bit counterparts,
+ * and unused data truncated. */
+
+#ifdef __64BIT__
+/* In case we're in a 64 bit process after all */
+#include <sys/socketvar.h>
+#include <sys/protosw.h>
+#include <sys/unpcb.h>
+#include <sys/mbuf_base.h>
+#include <netinet/ip_var.h>
+#include <netinet/tcp.h>
+#include <netinet/tcpip.h>
+#include <netinet/tcp_timer.h>
+#include <netinet/tcp_var.h>
+#define file64 file
+#define socket64 socket
+#define protosw64 protosw
+#define inpcb64 inpcb
+#define tcpcb64 tcpcb
+#define unpcb64 unpcb
+#define mbuf64 mbuf
+#else
+  struct file64 {
+    int f_flag;
+    int f_count;
+    int f_options;
+    int f_type;
+    u_longlong_t f_data;
+ };
+
+ struct socket64 {
+    short   so_type;                 /* generic type, see socket.h */
+    short   so_options;              /* from socket call, see socket.h */
+    ushort  so_linger;               /* time to linger while closing */
+    short   so_state;                /* internal state flags SS_*, below */
+    u_longlong_t so_pcb;             /* protocol control block */
+    u_longlong_t so_proto;           /* protocol handle */
+ };
+
+ struct protosw64 {
+    short   pr_type;                 /* socket type used for */
+    u_longlong_t pr_domain;          /* domain protocol a member of */
+    short   pr_protocol;             /* protocol number */
+    short   pr_flags;                /* see below */
+ };
+
+ struct inpcb64 {
+    u_longlong_t inp_next,inp_prev;
+                                     /* pointers to other pcb's */
+    u_longlong_t inp_head;           /* pointer back to chain of inpcb's
+                                       for this protocol */
+    u_int32_t inp_iflowinfo;         /* input flow label */
+    u_short inp_fport;               /* foreign port */
+    u_int16_t inp_fatype;            /* foreign address type */
+    union   in_addr_6 inp_faddr_6;   /* foreign host table entry */
+    u_int32_t inp_oflowinfo;         /* output flow label */
+    u_short inp_lport;               /* local port */
+    u_int16_t inp_latype;            /* local address type */
+    union   in_addr_6 inp_laddr_6;   /* local host table entry */
+    u_longlong_t inp_socket;         /* back pointer to socket */
+    u_longlong_t inp_ppcb;           /* pointer to per-protocol pcb */
+    u_longlong_t space_rt;
+    struct  sockaddr_in6 spare_dst;
+    u_longlong_t inp_ifa;            /* interface address to use */
+    int     inp_flags;               /* generic IP/datagram flags */
+};
+
+struct tcpcb64 {
+    u_longlong_t seg__next;
+    u_longlong_t seg__prev;
+    short   t_state;                 /* state of this connection */
+};
+
+struct unpcb64 {
+        u_longlong_t unp_socket;     /* pointer back to socket */
+        u_longlong_t unp_vnode;      /* if associated with file */
+        ino_t   unp_vno;             /* fake vnode number */
+        u_longlong_t unp_conn;       /* control block of connected socket */
+        u_longlong_t unp_refs;       /* referencing socket linked list */
+        u_longlong_t unp_nextref;    /* link in unp_refs list */
+        u_longlong_t unp_addr;       /* bound address of socket */
+};
+
+struct m_hdr64
+{
+    u_longlong_t mh_next;            /* next buffer in chain */
+    u_longlong_t mh_nextpkt;         /* next chain in queue/record */
+    long mh_len;                     /* amount of data in this mbuf */
+    u_longlong_t mh_data;            /* location of data */
+};
+
+struct mbuf64
+{
+    struct m_hdr64 m_hdr;
+};
+
+#define m_len               m_hdr.mh_len
+
+#endif
\ No newline at end of file
diff --git a/scripts/procinfo.py b/scripts/procinfo.py
index d8625560..54205de3 100755
--- a/scripts/procinfo.py
+++ b/scripts/procinfo.py
@@ -225,7 +225,8 @@ def run(pid, verbose=False):
 
     if 'io_counters' in pinfo:
         print_('I/O', str_ntuple(pinfo['io_counters'], bytes2human=True))
-    print_("ctx-switches", str_ntuple(pinfo['num_ctx_switches']))
+    if 'num_ctx_switches' in pinfo:
+        print_("ctx-switches", str_ntuple(pinfo['num_ctx_switches']))
     if pinfo['children']:
         template = "%-6s %s"
         print_("children", template % ("PID", "NAME"))
diff --git a/setup.py b/setup.py
index 5f268349..1bc94083 100755
--- a/setup.py
+++ b/setup.py
@@ -37,6 +37,7 @@ from _common import OSX  # NOQA
 from _common import POSIX  # NOQA
 from _common import SUNOS  # NOQA
 from _common import WINDOWS  # NOQA
+from _common import AIX  # NOQA
 
 
 macros = []
@@ -239,7 +240,17 @@ elif SUNOS:
         ],
         define_macros=macros,
         libraries=['kstat', 'nsl', 'socket'])
-
+# AIX
+elif AIX:
+    macros.append(("PSUTIL_AIX", 1))
+    ext = Extension(
+        'psutil._psutil_aix',
+        sources=sources + [
+            'psutil/_psutil_aix.c',
+            'psutil/arch/aix/net_connections.c',
+            'psutil/arch/aix/ifaddrs.c'],
+        libraries=['perfstat'],
+        define_macros=macros)
 else:
     sys.exit('platform %s is not supported' % sys.platform)
 
@@ -254,6 +265,8 @@ if POSIX:
         if platform.release() == '5.10':
             posix_extension.sources.append('psutil/arch/solaris/v10/ifaddrs.c')
             posix_extension.define_macros.append(('PSUTIL_SUNOS10', 1))
+    elif AIX:
+        posix_extension.sources.append('psutil/arch/aix/ifaddrs.c')
 
     extensions = [ext, posix_extension]
 else:


Test patch:
diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py
index c9cd5006..033d925e 100644
--- a/psutil/tests/__init__.py
+++ b/psutil/tests/__init__.py
@@ -72,6 +72,7 @@ __all__ = [
     "HAS_IONICE", "HAS_MEMORY_MAPS", "HAS_PROC_CPU_NUM", "HAS_RLIMIT",
     "HAS_SENSORS_BATTERY", "HAS_BATTERY""HAS_SENSORS_FANS",
     "HAS_SENSORS_TEMPERATURES", "HAS_MEMORY_FULL_INFO",
+    "HAS_NUM_CTX_SWITCHES",
     # subprocesses
     'pyrun', 'reap_children', 'get_test_subprocess', 'create_zombie_proc',
     'create_proc_children_pair',
@@ -156,6 +157,7 @@ HAS_PROC_IO_COUNTERS = hasattr(psutil.Process, "io_counters")
 HAS_IONICE = hasattr(psutil.Process, "ionice")
 HAS_MEMORY_FULL_INFO = 'uss' in psutil.Process().memory_full_info()._fields
 HAS_MEMORY_MAPS = hasattr(psutil.Process, "memory_maps")
+HAS_NUM_CTX_SWITCHES = hasattr(psutil.Process, "num_ctx_switches")
 HAS_PROC_CPU_NUM = hasattr(psutil.Process, "cpu_num")
 HAS_RLIMIT = hasattr(psutil.Process, "rlimit")
 HAS_SENSORS_BATTERY = hasattr(psutil, "sensors_battery")
diff --git a/psutil/tests/test_aix.py b/psutil/tests/test_aix.py
new file mode 100644
index 00000000..281c9bb0
--- /dev/null
+++ b/psutil/tests/test_aix.py
@@ -0,0 +1,129 @@
+#!/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 os
+import re
+
+import psutil
+from psutil import AIX
+from psutil.tests import retry_before_failing
+from psutil.tests import run_test_module_by_name
+from psutil.tests import sh
+from psutil.tests import unittest
+
+@unittest.skipIf(not AIX, "AIX only")
+class AIXSpecificTestCase(unittest.TestCase):
+
+    def test_virtual_memory(self):
+        out = sh('/usr/bin/svmon -O unit=KB')
+
+        # example output:
+        #     Unit: KB
+        #     --------------------------------------------------------------------------------------
+        #                    size       inuse        free         pin     virtual  available   mmode
+        #     memory      4194304     1844828     2349476     1250208     1412976    2621596     Ded
+        #     pg space     524288        8304
+        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')
+
+        # example output:
+        #     Page Space      Physical Volume   Volume Group    Size %Used   Active    Auto    Type   Chksum
+        #     hd6             hdisk0            rootvg         512MB     2     yes     yes      lv       0
+        # 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__)
diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py
index 65bad757..13a737e8 100755
--- a/psutil/tests/test_contracts.py
+++ b/psutil/tests/test_contracts.py
@@ -16,6 +16,7 @@ import time
 import traceback
 from contextlib import closing
 
+from psutil import AIX
 from psutil import BSD
 from psutil import FREEBSD
 from psutil import LINUX
@@ -65,7 +66,8 @@ class TestAvailability(unittest.TestCase):
         self.assertEqual(hasattr(psutil, "win_service_get"), WINDOWS)
 
     def test_PROCFS_PATH(self):
-        self.assertEqual(hasattr(psutil, "PROCFS_PATH"), LINUX or SUNOS)
+        self.assertEqual(hasattr(psutil, "PROCFS_PATH"),
+                         LINUX or SUNOS or AIX)
 
     def test_win_priority(self):
         ae = self.assertEqual
@@ -159,7 +161,7 @@ class TestAvailability(unittest.TestCase):
 
     def test_proc_memory_maps(self):
         hasit = hasattr(psutil.Process, "memory_maps")
-        self.assertEqual(hasit, False if OPENBSD or NETBSD else True)
+        self.assertEqual(hasit, False if OPENBSD or NETBSD or AIX else True)
 
 
 # ===================================================================
@@ -372,12 +374,14 @@ class TestFetchAllProcesses(unittest.TestCase):
         self.assertGreaterEqual(ret, 0)
 
     def ppid(self, ret, proc):
-        self.assertIsInstance(ret, int)
+        self.assertIsInstance(ret, (int, long))
         self.assertGreaterEqual(ret, 0)
 
     def name(self, ret, proc):
         self.assertIsInstance(ret, str)
-        assert ret
+        # on AIX, "<exiting>" processes don't have names
+        if not AIX:
+            assert ret
 
     def create_time(self, ret, proc):
         self.assertIsInstance(ret, float)
@@ -482,7 +486,7 @@ class TestFetchAllProcesses(unittest.TestCase):
         for value in ret:
             self.assertIsInstance(value, (int, long))
             self.assertGreaterEqual(value, 0)
-        if POSIX and ret.vms != 0:
+        if POSIX and not AIX and ret.vms != 0:
             # VMS is always supposed to be the highest
             for name in ret._fields:
                 if name != 'vms':
@@ -536,8 +540,8 @@ class TestFetchAllProcesses(unittest.TestCase):
             check_connection_ntuple(conn)
 
     def cwd(self, ret, proc):
-        self.assertIsInstance(ret, str)
-        if ret is not None:  # BSD may return None
+        if ret:     # 'ret' can be None or empty
+            self.assertIsInstance(ret, str)
             assert os.path.isabs(ret), ret
             try:
                 st = os.stat(ret)
diff --git a/psutil/tests/test_memory_leaks.py b/psutil/tests/test_memory_leaks.py
index 680fe780..b7638d32 100755
--- a/psutil/tests/test_memory_leaks.py
+++ b/psutil/tests/test_memory_leaks.py
@@ -24,6 +24,7 @@ import time
 
 import psutil
 import psutil._common
+from psutil import AIX
 from psutil import LINUX
 from psutil import OPENBSD
 from psutil import OSX
@@ -38,6 +39,7 @@ from psutil.tests import HAS_CPU_FREQ
 from psutil.tests import HAS_ENVIRON
 from psutil.tests import HAS_IONICE
 from psutil.tests import HAS_MEMORY_MAPS
+from psutil.tests import HAS_NUM_CTX_SWITCHES
 from psutil.tests import HAS_PROC_CPU_NUM
 from psutil.tests import HAS_PROC_IO_COUNTERS
 from psutil.tests import HAS_RLIMIT
@@ -288,6 +290,7 @@ class TestProcessObjectLeaks(TestMemLeak):
         self.execute(self.proc.num_fds)
 
     @skip_if_linux()
+    @unittest.skipIf(not HAS_NUM_CTX_SWITCHES, "not supported")
     def test_num_ctx_switches(self):
         self.execute(self.proc.num_ctx_switches)
 
diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py
index 580abdfd..f42a6e63 100755
--- a/psutil/tests/test_posix.py
+++ b/psutil/tests/test_posix.py
@@ -15,6 +15,7 @@ import sys
 import time
 
 import psutil
+from psutil import AIX
 from psutil import BSD
 from psutil import LINUX
 from psutil import OPENBSD
@@ -48,6 +49,8 @@ def ps(cmd):
     if SUNOS:
         cmd = cmd.replace("-o command", "-o comm")
         cmd = cmd.replace("-o start", "-o stime")
+    if AIX:
+        cmd = cmd.replace("-o rss", "-o rssize")
     output = sh(cmd)
     if not LINUX:
         output = output.split('\n')[1].strip()
@@ -206,7 +209,9 @@ class TestProcess(unittest.TestCase):
     # incorrect value (20); the real deal is getpriority(2) which
     # returns 0; psutil relies on it, see:
     # https://github.com/giampaolo/psutil/issues/1082
+    # AIX has the same issue
     @unittest.skipIf(SUNOS, "not reliable on SUNOS")
+    @unittest.skipIf(AIX, "not reliable on AIX")
     def test_nice(self):
         ps_nice = ps("ps --no-headers -o nice -p %s" % self.pid)
         psutil_nice = psutil.Process().nice()
@@ -262,7 +267,7 @@ class TestSystemAPIs(unittest.TestCase):
     def test_pids(self):
         # Note: this test might fail if the OS is starting/killing
         # other processes in the meantime
-        if SUNOS:
+        if SUNOS or AIX:
             cmd = ["ps", "-A", "-o", "pid"]
         else:
             cmd = ["ps", "ax", "-o", "pid"]
@@ -355,6 +360,8 @@ class TestSystemAPIs(unittest.TestCase):
                               psutil._psposix.wait_pid, os.getpid())
             assert m.called
 
+    # AIX can return '-' in df output instead of numbers, e.g. for /proc
+    @unittest.skipIf(AIX, "unreliable on AIX")
     def test_disk_usage(self):
         def df(device):
             out = sh("df -k %s" % device).strip()
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index 1f695a06..fab3a421 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -21,6 +21,7 @@ import types
 
 import psutil
 
+from psutil import AIX
 from psutil import BSD
 from psutil import LINUX
 from psutil import NETBSD
@@ -44,6 +45,7 @@ from psutil.tests import HAS_CPU_AFFINITY
 from psutil.tests import HAS_ENVIRON
 from psutil.tests import HAS_IONICE
 from psutil.tests import HAS_MEMORY_MAPS
+from psutil.tests import HAS_NUM_CTX_SWITCHES
 from psutil.tests import HAS_PROC_CPU_NUM
 from psutil.tests import HAS_PROC_IO_COUNTERS
 from psutil.tests import HAS_RLIMIT
@@ -317,7 +319,7 @@ class TestProcess(unittest.TestCase):
         with open(PYTHON, 'rb') as f:
             f.read()
         io2 = p.io_counters()
-        if not BSD:
+        if not BSD and not AIX:
             self.assertGreater(io2.read_count, io1.read_count)
             self.assertEqual(io2.write_count, io1.write_count)
             if LINUX:
@@ -868,7 +870,14 @@ class TestProcess(unittest.TestCase):
         # CPUs on get):
         # AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, ... != [0]
         for n in all_cpus:
-            p.cpu_affinity([n])
+            try:
+                p.cpu_affinity([n])
+            except ValueError as err:
+                if TRAVIS and LINUX and "not eligible" in str(err):
+                    # https://travis-ci.org/giampaolo/psutil/jobs/279890461
+                    continue
+                else:
+                    raise
             self.assertEqual(p.cpu_affinity(), [n])
             if hasattr(os, "sched_getaffinity"):
                 self.assertEqual(p.cpu_affinity(),
@@ -987,6 +996,7 @@ class TestProcess(unittest.TestCase):
 
     @skip_on_not_implemented(only_if=LINUX)
     @unittest.skipIf(OPENBSD or NETBSD, "not reliable on OPENBSD & NETBSD")
+    @unittest.skipIf(not HAS_NUM_CTX_SWITCHES, "not supported")
     def test_num_ctx_switches(self):
         p = psutil.Process()
         before = sum(p.num_ctx_switches())
@@ -1264,7 +1274,15 @@ class TestProcess(unittest.TestCase):
         # set methods
         succeed_or_zombie_p_exc(zproc.parent)
         if hasattr(zproc, 'cpu_affinity'):
-            succeed_or_zombie_p_exc(zproc.cpu_affinity, [0])
+            try:
+                succeed_or_zombie_p_exc(zproc.cpu_affinity, [0])
+            except ValueError as err:
+                if TRAVIS and LINUX and "not eligible" in str(err):
+                    # https://travis-ci.org/giampaolo/psutil/jobs/279890461
+                    pass
+                else:
+                    raise
+
         succeed_or_zombie_p_exc(zproc.nice, 0)
         if hasattr(zproc, 'ionice'):
             if LINUX:
diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py
index e93bb6b5..3485fb8f 100755
--- a/psutil/tests/test_system.py
+++ b/psutil/tests/test_system.py
@@ -19,6 +19,7 @@ import tempfile
 import time
 
 import psutil
+from psutil import AIX
 from psutil import BSD
 from psutil import FREEBSD
 from psutil import LINUX
@@ -742,7 +743,8 @@ class TestSystemAPIs(unittest.TestCase):
         for name in infos._fields:
             value = getattr(infos, name)
             self.assertGreaterEqual(value, 0)
-            if name in ('ctx_switches', 'interrupts'):
+            # on AIX, ctx_switches is always 0
+            if not AIX and name in ('ctx_switches', 'interrupts'):
                 self.assertGreater(value, 0)
 
     @unittest.skipIf(not HAS_CPU_FREQ, "not suported")