From 1e0aa8ffcecdfcf1c3892e2edf1909b9e455549c Mon Sep 17 00:00:00 2001 From: Arnon Yaari Date: Wed, 11 Mar 2015 19:14:31 +0200 Subject: [PATCH] AIX support --- docs/index.rst | 70 +-- psutil/TODO.aix | 30 ++ psutil/__init__.py | 20 +- psutil/_common.py | 1 + psutil/_psaix.py | 488 +++++++++++++++++ psutil/_psutil_aix.c | 777 +++++++++++++++++++++++++++ psutil/_psutil_aix.h | 25 + psutil/_psutil_posix.c | 6 +- psutil/arch/aix/ifaddrs.c | 118 ++++ psutil/arch/aix/ifaddrs.h | 26 + psutil/arch/aix/net_connections.c | 330 ++++++++++++ psutil/arch/aix/net_connections.h | 3 + psutil/arch/aix/net_kernel_structs.h | 103 ++++ setup.py | 13 +- 14 files changed, 1970 insertions(+), 40 deletions(-) create mode 100644 psutil/TODO.aix create mode 100644 psutil/_psaix.py create mode 100644 psutil/_psutil_aix.c create mode 100644 psutil/_psutil_aix.h create mode 100644 psutil/arch/aix/ifaddrs.c create mode 100644 psutil/arch/aix/ifaddrs.h create mode 100644 psutil/arch/aix/net_connections.c create mode 100644 psutil/arch/aix/net_connections.h create mode 100644 psutil/arch/aix/net_kernel_structs.h diff --git a/docs/index.rst b/docs/index.rst index f9808d6c36..b5f8296821 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 `__. @@ -563,6 +563,10 @@ Network (OSX) :class:`psutil.AccessDenied` is always raised unless running as root. This is a limitation of the OS and ``lsof`` does the same. + .. note:: + (AIX) :class:`psutil.AccessDenied` is always raised unless running as root + (lsof does the same). + .. note:: (Solaris) UNIX sockets are not supported. @@ -1358,7 +1362,7 @@ Process class >>> p.io_counters() pio(read_count=454556, write_count=3456, read_bytes=110592, write_bytes=0, read_chars=769931, write_chars=203) - Availability: all platforms except OSX and Solaris + Availability: Linux, BSD, Windows, AIX .. versionchanged:: 5.2.0 added *read_chars* and *write_chars* on Linux; added *other_count* and *other_bytes* on Windows. @@ -1368,6 +1372,8 @@ Process class The number voluntary and involuntary context switches performed by this process (cumulative). + Availability: all platforms except AIX + .. method:: num_fds() The number of file descriptors currently opened by this process @@ -1503,33 +1509,33 @@ Process class The "portable" fields available on all plaforms are `rss` and `vms`. All numbers are expressed in bytes. - +---------+---------+-------+---------+------------------------------+ - | Linux | OSX | BSD | Solaris | Windows | - +=========+=========+=======+=========+==============================+ - | rss | rss | rss | rss | rss (alias for ``wset``) | - +---------+---------+-------+---------+------------------------------+ - | vms | vms | vms | vms | vms (alias for ``pagefile``) | - +---------+---------+-------+---------+------------------------------+ - | shared | pfaults | text | | num_page_faults | - +---------+---------+-------+---------+------------------------------+ - | text | pageins | data | | peak_wset | - +---------+---------+-------+---------+------------------------------+ - | lib | | stack | | wset | - +---------+---------+-------+---------+------------------------------+ - | data | | | | peak_paged_pool | - +---------+---------+-------+---------+------------------------------+ - | dirty | | | | paged_pool | - +---------+---------+-------+---------+------------------------------+ - | | | | | peak_nonpaged_pool | - +---------+---------+-------+---------+------------------------------+ - | | | | | nonpaged_pool | - +---------+---------+-------+---------+------------------------------+ - | | | | | pagefile | - +---------+---------+-------+---------+------------------------------+ - | | | | | peak_pagefile | - +---------+---------+-------+---------+------------------------------+ - | | | | | private | - +---------+---------+-------+---------+------------------------------+ + +---------+---------+-------+---------+-----+------------------------------+ + | Linux | OSX | BSD | Solaris | AIX | Windows | + +=========+=========+=======+=========+=====+==============================+ + | rss | rss | rss | rss | rss | rss (alias for ``wset``) | + +---------+---------+-------+---------+-----+------------------------------+ + | vms | vms | vms | vms | vms | vms (alias for ``pagefile``) | + +---------+---------+-------+---------+-----+------------------------------+ + | shared | pfaults | text | | | num_page_faults | + +---------+---------+-------+---------+-----+------------------------------+ + | text | pageins | data | | | peak_wset | + +---------+---------+-------+---------+-----+------------------------------+ + | lib | | stack | | | wset | + +---------+---------+-------+---------+-----+------------------------------+ + | data | | | | | peak_paged_pool | + +---------+---------+-------+---------+-----+------------------------------+ + | dirty | | | | | paged_pool | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | peak_nonpaged_pool | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | nonpaged_pool | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | pagefile | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | peak_pagefile | + +---------+---------+-------+---------+-----+------------------------------+ + | | | | | | private | + +---------+---------+-------+---------+-----+------------------------------+ - **rss**: aka "Resident Set Size", this is the non-swapped physical memory a process has used. @@ -1700,7 +1706,7 @@ Process class pmmap_ext(addr='02829000-02ccf000', perms='rw-p', path='[heap]', rss=4743168, size=4874240, pss=4743168, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=4743168, referenced=4718592, anonymous=4743168, swap=0), ...] - Availability: All platforms except OpenBSD and NetBSD. + Availability: All platforms except OpenBSD, NetBSD and AIX. .. method:: children(recursive=False) @@ -1864,6 +1870,10 @@ Process class .. versionchanged:: 5.3.0 : "laddr" and "raddr" are named tuples. + .. note:: + (AIX) :class:`psutil.AccessDenied` is always raised unless running + as root (lsof does the same). + .. method:: is_running() Return whether the current process is running in the current process list. diff --git a/psutil/TODO.aix b/psutil/TODO.aix new file mode 100644 index 0000000000..d6eedccb8c --- /dev/null +++ b/psutil/TODO.aix @@ -0,0 +1,30 @@ +AIX support is experimental and incomplete at this time. +The following functions and methods are UNSUPPORTED on the AIX platform: + + psutil.Process.memory_maps + psutil.Process.num_ctx_switches + + +Process.io_counters read count is always 0 + + +TestSystemAPIs.test_pid_exists_2 there are pids in /proc that don't really exist +TestProcess.test_name isolated python calls execve which changes process name +TestProcess.test_num_fds opening a socket doesn't create fd in /proc/pid/fd (until data is sent??) +TestProcess.test_open_files /dev/null shows in open_files but it isn't a file +TestProcess.test_pid_0 pid 0 doesn't have a name on AIX +TestProcess.test_prog_w_funky_name funky name test doesn't work on UNIX environments +TestProcess.test_zombie_process trying to create a zombie process doesn't create a zombie process on AIX + +TestProcess.test_memory_maps missing API +TestProcess.test_num_ctx_switches missing API +TestExampleScripts.test_pmap missing API + +TestProcess.test_send_signal unknown +TestFetchAllProcesses.test_fetch_all unknown +TestProcess.test_Popen unknown (flaky) +TestProcess.test_children unknown (flaky) +TestProcess.test_children_recursive unknown (flaky) +TestProcess.test_connections unknown (flaky) +TestSystemAPIs.test_wait_procs_no_timeout unknown (flaky) +TestProcess.test_cmdline long args are cut from cmdline in /proc/pid/psinfo and getargs (flaky) \ No newline at end of file diff --git a/psutil/__init__.py b/psutil/__init__.py index 50ae2a3370..4574f33b13 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -82,6 +82,7 @@ from ._common import POSIX # NOQA from ._common import SUNOS from ._common import WINDOWS +from ._common import AIX if LINUX: # This is public API and it will be retrieved from _pslinux.py @@ -158,6 +159,9 @@ # _pssunos.py via sys.modules. PROCFS_PATH = "/proc" +elif AIX: + from . import _psaix as _psplatform + else: # pragma: no cover raise NotImplementedError('platform %s is not supported' % sys.platform) @@ -185,7 +189,7 @@ "POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED", "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "OSX", "POSIX", "SUNOS", - "WINDOWS", + "WINDOWS", "AIX", # classes "Process", "Popen", @@ -785,7 +789,7 @@ def num_fds(self): """ 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 +894,12 @@ def num_handles(self): """ 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 not AIX: + 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 +1176,6 @@ def memory_percent(self, memtype="rss"): 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 7c4af3d85b..2d562f93e4 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -81,6 +81,7 @@ 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 0000000000..e02296453a --- /dev/null +++ b/psutil/_psaix.py @@ -0,0 +1,488 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""AIX platform implementation.""" + +import errno +import glob +import os +import re +import subprocess +import sys +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_posix as cext_posix +from . import _psutil_aix as cext +from ._common import socktype_to_enum, sockfam_to_enum +from ._common import NIC_DUPLEX_FULL, NIC_DUPLEX_HALF, NIC_DUPLEX_UNKNOWN +from ._common import usage_percent +from ._compat import PY3 + + +__extra__all__ = [] + + +PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') +AF_LINK = cext_posix.AF_LINK + +PROC_STATUSES = { + + cext.SIDL: _common.STATUS_IDLE, + cext.SRUN: _common.STATUS_RUNNING, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SSWAP: _common.STATUS_RUNNING, # TODO what status is this? + cext.SSTOP: _common.STATUS_STOPPED, + cext.SZOMB: _common.STATUS_ZOMBIE +} + +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, +} + +# 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)) + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + +# --- functions + +disk_io_counters = cext.disk_io_counters +disk_usage = _psposix.disk_usage +net_if_addrs = cext_posix.net_if_addrs +net_io_counters = cext.net_io_counters + + +def virtual_memory(): + total, free, pinned, inuse = cext.virtual_mem() + total = total * PAGE_SIZE + avail = free * PAGE_SIZE + used = inuse * PAGE_SIZE + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free) + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir('/proc') if x.isdigit()] + + +def pid_exists(pid): + """Check for the existence of a unix pid.""" + return _psposix.pid_exists(pid) + + +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) + + +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) + + +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 = 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) + retlist.append(nt) + return retlist + + +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 + + +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.copy() + 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] + 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 + 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 + + +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"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + def oneshot_enter(self): + pass + + def oneshot_exit(self): + pass + + @wrap_exceptions + def name(self): + # note: this is limited to 15 characters + return cext.proc_name_and_args(self.pid)[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 cext.proc_name_and_args(self.pid)[1].split(' ') + + @wrap_exceptions + def create_time(self): + return cext.proc_basic_info(self.pid)[3] + + @wrap_exceptions + def num_threads(self): + return cext.proc_basic_info(self.pid)[5] + + @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: + os.stat('/proc/%s' % self.pid) # will raise NSP if process is gone + 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: + os.stat('/proc/%s' % self.pid) # will raise NSP if process is gone + 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): + if self.pid in (2, 3): + # Special case PIDs: internally setpriority(3) return ESRCH + # (no such process), no matter what. + # The process actually exists though, as it has a name, + # creation time, etc. + raise AccessDenied(self.pid, self._name) + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def ppid(self): + return cext.proc_basic_info(self.pid)[0] + + @wrap_exceptions + def uids(self): + real, effective, saved, _, _, _ = cext.proc_cred(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + _, _, _, real, effective, saved = cext.proc_cred(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + cpu_times = cext.proc_cpu_times(self.pid) + return _common.pcputimes(*cpu_times) + + @wrap_exceptions + def terminal(self): + psinfo = cext.proc_basic_info(self.pid) + ttydev = psinfo[-1] + # 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): + try: + return os.readlink("/proc/%s/cwd" % self.pid).rstrip('/') + except OSError as err: + if err.errno == errno.ENOENT: + os.stat("/proc/%s" % self.pid) + return None + raise + + @wrap_exceptions + def memory_info(self): + ret = cext.proc_basic_info(self.pid) + rss, vms = ret[1] * 1024, ret[2] * 1024 + return pmem(rss, vms) + + memory_full_info = memory_info + + @wrap_exceptions + def status(self): + code = cext.proc_basic_info(self.pid)[6] + # 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: + 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 == "Cannot be retrieved": + continue + retlist.append(_common.popenfile(path, int(fd))) + return retlist + + @wrap_exceptions + def num_fds(self): + return len(os.listdir("/proc/%s/fd" % self.pid)) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def wait(self, timeout=None): + 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): + rc, wc, rb, wb = cext.proc_io_counters(self.pid) + return _common.pio(rc, wc, rb, wb) diff --git a/psutil/_psutil_aix.c b/psutil/_psutil_aix.c new file mode 100644 index 0000000000..d81e1f0a56 --- /dev/null +++ b/psutil/_psutil_aix.c @@ -0,0 +1,777 @@ +// 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arch/aix/ifaddrs.h" +#include "arch/aix/net_connections.h" +#include "_psutil_aix.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; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/psinfo", pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + 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) info.pr_lwp.pr_state, // 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; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/psinfo", pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("s#s", info.pr_fname, PRFNSZ, info.pr_psargs); +} + + +/* + * 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_SetFromErrno(PyExc_OSError); + 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, + procinfo.outOps, + procinfo.inBytes, + 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; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/status", 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; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/cred", 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 *ret_list = PyList_New(0); + PyObject *tuple = NULL; + PyObject *user_proc = NULL; + + if (ret_list == NULL) + return NULL; + + setutxent(); + while (NULL != (ut = getutxent())) { + if (ut->ut_type == USER_PROCESS) + user_proc = Py_True; + else + user_proc = Py_False; + tuple = Py_BuildValue( + "(sssfO)", + ut->ut_user, // username + ut->ut_line, // tty + ut->ut_host, // hostname + (float)ut->ut_tv.tv_sec, // tstamp + user_proc); // (bool) user process + if (tuple == NULL) + goto error; + if (PyList_Append(ret_list, tuple)) + goto error; + Py_DECREF(tuple); + } + endutxent(); + + return ret_list; + +error: + Py_XDECREF(tuple); + Py_DECREF(ret_list); + if (ut != NULL) + endutent(); + 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_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + 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_tuple = Py_BuildValue( + "(ssss)", + mt->mnt_fsname, // device + mt->mnt_dir, // 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_tuple); + mt = getmntent(file); + } + endmntent(file); + return py_retlist; + +error: + 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_netadapter_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_netadapter_t structures are available */ + tot = perfstat_netadapter(NULL, NULL, sizeof(perfstat_netadapter_t), 0); + if (tot == 0) { + PyErr_SetString(PyExc_RuntimeError, "no net adapter found"); + goto error; + } + if (tot < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + statp = (perfstat_netadapter_t *) + malloc(tot * sizeof(perfstat_netadapter_t)); + if (statp == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + strcpy(first.name, FIRST_NETINTERFACE); + tot = perfstat_netadapter(&first, statp, + sizeof(perfstat_netadapter_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].tx_bytes, + statp[i].rx_bytes, + statp[i].tx_packets, + statp[i].rx_packets, + statp[i].tx_errors, + statp[i].rx_errors, + statp[i].tx_packets_dropped, + statp[i].rx_packets_dropped + ); + 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; +} + +// a signaler for connections without an actual status +static int PSUTIL_CONN_NONE = 128; + + +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) { + return Py_BuildValue("f", boot_time); + } + else { + PyErr_SetString(PyExc_RuntimeError, "can't determine boot time"); + return NULL; + } +} + + +/* + * 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; + 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); + return NULL; + } + + /* allocate enough memory to hold the ncpu structures */ + cpu = (perfstat_cpu_t *) malloc(ncpu * sizeof(perfstat_cpu_t)); + if (cpu == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + strcpy(id.name, ""); + rc = perfstat_cpu(&id, cpu, sizeof(perfstat_cpu_t), ncpu); + + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + 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); + 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_SetFromErrno(PyExc_OSError); + 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; + 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.real_total, + (unsigned long long) memory.real_free, + (unsigned long long) memory.real_pinned, + (unsigned long long) memory.real_inuse + ); +} + + + +/* + * 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, + (unsigned long long) memory.pgsp_free, + (unsigned long long) memory.pgins * pagesize, + (unsigned long long) memory.pgouts * pagesize + ); +} + + +/* + * 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)"}, +{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", TSIDL); + PyModule_AddIntConstant(module, "SRUN", TSRUN); + PyModule_AddIntConstant(module, "SSLEEP", TSSLEEP); + PyModule_AddIntConstant(module, "SSWAP", TSSWAP); + PyModule_AddIntConstant(module, "SSTOP", TSSTOP); + PyModule_AddIntConstant(module, "SZOMB", TSZOMB); + + 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_aix.h b/psutil/_psutil_aix.h new file mode 100644 index 0000000000..2226d29af0 --- /dev/null +++ b/psutil/_psutil_aix.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +// processes +static PyObject* psutil_proc_basic_info(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cred(PyObject* self, PyObject* args); +static PyObject* psutil_proc_name_and_args(PyObject* self, PyObject* args); +static PyObject* psutil_proc_io_counters(PyObject* self, PyObject* args); + +// system +static PyObject* psutil_boot_time(PyObject* self, PyObject* args); +static PyObject* psutil_users(PyObject* self, PyObject* args); +static PyObject* psutil_disk_partitions(PyObject* self, PyObject* args); +static PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_per_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_virtual_mem(PyObject* self, PyObject* args); +static PyObject* psutil_swap_mem(PyObject* self, PyObject* args); +static PyObject* psutil_net_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_net_if_stats(PyObject* self, PyObject* args); diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c index 80c1b8cba1..670724e2ab 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 _AIX + #include "arch/aix/ifaddrs.h" #else #include #endif @@ -35,6 +37,8 @@ #elif defined(PSUTIL_SUNOS) #include #include +#elif defined(_AIX) + #include #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(_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 0000000000..8d46035eb1 --- /dev/null +++ b/psutil/arch/aix/ifaddrs.c @@ -0,0 +1,118 @@ +/*! This file was copied from https://lists.samba.org/archive/samba-technical/2009-February/063079.html !*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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); + 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; + + sd = socket(AF_INET, SOCK_DGRAM, 0); + + *ifap = NULL; + + /* 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); + 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)); + cifa->ifa_next = NULL; + cifa->ifa_name = strdup(ifr->ifr_name); + cifa->ifa_flags = 0; + cifa->ifa_dstaddr = NULL; + + if (pifa == NULL) *ifap = cifa; /* first one */ + else pifa->ifa_next = cifa; + + cifa->ifa_addr = sa_dup(&ifr->ifr_addr); + + if (fam == AF_INET) { + if (ioctl(sd, SIOCGIFNETMASK, ifr, IFREQSZ) < 0) + goto error; + cifa->ifa_netmask = sa_dup(&ifr->ifr_addr); + } + + 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); + } + else + cifa->ifa_dstaddr = sa_dup(&ifr->ifr_addr); + } + 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); + 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 0000000000..db718418f2 --- /dev/null +++ b/psutil/arch/aix/ifaddrs.h @@ -0,0 +1,26 @@ +/*! This file was copied from https://lists.samba.org/archive/samba-technical/2009-February/063079.html !*/ + + +#ifndef GENERIC_AIX_IFADDRS_H +#define GENERIC_AIX_IFADDRS_H + +#include +#include + +#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 0000000000..4ed644c7a7 --- /dev/null +++ b/psutil/arch/aix/net_connections.c @@ -0,0 +1,330 @@ +/* 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 +#include +#define _KERNEL 1 +#include +#undef _KERNEL +#include +#include +#include +#include +#include +#include +#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) + return(1); + br = read(Kd, buf, len); + return((br == len) ? 0 : 1); +} + +static void +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; + } + + 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 (mb.m_hdr.mh_data + && !psutil_kread(Kd, (KA_T)mb.m_hdr.mh_data, (char *)&un, sizeof(un)) + ) { + 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); + } +} + +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))) { + PyErr_SetFromErrno(PyExc_OSError); + 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))) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if (!s.so_type) { + return NO_SOCKET; + } + + if (!s.so_proto || + psutil_kread(Kd, (KA_T)s.so_proto, (char *)&p, sizeof(p))) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if (!p.pr_domain + || psutil_kread(Kd, (KA_T)p.pr_domain, (char *)&d, sizeof(d))) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + fam = d.dom_family; + if (fam == AF_INET || fam == AF_INET6) { + /* Read protocol control block */ + if (!s.so_pcb + || psutil_kread(Kd, (KA_T) s.so_pcb, (char *) &inp, sizeof(inp))) { + PyErr_SetFromErrno(PyExc_OSError); + 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 this is an IPv6 (AF_INET6) socket and IPv4 compatibility + * mode is enabled, change the family indicator from AF_INET6 to + * AF_INET. + */ + if (fam == AF_INET6 && (inp.inp_flags & INP_COMPATV4)) { + fam = AF_INET; + } + + 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, d.dom_family, + s.so_type, laddr_str, lport, raddr_str, + rport, state, pid); + } + else { + return Py_BuildValue("(iii(si)()ii)", fd, d.dom_family, + 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))) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if ((KA_T) f.f_data != (KA_T) unp.unp_socket) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if (unp.unp_addr) { + read_unp_addr(Kd, unp.unp_addr, unix_laddr_str, + sizeof(unix_laddr_str)); + } + + if (unp.unp_conn) { + if (psutil_kread(Kd, (KA_T) unp.unp_conn, (char *)&unp, + sizeof(unp))) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + read_unp_addr(Kd, unp.unp_addr, unix_raddr_str, + sizeof(unix_raddr_str)); + } + + 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_SetFromErrno(PyExc_OSError); + goto error; + } + + /* Read the process table */ + msz = (size_t)(PROCSIZE * PROCINFO_INCR); + processes = (struct procentry64 *)malloc(msz); + if (!processes) { + PyErr_SetFromErrno(PyExc_OSError); + 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_SetFromErrno(PyExc_OSError); + 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_SetFromErrno(PyExc_OSError); + 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 0000000000..a371933ba4 --- /dev/null +++ b/psutil/arch/aix/net_connections.h @@ -0,0 +1,3 @@ +#include + +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 0000000000..354cbd2082 --- /dev/null +++ b/psutil/arch/aix/net_kernel_structs.h @@ -0,0 +1,103 @@ + +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#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/setup.py b/setup.py index 5f2683497b..60bd18a3af 100755 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ from _common import POSIX # NOQA from _common import SUNOS # NOQA from _common import WINDOWS # NOQA +from _common import AIX # NOQA macros = [] @@ -239,7 +240,15 @@ def get_ethtool_macro(): ], define_macros=macros, libraries=['kstat', 'nsl', 'socket']) - +# AIX +elif AIX: + ext = Extension( + 'psutil._psutil_aix', + sources=[ + 'psutil/_psutil_aix.c', + 'psutil/arch/aix/net_connections.c'], + libraries=['perfstat'], + define_macros=macros) else: sys.exit('platform %s is not supported' % sys.platform) @@ -254,6 +263,8 @@ def get_ethtool_macro(): if platform.release() == '5.10': posix_extension.sources.append('psutil/arch/solaris/v10/ifaddrs.c') posix_extension.define_macros.append(('PSUTIL_SUNOS10', 1)) + if AIX: + posix_extension.sources.append('psutil/arch/aix/ifaddrs.c') extensions = [ext, posix_extension] else: