From cad8406514db2b2241d0e522ede4d19267c8e441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Revol?= Date: Wed, 12 Feb 2020 04:16:40 +0100 Subject: [PATCH] WIP: preliminary Haiku port --- psutil/__init__.py | 5 + psutil/_common.py | 5 +- psutil/_pshaiku.py | 433 ++++++++++++++++++++ psutil/_psutil_haiku.cpp | 841 +++++++++++++++++++++++++++++++++++++++ psutil/_psutil_posix.c | 5 +- setup.py | 11 + 6 files changed, 1297 insertions(+), 3 deletions(-) create mode 100644 psutil/_pshaiku.py create mode 100644 psutil/_psutil_haiku.cpp diff --git a/psutil/__init__.py b/psutil/__init__.py index b267239..0ba8fc7 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -16,6 +16,7 @@ sensors) in Python. Supported platforms: - NetBSD - Sun Solaris - AIX + - Haiku Works with Python versions from 2.6 to 3.4+. """ @@ -79,6 +80,7 @@ from ._common import NIC_DUPLEX_UNKNOWN from ._common import AIX from ._common import BSD from ._common import FREEBSD # NOQA +from ._common import HAIKU from ._common import LINUX from ._common import MACOS from ._common import NETBSD # NOQA @@ -174,6 +176,9 @@ elif AIX: # via sys.modules. PROCFS_PATH = "/proc" +elif HAIKU: + from . import _pshaiku as _psplatform + else: # pragma: no cover raise NotImplementedError('platform %s is not supported' % sys.platform) diff --git a/psutil/_common.py b/psutil/_common.py index 126d9d6..88ac58f 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -42,8 +42,8 @@ PY3 = sys.version_info[0] == 3 __all__ = [ # constants - 'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX', - 'SUNOS', 'WINDOWS', + 'FREEBSD', 'BSD', 'HAIKU', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', + 'POSIX', 'SUNOS', 'WINDOWS', 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', # connection constants 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED', @@ -84,6 +84,7 @@ NETBSD = sys.platform.startswith("netbsd") BSD = FREEBSD or OPENBSD or NETBSD SUNOS = sys.platform.startswith(("sunos", "solaris")) AIX = sys.platform.startswith("aix") +HAIKU = sys.platform.startswith("haiku") # =================================================================== diff --git a/psutil/_pshaiku.py b/psutil/_pshaiku.py new file mode 100644 index 0000000..89115f7 --- /dev/null +++ b/psutil/_pshaiku.py @@ -0,0 +1,433 @@ +# Copyright (c) 2009, Giampaolo Rodola' +# Copyright (c) 2017, Arnon Yaari +# Copyright (c) 2020, François Revol +# All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Haiku platform implementation.""" + +import functools +# import glob +import os +# import re +# import subprocess +# import sys +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_haiku as cext +from . import _psutil_posix as cext_posix +from ._common import AccessDenied +# from ._common import conn_to_ntuple +from ._common import memoize_when_activated +from ._common import NoSuchProcess +from ._common import usage_percent +from ._common import ZombieProcess +from ._compat import FileNotFoundError +from ._compat import PermissionError +from ._compat import ProcessLookupError +# from ._compat import PY3 + + +__extra__all__ = [] + + +# ===================================================================== +# --- globals +# ===================================================================== + + +HAS_NET_IO_COUNTERS = hasattr(cext, "net_io_counters") +HAS_PROC_IO_COUNTERS = hasattr(cext, "proc_io_counters") + +PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') +AF_LINK = cext_posix.AF_LINK + +PROC_STATUSES = { + cext.B_THREAD_RUNNING: _common.STATUS_RUNNING, + cext.B_THREAD_READY: _common.STATUS_IDLE, + cext.B_THREAD_RECEIVING: _common.STATUS_WAITING, + cext.B_THREAD_ASLEEP: _common.STATUS_SLEEPING, + cext.B_THREAD_SUSPENDED: _common.STATUS_STOPPED, + cext.B_THREAD_WAITING: _common.STATUS_WAITING, +} + +team_info_map = dict( + thread_count=0, + image_count=1, + area_count=2, + uid=3, + gid=4, + name=5) + +team_usage_info_map = dict( + user_time=0, + kernel_time=1) + +thread_info_map = dict( + id=0, + user_time=1, + kernel_time=2, + state=3) + + +# ===================================================================== +# --- 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', 'used', 'percent', 'cached', 'buffers', + 'ignored', 'needed', 'available']) + + +# ===================================================================== +# --- memory +# ===================================================================== + + +def virtual_memory(): + total, inuse, cached, buffers, ignored, needed, avail = cext.virtual_mem() + percent = usage_percent((total - avail), total, round_=1) + return svmem(total, inuse, percent, cached, buffers, ignored, needed, + avail) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + total, free = cext.swap_mem() + sin = 0 + sout = 0 + 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(): + # TODO: + return 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) + + +def cpu_freq(): + """Return CPU frequency. + """ + curr, min_, max_ = cext.cpu_freq() + return [_common.scpufreq(curr, min_, max_)] + + +# ===================================================================== +# --- 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 + +if HAS_NET_IO_COUNTERS: + 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). + """ + # TODO + return None + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + # TODO + return None + + +# ===================================================================== +# --- 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(): + ls = cext.pids() + return ls + + +pid_exists = _psposix.pid_exists + + +def wrap_exceptions(fun): + """Call callable into a try/except clause and translate ENOENT, + EACCES and EPERM in NoSuchProcess or AccessDenied exceptions. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except (FileNotFoundError, ProcessLookupError): + # 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 not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + except PermissionError: + raise AccessDenied(self.pid, self._name) + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid", "_cache"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + def oneshot_enter(self): + self._proc_team_info.cache_activate(self) + self._proc_team_usage_info.cache_activate(self) + + def oneshot_exit(self): + self._proc_team_info.cache_deactivate(self) + self._proc_team_usage_info.cache_deactivate(self) + + @wrap_exceptions + @memoize_when_activated + def _proc_team_info(self): + ret = cext.proc_team_info_oneshot(self.pid) + print("%d %d\n" % (len(ret), len(team_info_map))) + assert len(ret) == len(team_info_map) + return ret + + @wrap_exceptions + @memoize_when_activated + def _proc_team_usage_info(self): + ret = cext.proc_team_usage_info_oneshot(self.pid) + print("%d %d\n" % (len(ret), len(team_usage_info_map))) + assert len(ret) == len(team_usage_info_map) + return ret + + @wrap_exceptions + def name(self): + return cext.proc_name(self.pid).rstrip("\x00") + + @wrap_exceptions + def exe(self): + return cext.proc_exe(self.pid) + + @wrap_exceptions + def cmdline(self): + return cext.proc_cmdline(self.pid) + + @wrap_exceptions + def environ(self): + return cext.proc_environ(self.pid) + + @wrap_exceptions + def create_time(self): + return None + + @wrap_exceptions + def num_threads(self): + return self._proc_team_info()[team_info_map['thread_count']] + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime, state in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + 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): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def ppid(self): + return None + + @wrap_exceptions + def uids(self): + uid = self._proc_team_info()[team_info_map['uid']] + return _common.puids(uid, uid, uid) + + @wrap_exceptions + def gids(self): + gid = self._proc_team_info()[team_info_map['gid']] + return _common.puids(gid, gid, gid) + + @wrap_exceptions + def cpu_times(self): + _user, _kern = self._proc_team_usage_info() + return _common.pcputimes(_user, _kern, _user, _kern) + + @wrap_exceptions + def terminal(self): + # TODO + return None + + @wrap_exceptions + def cwd(self): + return None + + @wrap_exceptions + def memory_info(self): + # TODO: + rss = 0 + vms = 0 + return pmem(rss, vms) + + memory_full_info = memory_info + + @wrap_exceptions + def status(self): + threads = cext.proc_threads(self.pid) + code = threads[0][thread_info_map['state']] + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + def open_files(self): + # TODO: + return [] + + @wrap_exceptions + def num_fds(self): + # TODO: + return None + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw( + *cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def wait(self, timeout=None): + return _psposix.wait_pid(self.pid, timeout, self._name) + + @wrap_exceptions + def io_counters(self): + return _common.pio( + 0, + 0, + -1, + -1) diff --git a/psutil/_psutil_haiku.cpp b/psutil/_psutil_haiku.cpp new file mode 100644 index 0000000..f9b3a44 --- /dev/null +++ b/psutil/_psutil_haiku.cpp @@ -0,0 +1,841 @@ +/* + * 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. + * + * macOS platform-specific module methods. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +extern "C" { + +#include "_psutil_common.h" +#include "_psutil_posix.h" +//#include "arch/haiku/process_info.h" + +} + +static PyObject *ZombieProcessError; + + +/* + * Return a Python list of all the PIDs running on the system. + */ +static PyObject * +psutil_pids(PyObject *self, PyObject *args) { + int32 cookie = 0; + team_info info; + PyObject *py_pid = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + while (get_next_team_info(&cookie, &info) == B_OK) { + /* quite wasteful: we have all team infos already */ + py_pid = Py_BuildValue("i", info.team); + if (! py_pid) + goto error; + if (PyList_Append(py_retlist, py_pid)) + goto error; + Py_CLEAR(py_pid); + } + + return py_retlist; + +error: + Py_XDECREF(py_pid); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * Return multiple process info as a Python tuple in one shot by + * using get_team_info() and filling up a team_info struct. + */ +static PyObject * +psutil_proc_team_info_oneshot(PyObject *self, PyObject *args) { + pid_t pid; + team_info info; + PyObject *py_name; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (get_team_info(pid, &info) != B_OK) + return NULL; + + py_name = PyUnicode_DecodeFSDefault(info.args); + if (! py_name) { + // Likely a decoding error. We don't want to fail the whole + // operation. The python module may retry with proc_name(). + PyErr_Clear(); + py_name = Py_None; + } + + py_retlist = Py_BuildValue( + "lllllO", + (long)info.thread_count, // (long) thread_count + (long)info.image_count, // (long) image_count + (long)info.area_count, // (long) area_count + (long)info.uid, // (long) uid + (long)info.gid, // (long) gid + py_name // (pystr) name + ); + + if (py_retlist != NULL) { + // XXX shall we decref() also in case of Py_BuildValue() error? + Py_DECREF(py_name); + } + return py_retlist; +} + + +/* + * Return multiple process info as a Python tuple in one shot by + * using get_team_usage_info() and filling up a team_usage_info struct. + */ +static PyObject * +psutil_proc_team_usage_info_oneshot(PyObject *self, PyObject *args) { + pid_t pid; + team_usage_info info; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (get_team_usage_info(pid, B_TEAM_USAGE_SELF, &info) != B_OK) + return NULL; + + py_retlist = Py_BuildValue( + "KK", + (unsigned long long)info.user_time, + (unsigned long long)info.kernel_time + ); + + return py_retlist; +} + + +/* + * Return process name from kinfo_proc as a Python string. + */ +static PyObject * +psutil_proc_name(PyObject *self, PyObject *args) { + pid_t pid; + team_info info; + PyObject *py_name; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (get_team_info(pid, &info) != B_OK) + return NULL; + + return PyUnicode_DecodeFSDefault(info.args); +} + + +/* + * Return process current working directory. + * Raises NSP in case of zombie process. + */ +static PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * Return path of the process executable. + */ +static PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) { + pid_t pid; + image_info info; + int32 cookie = 0; + PyObject *py_name; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + while (get_next_image_info(pid, &cookie, &info) == B_OK) { + if (info.type != B_APP_IMAGE) + continue; + return PyUnicode_DecodeFSDefault(info.name); + } + return NULL; +} + + +/* + * Return process cmdline as a Python list of cmdline arguments. + */ +static PyObject * +psutil_proc_cmdline(PyObject *self, PyObject *args) { + return Py_BuildValue("[]"); + /* TODO! */ + pid_t pid; + team_info info; + PyObject *py_arg; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + if (get_team_info(pid, &info) != B_OK) + return NULL; + + py_retlist = PyList_New(0); + if (py_retlist == NULL) + return NULL; + + /* TODO: we can't really differentiate args as we have a single string */ + /* TODO: try to split? */ + py_arg = PyUnicode_DecodeFSDefault(info.args); + if (!py_arg) + goto error; + if (PyList_Append(py_retlist, py_arg)) + goto error; + + return Py_BuildValue("N", py_retlist); + +error: + Py_XDECREF(py_arg); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * Return process environment as a Python string. + */ +static PyObject * +psutil_proc_environ(PyObject *self, PyObject *args) { + /* TODO: likely impossible */ + return NULL; +} + + +/* + * Return the number of logical CPUs in the system. + */ +static PyObject * +psutil_cpu_count_logical(PyObject *self, PyObject *args) { + /* TODO:get_cpu_topology_info */ + return NULL; +} + + +/* + * Return the number of physical CPUs in the system. + */ +static PyObject * +psutil_cpu_count_phys(PyObject *self, PyObject *args) { + /* TODO:get_cpu_topology_info */ + return NULL; +} + + +/* + * Returns the USS (unique set size) of the process. Reference: + * https://dxr.mozilla.org/mozilla-central/source/xpcom/base/ + * nsMemoryReporterManager.cpp + */ +static PyObject * +psutil_proc_memory_uss(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * Return system virtual memory stats. + * See: + * https://opensource.apple.com/source/system_cmds/system_cmds-790/ + * vm_stat.tproj/vm_stat.c.auto.html + */ +static PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + system_info info; + status_t ret; + int pagesize = getpagesize(); + + + ret = get_system_info(&info); + if (ret != B_OK) { + PyErr_Format( + PyExc_RuntimeError, "get_system_info() syscall failed: %s", + strerror(ret)); + return NULL; + } + + return Py_BuildValue( + "KKKKKKK", + (unsigned long long) info.max_pages * pagesize, // total + (unsigned long long) info.used_pages * pagesize, // used + (unsigned long long) info.cached_pages * pagesize, // cached + (unsigned long long) info.block_cache_pages * pagesize, // buffers + (unsigned long long) info.ignored_pages * pagesize, // ignored + (unsigned long long) info.needed_memory, // needed + (unsigned long long) info.free_memory // available + ); +} + + +/* + * Return stats about swap memory. + */ +static PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + system_info info; + status_t ret; + int pagesize = getpagesize(); + + + ret = get_system_info(&info); + if (ret != B_OK) { + PyErr_Format( + PyExc_RuntimeError, "get_system_info() syscall failed: %s", + strerror(ret)); + return NULL; + } + + return Py_BuildValue( + "KK", + (unsigned long long) info.max_swap_pages * pagesize, + (unsigned long long) info.free_swap_pages * pagesize); + /* XXX: .page_faults? */ +} + + +/* + * Return a Python tuple representing user, kernel and idle CPU times + */ +static PyObject * +psutil_cpu_times(PyObject *self, PyObject *args) { + system_info info; + status_t ret; + int pagesize = getpagesize(); + + ret = get_system_info(&info); + if (ret != B_OK) { + PyErr_Format( + PyExc_RuntimeError, "get_system_info() syscall failed: %s", + strerror(ret)); + return NULL; + } + + cpu_info cpus[info.cpu_count]; + + ret = get_cpu_info(0, info.cpu_count, cpus); + if (ret != B_OK) { + PyErr_Format( + PyExc_RuntimeError, "get_cpu_info() syscall failed: %s", + strerror(ret)); + return NULL; + } + + /* TODO */ + return Py_BuildValue( + "(dddd)", + (double)0.0, + (double)0.0, + (double)0.0, + (double)0.0 + ); +} + + +/* + * Return a Python list of tuple representing per-cpu times + */ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + system_info info; + status_t ret; + uint32 i, pagesize = getpagesize(); + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + ret = get_system_info(&info); + if (ret != B_OK) { + PyErr_Format( + PyExc_RuntimeError, "get_system_info() syscall failed: %s", + strerror(ret)); + return NULL; + } + + cpu_info cpus[info.cpu_count]; + + ret = get_cpu_info(0, info.cpu_count, cpus); + if (ret != B_OK) { + PyErr_Format( + PyExc_RuntimeError, "get_cpu_info() syscall failed: %s", + strerror(ret)); + return NULL; + } + + /* TODO: check idle thread times? */ + + for (i = 0; i < info.cpu_count; i++) { + py_cputime = Py_BuildValue( + "(dddd)", + (double)0.0, + (double)0.0, + (double)0.0, + (double)0.0 + ); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_CLEAR(py_cputime); + } + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * Retrieve CPU frequency. + */ +static PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + status_t ret; + uint32 i, topologyNodeCount = 0; + ret = get_cpu_topology_info(NULL, &topologyNodeCount); + if (ret != B_OK || topologyNodeCount == 0) + return NULL; + cpu_topology_node_info topology[topologyNodeCount]; + ret = get_cpu_topology_info(topology, &topologyNodeCount); + + for (i = 0; i < topologyNodeCount; i++) { + if (topology[i].type == B_TOPOLOGY_CORE) + /* TODO: find min / max? */ + return Py_BuildValue( + "KKK", + topology[i].data.core.default_frequency, + topology[i].data.core.default_frequency, + topology[i].data.core.default_frequency); + } + + return NULL; +} + + +/* + * Return a Python float indicating the system boot time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + system_info info; + status_t ret; + int pagesize = getpagesize(); + + ret = get_system_info(&info); + if (ret != B_OK) { + PyErr_Format( + PyExc_RuntimeError, "get_system_info() syscall failed: %s", + strerror(ret)); + return NULL; + } + + return Py_BuildValue("f", (float)info.boot_time / 1000000.0); +} + + +/* + * Return a list of tuples including device, mount point and fs type + * for all partitions mounted on the system. + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + int32 cookie = 0; + dev_t dev; + fs_info info; + uint32 flags; + BString opts; + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + while ((dev = next_dev(&cookie)) >= 0) { + opts = ""; + if (fs_stat_dev(dev, &info) != B_OK) + continue; + flags = info.flags; + + // see fs_info.h + if (flags & B_FS_IS_READONLY) + opts << "ro"; + else + opts << "rw"; + // TODO + if (flags & B_FS_IS_REMOVABLE) + opts << ",removable"; + if (flags & B_FS_IS_PERSISTENT) + opts << ",persistent"; + if (flags & B_FS_IS_SHARED) + opts << ",shared"; + if (flags & B_FS_HAS_MIME) + opts << ",has_mime"; + if (flags & B_FS_HAS_ATTR) + opts << ",has_attr"; + if (flags & B_FS_HAS_QUERY) + opts << ",has_query"; + if (flags & B_FS_HAS_SELF_HEALING_LINKS) + opts << ",has_self_healing_links"; + if (flags & B_FS_HAS_ALIASES) + opts << ",has_aliases"; + if (flags & B_FS_SUPPORTS_NODE_MONITORING) + opts << ",has_node_monitoring"; + if (flags & B_FS_SUPPORTS_MONITOR_CHILDREN) + opts << ",cmdflags"; + + py_dev = PyUnicode_DecodeFSDefault(info.device_name); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(info.volume_name); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue( + "(OOss)", + py_dev, // device + py_mountp, // mount point + info.fsh_name, // fs type + opts.String()); // options + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_dev); + Py_CLEAR(py_mountp); + Py_CLEAR(py_tuple); + } + + return py_retlist; + +error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * Return process threads + */ +static PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + pid_t pid; + int32 cookie = 0; + thread_info info; + int err, ret; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + while (get_next_thread_info(pid, &cookie, &info) == B_OK) { + py_tuple = Py_BuildValue( + "Iffl", + info.thread, + (float)info.user_time / 1000000.0, + (float)info.kernel_time / 1000000.0, + (long)info.state + //XXX: priority, ? + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * Return process open files as a Python tuple. + * References: + * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/m78fd + * - /usr/include/sys/proc_info.h + */ +static PyObject * +psutil_proc_open_files(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * Return process TCP and UDP connections as a list of tuples. + * Raises NSP in case of zombie process. + * References: + * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/wNrC0 + * - /usr/include/sys/proc_info.h + */ +static PyObject * +psutil_proc_connections(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * Return number of file descriptors opened by process. + * Raises NSP in case of zombie process. + */ +static PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * Return a Python list of named tuples with overall network I/O information + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * Return a Python dict of tuples for disk I/O information + */ +static PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * Return currently connected users as a list of tuples. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * Return CPU statistics. + */ +static PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * Return battery information. + */ +static PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + /* TODO */ + return NULL; +} + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef mod_methods[] = { + // --- per-process functions + + {"proc_team_info_oneshot", psutil_proc_team_info_oneshot, METH_VARARGS, + "Return multiple process info."}, + {"proc_team_usage_info_oneshot", psutil_proc_team_usage_info_oneshot, METH_VARARGS, + "Return multiple process info."}, + {"proc_name", psutil_proc_name, METH_VARARGS, + "Return process name"}, + {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS, + "Return process cmdline as a list of cmdline arguments"}, + {"proc_environ", psutil_proc_environ, METH_VARARGS, + "Return process environment data"}, + {"proc_exe", psutil_proc_exe, METH_VARARGS, + "Return path of the process executable"}, + {"proc_cwd", psutil_proc_cwd, METH_VARARGS, + "Return process current working directory."}, + {"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS, + "Return process USS memory"}, + {"proc_threads", psutil_proc_threads, METH_VARARGS, + "Return process threads as a list of tuples"}, + {"proc_open_files", psutil_proc_open_files, METH_VARARGS, + "Return files opened by process as a list of tuples"}, + {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS, + "Return the number of fds opened by process."}, + {"proc_connections", psutil_proc_connections, METH_VARARGS, + "Get process TCP and UDP connections as a list of tuples"}, + + // --- system-related functions + + {"pids", psutil_pids, METH_VARARGS, + "Returns a list of PIDs currently running on the system"}, + {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS, + "Return number of logical CPUs on the system"}, + {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, + "Return number of physical CPUs on the system"}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS, + "Return system virtual memory stats"}, + {"swap_mem", psutil_swap_mem, METH_VARARGS, + "Return stats about swap memory, in bytes"}, + {"cpu_times", psutil_cpu_times, METH_VARARGS, + "Return system cpu times as a tuple (user, system, nice, idle, irc)"}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-cpu times as a list of tuples"}, + {"cpu_freq", psutil_cpu_freq, METH_VARARGS, + "Return cpu current frequency"}, + {"boot_time", psutil_boot_time, METH_VARARGS, + "Return the system boot time expressed in seconds since the epoch."}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return a list of tuples including device, mount point and " + "fs type for all partitions mounted on the system."}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS, + "Return dict of tuples of networks I/O information."}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, + "Return dict of tuples of disks I/O information."}, + {"users", psutil_users, METH_VARARGS, + "Return currently connected users as a list of tuples"}, + {"cpu_stats", psutil_cpu_stats, METH_VARARGS, + "Return CPU statistics"}, + {"sensors_battery", psutil_sensors_battery, METH_VARARGS, + "Return battery information."}, + + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + + {NULL, NULL, 0, NULL} +}; + + +#if PY_MAJOR_VERSION >= 3 + #define INITERR return NULL + + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_psutil_haiku", + NULL, + -1, + mod_methods, + NULL, + NULL, + NULL, + NULL + }; + +extern "C" PyObject *PyInit__psutil_haiku(void); + + PyObject *PyInit__psutil_haiku(void) +#else /* PY_MAJOR_VERSION */ + #define INITERR return + +extern "C" void init_psutil_haiku(void); + + void init_psutil_haiku(void) +#endif /* PY_MAJOR_VERSION */ +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *mod = PyModule_Create(&moduledef); +#else + PyObject *mod = Py_InitModule("_psutil_haiku", mod_methods); +#endif + if (mod == NULL) + INITERR; + + if (psutil_setup() != 0) + INITERR; + + if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION)) + INITERR; + // process status constants, defined in: + // headers/os/kernel/OS.h + if (PyModule_AddIntConstant(mod, "B_THREAD_RUNNING", B_THREAD_RUNNING)) + INITERR; + if (PyModule_AddIntConstant(mod, "B_THREAD_READY", B_THREAD_READY)) + INITERR; + if (PyModule_AddIntConstant(mod, "B_THREAD_RECEIVING", B_THREAD_RECEIVING)) + INITERR; + if (PyModule_AddIntConstant(mod, "B_THREAD_ASLEEP", B_THREAD_ASLEEP)) + INITERR; + if (PyModule_AddIntConstant(mod, "B_THREAD_SUSPENDED", B_THREAD_SUSPENDED)) + INITERR; + if (PyModule_AddIntConstant(mod, "B_THREAD_WAITING", B_THREAD_WAITING)) + INITERR; + // connection status constants +/*XXX: + if (PyModule_AddIntConstant(mod, "TCPS_CLOSED", TCPS_CLOSED)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_CLOSING", TCPS_CLOSING)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_LISTEN", TCPS_LISTEN)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_ESTABLISHED", TCPS_ESTABLISHED)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_SYN_SENT", TCPS_SYN_SENT)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_SYN_RECEIVED", TCPS_SYN_RECEIVED)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_LAST_ACK", TCPS_LAST_ACK)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_TIME_WAIT", TCPS_TIME_WAIT)) + INITERR; +*/ + if (PyModule_AddIntConstant(mod, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE)) + INITERR; + + if (mod == NULL) + INITERR; +#if PY_MAJOR_VERSION >= 3 + return mod; +#endif +} diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c index aa60084..8165475 100644 --- a/psutil/_psutil_posix.c +++ b/psutil/_psutil_posix.c @@ -40,6 +40,9 @@ #include #elif defined(PSUTIL_AIX) #include +#elif defined(PSUTIL_HAIKU) + #include + #include #endif #include "_psutil_common.h" @@ -675,7 +678,7 @@ static PyMethodDef mod_methods[] = { if (mod == NULL) INITERR; -#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) || defined(PSUTIL_SUNOS) || defined(PSUTIL_AIX) +#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) || defined(PSUTIL_SUNOS) || defined(PSUTIL_AIX) || defined(PSUTIL_HAIKU) if (PyModule_AddIntConstant(mod, "AF_LINK", AF_LINK)) INITERR; #endif diff --git a/setup.py b/setup.py index 99818ad..8b81d25 100755 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ sys.path.insert(0, os.path.join(HERE, "psutil")) from _common import AIX # NOQA from _common import BSD # NOQA from _common import FREEBSD # NOQA +from _common import HAIKU # NOQA from _common import LINUX # NOQA from _common import MACOS # NOQA from _common import NETBSD # NOQA @@ -256,6 +257,16 @@ elif AIX: libraries=['perfstat'], define_macros=macros) +elif HAIKU: + macros.append(("PSUTIL_HAIKU", 1)) + macros.append(("_DEFAULT_SOURCE", 1)) + ext = Extension( + 'psutil._psutil_haiku', + sources=sources + [ + 'psutil/_psutil_haiku.cpp'], + libraries=['be', 'network'], + define_macros=macros) + else: sys.exit('platform %s is not supported' % sys.platform) -- 2.24.1