summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwiggin15 <wiggin15@yahoo.com>2017-09-26 14:52:42 +0300
committerGiampaolo Rodola <g.rodola@gmail.com>2017-09-26 19:52:42 +0800
commit1ebe625e5aa21b33e9de5652c305d1d0a2147059 (patch)
tree79448f6cc3df6d892c2fe22ead726b2a94a52111
parent730e0fbba6a2ef4a01a5a67759e15dad5613d3a9 (diff)
downloadpsutil-1ebe625e5aa21b33e9de5652c305d1d0a2147059.tar.gz
AIX support (#1123)
* AIX support * AIX support * AIX support * AIX support - use get_procfs_path() instead of /proc * AIX support - group sections like in other modules * AIX support * AIX support * AIX support * AIX support - remove unnecessary dict copy
-rw-r--r--MANIFEST.in1
-rw-r--r--docs/index.rst74
-rw-r--r--psutil/TODO.aix16
-rw-r--r--psutil/__init__.py25
-rw-r--r--psutil/_common.py1
-rw-r--r--psutil/_psaix.py590
-rw-r--r--psutil/_psutil_aix.c930
-rw-r--r--psutil/_psutil_posix.c6
-rw-r--r--psutil/arch/aix/ifaddrs.c149
-rw-r--r--psutil/arch/aix/ifaddrs.h35
-rw-r--r--psutil/arch/aix/net_connections.c346
-rw-r--r--psutil/arch/aix/net_connections.h10
-rw-r--r--psutil/arch/aix/net_kernel_structs.h110
-rw-r--r--psutil/tests/__init__.py2
-rw-r--r--psutil/tests/test_aix.py129
-rwxr-xr-xpsutil/tests/test_contracts.py18
-rwxr-xr-xpsutil/tests/test_memory_leaks.py3
-rwxr-xr-xpsutil/tests/test_posix.py9
-rwxr-xr-xpsutil/tests/test_process.py5
-rwxr-xr-xpsutil/tests/test_system.py4
-rwxr-xr-xscripts/procinfo.py3
-rwxr-xr-xsetup.py15
22 files changed, 2429 insertions, 52 deletions
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>`__.
@@ -564,6 +564,10 @@ Network
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.
.. note::
@@ -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/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/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 0686ba25..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:
@@ -994,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())
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")
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: