diff options
author | wiggin15 <wiggin15@yahoo.com> | 2017-09-26 14:52:42 +0300 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2017-09-26 19:52:42 +0800 |
commit | 1ebe625e5aa21b33e9de5652c305d1d0a2147059 (patch) | |
tree | 79448f6cc3df6d892c2fe22ead726b2a94a52111 | |
parent | 730e0fbba6a2ef4a01a5a67759e15dad5613d3a9 (diff) | |
download | psutil-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.in | 1 | ||||
-rw-r--r-- | docs/index.rst | 74 | ||||
-rw-r--r-- | psutil/TODO.aix | 16 | ||||
-rw-r--r-- | psutil/__init__.py | 25 | ||||
-rw-r--r-- | psutil/_common.py | 1 | ||||
-rw-r--r-- | psutil/_psaix.py | 590 | ||||
-rw-r--r-- | psutil/_psutil_aix.c | 930 | ||||
-rw-r--r-- | psutil/_psutil_posix.c | 6 | ||||
-rw-r--r-- | psutil/arch/aix/ifaddrs.c | 149 | ||||
-rw-r--r-- | psutil/arch/aix/ifaddrs.h | 35 | ||||
-rw-r--r-- | psutil/arch/aix/net_connections.c | 346 | ||||
-rw-r--r-- | psutil/arch/aix/net_connections.h | 10 | ||||
-rw-r--r-- | psutil/arch/aix/net_kernel_structs.h | 110 | ||||
-rw-r--r-- | psutil/tests/__init__.py | 2 | ||||
-rw-r--r-- | psutil/tests/test_aix.py | 129 | ||||
-rwxr-xr-x | psutil/tests/test_contracts.py | 18 | ||||
-rwxr-xr-x | psutil/tests/test_memory_leaks.py | 3 | ||||
-rwxr-xr-x | psutil/tests/test_posix.py | 9 | ||||
-rwxr-xr-x | psutil/tests/test_process.py | 5 | ||||
-rwxr-xr-x | psutil/tests/test_system.py | 4 | ||||
-rwxr-xr-x | scripts/procinfo.py | 3 | ||||
-rwxr-xr-x | setup.py | 15 |
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")) @@ -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: |