From a7205fc9d77b13a161da1502bd0988db8b9b5971 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 20 Oct 2023 18:05:16 +0200 Subject: [PATCH] Linux C files refactoring (#2320) --- MANIFEST.in | 10 + docs/index.rst | 31 +-- psutil/_psutil_linux.c | 496 +------------------------------------- psutil/arch/linux/disk.c | 75 ++++++ psutil/arch/linux/disk.h | 9 + psutil/arch/linux/mem.c | 30 +++ psutil/arch/linux/mem.h | 9 + psutil/arch/linux/net.c | 119 +++++++++ psutil/arch/linux/net.h | 9 + psutil/arch/linux/proc.c | 200 +++++++++++++++ psutil/arch/linux/proc.h | 25 ++ psutil/arch/linux/users.c | 72 ++++++ psutil/arch/linux/users.h | 9 + setup.py | 6 +- 14 files changed, 595 insertions(+), 505 deletions(-) create mode 100644 psutil/arch/linux/disk.c create mode 100644 psutil/arch/linux/disk.h create mode 100644 psutil/arch/linux/mem.c create mode 100644 psutil/arch/linux/mem.h create mode 100644 psutil/arch/linux/net.c create mode 100644 psutil/arch/linux/net.h create mode 100644 psutil/arch/linux/proc.c create mode 100644 psutil/arch/linux/proc.h create mode 100644 psutil/arch/linux/users.c create mode 100644 psutil/arch/linux/users.h diff --git a/MANIFEST.in b/MANIFEST.in index fb25643af..ae8056349 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -72,6 +72,16 @@ include psutil/arch/freebsd/sensors.c include psutil/arch/freebsd/sensors.h include psutil/arch/freebsd/sys_socks.c include psutil/arch/freebsd/sys_socks.h +include psutil/arch/linux/disk.c +include psutil/arch/linux/disk.h +include psutil/arch/linux/mem.c +include psutil/arch/linux/mem.h +include psutil/arch/linux/net.c +include psutil/arch/linux/net.h +include psutil/arch/linux/proc.c +include psutil/arch/linux/proc.h +include psutil/arch/linux/users.c +include psutil/arch/linux/users.h include psutil/arch/netbsd/cpu.c include psutil/arch/netbsd/cpu.h include psutil/arch/netbsd/disk.c diff --git a/docs/index.rst b/docs/index.rst index 0e782ebc7..00a6f6006 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2630,21 +2630,22 @@ If you want to develop psutil take a look at the `DEVGUIDE.rst`_. Platforms support history ========================= -* psutil 5.9.6 (XXXX-XX): drop Python 3.4 and 3.5 support -* psutil 5.9.1 (2022-05): drop Python 2.6 support -* psutil 5.9.0 (2021-12): **MidnightBSD** -* psutil 5.8.0 (2020-12): **PyPy 2** on Windows -* psutil 5.7.1 (2020-07): **Windows Nano** -* psutil 5.7.0 (2020-02): drop Windows XP & Server 2003 support -* psutil 5.7.0 (2020-02): **PyPy 3** on Windows -* psutil 5.4.0 (2017-11): **AIX** -* psutil 3.4.1 (2016-01): **NetBSD** -* psutil 3.3.0 (2015-11): **OpenBSD** -* psutil 1.0.0 (2013-07): **Solaris** -* psutil 0.1.1 (2009-03): **FreeBSD** -* psutil 0.1.0 (2009-01): **Linux, Windows, macOS** - -Supported Python versions are 2.7, 3.6+ and PyPy3. +* psutil 5.9.6 (2023-10): drop Python 3.4 and 3.5 +* psutil 5.9.1 (2022-05): drop Python 2.6 +* psutil 5.9.0 (2021-12): add **MidnightBSD** +* psutil 5.8.0 (2020-12): add **PyPy 2** on Windows +* psutil 5.7.1 (2020-07): add **Windows Nano** +* psutil 5.7.0 (2020-02): drop Windows XP & Windows Server 2003 +* psutil 5.7.0 (2020-02): add **PyPy 3** on Windows +* psutil 5.4.0 (2017-11): add **AIX** +* psutil 3.4.1 (2016-01): add **NetBSD** +* psutil 3.3.0 (2015-11): add **OpenBSD** +* psutil 1.0.0 (2013-07): add **Solaris** +* psutil 0.1.1 (2009-03): add **FreeBSD** +* psutil 0.1.0 (2009-01): add **Linux, Windows, macOS** + +Supported Python versions at the time of writing are cPython 2.7, 3.6+ and +PyPy3. Timeline ======== diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c index 3e6b3b900..38fa2cbaf 100644 --- a/psutil/_psutil_linux.c +++ b/psutil/_psutil_linux.c @@ -10,499 +10,19 @@ #define _GNU_SOURCE 1 #endif #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// see: https://github.com/giampaolo/psutil/issues/659 -#ifdef PSUTIL_ETHTOOL_MISSING_TYPES - #include - typedef __u64 u64; - typedef __u32 u32; - typedef __u16 u16; - typedef __u8 u8; -#endif -/* Avoid redefinition of struct sysinfo with musl libc */ -#define _LINUX_SYSINFO_H -#include - -/* The minimum number of CPUs allocated in a cpu_set_t */ -static const int NCPUS_START = sizeof(unsigned long) * CHAR_BIT; - -// Linux >= 2.6.13 -#define PSUTIL_HAVE_IOPRIO defined(__NR_ioprio_get) && defined(__NR_ioprio_set) - -// Should exist starting from CentOS 6 (year 2011). -#ifdef CPU_ALLOC - #define PSUTIL_HAVE_CPU_AFFINITY -#endif +#include // DUPLEX_* #include "_psutil_common.h" -#include "_psutil_posix.h" - -// May happen on old RedHat versions, see: -// https://github.com/giampaolo/psutil/issues/607 -#ifndef DUPLEX_UNKNOWN - #define DUPLEX_UNKNOWN 0xff -#endif - - -#ifndef SPEED_UNKNOWN - #define SPEED_UNKNOWN -1 -#endif - - -#if PSUTIL_HAVE_IOPRIO -enum { - IOPRIO_WHO_PROCESS = 1, -}; - -static inline int -ioprio_get(int which, int who) { - return syscall(__NR_ioprio_get, which, who); -} - -static inline int -ioprio_set(int which, int who, int ioprio) { - return syscall(__NR_ioprio_set, which, who, ioprio); -} - -// * defined in linux/ethtool.h but not always available (e.g. Android) -// * #ifdef check needed for old kernels, see: -// https://github.com/giampaolo/psutil/issues/2164 -static inline uint32_t -psutil_ethtool_cmd_speed(const struct ethtool_cmd *ecmd) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) - return ecmd->speed; -#else - return (ecmd->speed_hi << 16) | ecmd->speed; -#endif -} - -#define IOPRIO_CLASS_SHIFT 13 -#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) - -#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) -#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) -#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) - - -/* - * Return a (ioclass, iodata) Python tuple representing process I/O priority. - */ -static PyObject * -psutil_proc_ioprio_get(PyObject *self, PyObject *args) { - pid_t pid; - int ioprio, ioclass, iodata; - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); - if (ioprio == -1) - return PyErr_SetFromErrno(PyExc_OSError); - ioclass = IOPRIO_PRIO_CLASS(ioprio); - iodata = IOPRIO_PRIO_DATA(ioprio); - return Py_BuildValue("ii", ioclass, iodata); -} - - -/* - * A wrapper around ioprio_set(); sets process I/O priority. - * ioclass can be either IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE - * or 0. iodata goes from 0 to 7 depending on ioclass specified. - */ -static PyObject * -psutil_proc_ioprio_set(PyObject *self, PyObject *args) { - pid_t pid; - int ioprio, ioclass, iodata; - int retval; - - if (! PyArg_ParseTuple( - args, _Py_PARSE_PID "ii", &pid, &ioclass, &iodata)) { - return NULL; - } - ioprio = IOPRIO_PRIO_VALUE(ioclass, iodata); - retval = ioprio_set(IOPRIO_WHO_PROCESS, pid, ioprio); - if (retval == -1) - return PyErr_SetFromErrno(PyExc_OSError); - Py_RETURN_NONE; -} -#endif - - -/* - * 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 *entry; - char *mtab_path; - PyObject *py_dev = NULL; - PyObject *py_mountp = NULL; - PyObject *py_tuple = NULL; - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; +#include "arch/linux/disk.h" +#include "arch/linux/mem.h" +#include "arch/linux/net.h" +#include "arch/linux/proc.h" +#include "arch/linux/users.h" - if (!PyArg_ParseTuple(args, "s", &mtab_path)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - file = setmntent(mtab_path, "r"); - Py_END_ALLOW_THREADS - if ((file == 0) || (file == NULL)) { - psutil_debug("setmntent() failed"); - PyErr_SetFromErrnoWithFilename(PyExc_OSError, mtab_path); - goto error; - } - - while ((entry = getmntent(file))) { - if (entry == NULL) { - PyErr_Format(PyExc_RuntimeError, "getmntent() syscall failed"); - goto error; - } - py_dev = PyUnicode_DecodeFSDefault(entry->mnt_fsname); - if (! py_dev) - goto error; - py_mountp = PyUnicode_DecodeFSDefault(entry->mnt_dir); - if (! py_mountp) - goto error; - py_tuple = Py_BuildValue("(OOss)", - py_dev, // device - py_mountp, // mount point - entry->mnt_type, // fs type - entry->mnt_opts); // 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); - } - endmntent(file); - return py_retlist; - -error: - if (file != NULL) - endmntent(file); - Py_XDECREF(py_dev); - Py_XDECREF(py_mountp); - Py_XDECREF(py_tuple); - Py_DECREF(py_retlist); - return NULL; -} - - -/* - * A wrapper around sysinfo(), return system memory usage statistics. - */ -static PyObject * -psutil_linux_sysinfo(PyObject *self, PyObject *args) { - struct sysinfo info; - - if (sysinfo(&info) != 0) - return PyErr_SetFromErrno(PyExc_OSError); - // note: boot time might also be determined from here - return Py_BuildValue( - "(kkkkkkI)", - info.totalram, // total - info.freeram, // free - info.bufferram, // buffer - info.sharedram, // shared - info.totalswap, // swap tot - info.freeswap, // swap free - info.mem_unit // multiplier - ); -} - - -/* - * Return process CPU affinity as a Python list - */ -#ifdef PSUTIL_HAVE_CPU_AFFINITY - -static PyObject * -psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) { - int cpu, ncpus, count, cpucount_s; - pid_t pid; - size_t setsize; - cpu_set_t *mask = NULL; - PyObject *py_list = NULL; - - if (!PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - ncpus = NCPUS_START; - while (1) { - setsize = CPU_ALLOC_SIZE(ncpus); - mask = CPU_ALLOC(ncpus); - if (mask == NULL) { - psutil_debug("CPU_ALLOC() failed"); - return PyErr_NoMemory(); - } - if (sched_getaffinity(pid, setsize, mask) == 0) - break; - CPU_FREE(mask); - if (errno != EINVAL) - return PyErr_SetFromErrno(PyExc_OSError); - if (ncpus > INT_MAX / 2) { - PyErr_SetString(PyExc_OverflowError, "could not allocate " - "a large enough CPU set"); - return NULL; - } - ncpus = ncpus * 2; - } - - py_list = PyList_New(0); - if (py_list == NULL) - goto error; - - cpucount_s = CPU_COUNT_S(setsize, mask); - for (cpu = 0, count = cpucount_s; count; cpu++) { - if (CPU_ISSET_S(cpu, setsize, mask)) { -#if PY_MAJOR_VERSION >= 3 - PyObject *cpu_num = PyLong_FromLong(cpu); -#else - PyObject *cpu_num = PyInt_FromLong(cpu); -#endif - if (cpu_num == NULL) - goto error; - if (PyList_Append(py_list, cpu_num)) { - Py_DECREF(cpu_num); - goto error; - } - Py_DECREF(cpu_num); - --count; - } - } - CPU_FREE(mask); - return py_list; - -error: - if (mask) - CPU_FREE(mask); - Py_XDECREF(py_list); - return NULL; -} - - -/* - * Set process CPU affinity; expects a bitmask - */ -static PyObject * -psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { - cpu_set_t cpu_set; - size_t len; - pid_t pid; - Py_ssize_t i, seq_len; - PyObject *py_cpu_set; - - if (!PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &py_cpu_set)) - return NULL; - - if (!PySequence_Check(py_cpu_set)) { - return PyErr_Format( - PyExc_TypeError, -#if PY_MAJOR_VERSION >= 3 - "sequence argument expected, got %R", Py_TYPE(py_cpu_set) -#else - "sequence argument expected, got %s", Py_TYPE(py_cpu_set)->tp_name -#endif - ); - } - - seq_len = PySequence_Size(py_cpu_set); - if (seq_len < 0) { - return NULL; - } - CPU_ZERO(&cpu_set); - for (i = 0; i < seq_len; i++) { - PyObject *item = PySequence_GetItem(py_cpu_set, i); - if (!item) { - return NULL; - } -#if PY_MAJOR_VERSION >= 3 - long value = PyLong_AsLong(item); -#else - long value = PyInt_AsLong(item); -#endif - Py_XDECREF(item); - if ((value == -1) || PyErr_Occurred()) { - if (!PyErr_Occurred()) - PyErr_SetString(PyExc_ValueError, "invalid CPU value"); - return NULL; - } - CPU_SET(value, &cpu_set); - } - - len = sizeof(cpu_set); - if (sched_setaffinity(pid, len, &cpu_set)) { - return PyErr_SetFromErrno(PyExc_OSError); - } - - Py_RETURN_NONE; -} -#endif /* PSUTIL_HAVE_CPU_AFFINITY */ - - -/* - * Return currently connected users as a list of tuples. - */ -static PyObject * -psutil_users(PyObject *self, PyObject *args) { - struct utmp *ut; - PyObject *py_retlist = PyList_New(0); - PyObject *py_tuple = NULL; - PyObject *py_username = NULL; - PyObject *py_tty = NULL; - PyObject *py_hostname = NULL; - PyObject *py_user_proc = NULL; - - if (py_retlist == NULL) - return NULL; - setutent(); - while (NULL != (ut = getutent())) { - py_tuple = NULL; - py_user_proc = NULL; - if (ut->ut_type == USER_PROCESS) - py_user_proc = Py_True; - else - py_user_proc = Py_False; - py_username = PyUnicode_DecodeFSDefault(ut->ut_user); - if (! py_username) - goto error; - py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); - if (! py_tty) - goto error; - py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); - if (! py_hostname) - goto error; - - py_tuple = Py_BuildValue( - "OOOdO" _Py_PARSE_PID, - py_username, // username - py_tty, // tty - py_hostname, // hostname - (double)ut->ut_tv.tv_sec, // tstamp - py_user_proc, // (bool) user process - ut->ut_pid // process id - ); - if (! py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_username); - Py_CLEAR(py_tty); - Py_CLEAR(py_hostname); - Py_CLEAR(py_tuple); - } - endutent(); - return py_retlist; - -error: - Py_XDECREF(py_username); - Py_XDECREF(py_tty); - Py_XDECREF(py_hostname); - Py_XDECREF(py_tuple); - Py_DECREF(py_retlist); - endutent(); - return NULL; -} - - -/* - * Return stats about a particular network - * interface. References: - * https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py - * http://www.i-scream.org/libstatgrab/ - */ -static PyObject* -psutil_net_if_duplex_speed(PyObject* self, PyObject* args) { - char *nic_name; - int sock = 0; - int ret; - int duplex; - __u32 uint_speed; - int speed; - struct ifreq ifr; - struct ethtool_cmd ethcmd; - PyObject *py_retlist = NULL; - - if (! PyArg_ParseTuple(args, "s", &nic_name)) - return NULL; - - sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock == -1) - return PyErr_SetFromOSErrnoWithSyscall("socket()"); - PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); - - // duplex and speed - memset(ðcmd, 0, sizeof ethcmd); - ethcmd.cmd = ETHTOOL_GSET; - ifr.ifr_data = (void *)ðcmd; - ret = ioctl(sock, SIOCETHTOOL, &ifr); - - if (ret != -1) { - duplex = ethcmd.duplex; - // speed is returned from ethtool as a __u32 ranging from 0 to INT_MAX - // or SPEED_UNKNOWN (-1) - uint_speed = psutil_ethtool_cmd_speed(ðcmd); - if (uint_speed == (__u32)SPEED_UNKNOWN || uint_speed > INT_MAX) { - speed = 0; - } - else { - speed = (int)uint_speed; - } - } - else { - if ((errno == EOPNOTSUPP) || (errno == EINVAL)) { - // EOPNOTSUPP may occur in case of wi-fi cards. - // For EINVAL see: - // https://github.com/giampaolo/psutil/issues/797 - // #issuecomment-202999532 - duplex = DUPLEX_UNKNOWN; - speed = 0; - } - else { - PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCETHTOOL)"); - goto error; - } - } - - py_retlist = Py_BuildValue("[ii]", duplex, speed); - if (!py_retlist) - goto error; - close(sock); - return py_retlist; - -error: - if (sock != -1) - close(sock); - return NULL; -} - - -/* - * Module init. - */ static PyMethodDef mod_methods[] = { // --- per-process functions - -#if PSUTIL_HAVE_IOPRIO +#ifdef PSUTIL_HAVE_IOPRIO {"proc_ioprio_get", psutil_proc_ioprio_get, METH_VARARGS}, {"proc_ioprio_set", psutil_proc_ioprio_set, METH_VARARGS}, #endif @@ -514,13 +34,11 @@ static PyMethodDef mod_methods[] = { {"disk_partitions", psutil_disk_partitions, METH_VARARGS}, {"users", psutil_users, METH_VARARGS}, {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS}, - // --- linux specific {"linux_sysinfo", psutil_linux_sysinfo, METH_VARARGS}, // --- others {"check_pid_range", psutil_check_pid_range, METH_VARARGS}, {"set_debug", psutil_set_debug, METH_VARARGS}, - {NULL, NULL, 0, NULL} }; diff --git a/psutil/arch/linux/disk.c b/psutil/arch/linux/disk.c new file mode 100644 index 000000000..692a7d5d4 --- /dev/null +++ b/psutil/arch/linux/disk.c @@ -0,0 +1,75 @@ +/* + * 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 +#include + +#include "../../_psutil_common.h" + + +// Return disk mounted partitions as a list of tuples including device, +// mount point and filesystem type. +PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + FILE *file = NULL; + struct mntent *entry; + char *mtab_path; + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (!PyArg_ParseTuple(args, "s", &mtab_path)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + file = setmntent(mtab_path, "r"); + Py_END_ALLOW_THREADS + if ((file == 0) || (file == NULL)) { + psutil_debug("setmntent() failed"); + PyErr_SetFromErrnoWithFilename(PyExc_OSError, mtab_path); + goto error; + } + + while ((entry = getmntent(file))) { + if (entry == NULL) { + PyErr_Format(PyExc_RuntimeError, "getmntent() syscall failed"); + goto error; + } + py_dev = PyUnicode_DecodeFSDefault(entry->mnt_fsname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(entry->mnt_dir); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue("(OOss)", + py_dev, // device + py_mountp, // mount point + entry->mnt_type, // fs type + entry->mnt_opts); // 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); + } + endmntent(file); + return py_retlist; + +error: + if (file != NULL) + endmntent(file); + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} diff --git a/psutil/arch/linux/disk.h b/psutil/arch/linux/disk.h new file mode 100644 index 000000000..90a86d611 --- /dev/null +++ b/psutil/arch/linux/disk.h @@ -0,0 +1,9 @@ +/* + * 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 + +PyObject *psutil_disk_partitions(PyObject* self, PyObject* args); diff --git a/psutil/arch/linux/mem.c b/psutil/arch/linux/mem.c new file mode 100644 index 000000000..3b9b4fef3 --- /dev/null +++ b/psutil/arch/linux/mem.c @@ -0,0 +1,30 @@ +/* + * 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 +#include + +#include "../../_psutil_common.h" + + +PyObject * +psutil_linux_sysinfo(PyObject *self, PyObject *args) { + struct sysinfo info; + + if (sysinfo(&info) != 0) + return PyErr_SetFromErrno(PyExc_OSError); + // note: boot time might also be determined from here + return Py_BuildValue( + "(kkkkkkI)", + info.totalram, // total + info.freeram, // free + info.bufferram, // buffer + info.sharedram, // shared + info.totalswap, // swap tot + info.freeswap, // swap free + info.mem_unit // multiplier + ); +} diff --git a/psutil/arch/linux/mem.h b/psutil/arch/linux/mem.h new file mode 100644 index 000000000..582d3e031 --- /dev/null +++ b/psutil/arch/linux/mem.h @@ -0,0 +1,9 @@ +/* + * 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 + +PyObject *psutil_linux_sysinfo(PyObject* self, PyObject* args); diff --git a/psutil/arch/linux/net.c b/psutil/arch/linux/net.c new file mode 100644 index 000000000..522a55dfc --- /dev/null +++ b/psutil/arch/linux/net.c @@ -0,0 +1,119 @@ +/* + * 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 +#include +#include +#include +#include +#include + +// see: https://github.com/giampaolo/psutil/issues/659 +#ifdef PSUTIL_ETHTOOL_MISSING_TYPES + #include + typedef __u64 u64; + typedef __u32 u32; + typedef __u16 u16; + typedef __u8 u8; +#endif + +// Avoid redefinition of struct sysinfo with musl libc. +#define _LINUX_SYSINFO_H +#include + +#include "../../_psutil_common.h" + + +// * defined in linux/ethtool.h but not always available (e.g. Android) +// * #ifdef check needed for old kernels, see: +// https://github.com/giampaolo/psutil/issues/2164 +static inline uint32_t +psutil_ethtool_cmd_speed(const struct ethtool_cmd *ecmd) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) + return ecmd->speed; +#else + return (ecmd->speed_hi << 16) | ecmd->speed; +#endif +} + +// May happen on old RedHat versions, see: +// https://github.com/giampaolo/psutil/issues/607 +#ifndef DUPLEX_UNKNOWN + #define DUPLEX_UNKNOWN 0xff +#endif +// https://github.com/giampaolo/psutil/pull/2156 +#ifndef SPEED_UNKNOWN + #define SPEED_UNKNOWN -1 +#endif + + +// References: +// * https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py +// * http://www.i-scream.org/libstatgrab/ +PyObject* +psutil_net_if_duplex_speed(PyObject* self, PyObject* args) { + char *nic_name; + int sock = 0; + int ret; + int duplex; + __u32 uint_speed; + int speed; + struct ifreq ifr; + struct ethtool_cmd ethcmd; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + return PyErr_SetFromOSErrnoWithSyscall("socket()"); + PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + + // duplex and speed + memset(ðcmd, 0, sizeof ethcmd); + ethcmd.cmd = ETHTOOL_GSET; + ifr.ifr_data = (void *)ðcmd; + ret = ioctl(sock, SIOCETHTOOL, &ifr); + + if (ret != -1) { + duplex = ethcmd.duplex; + // speed is returned from ethtool as a __u32 ranging from 0 to INT_MAX + // or SPEED_UNKNOWN (-1) + uint_speed = psutil_ethtool_cmd_speed(ðcmd); + if (uint_speed == (__u32)SPEED_UNKNOWN || uint_speed > INT_MAX) { + speed = 0; + } + else { + speed = (int)uint_speed; + } + } + else { + if ((errno == EOPNOTSUPP) || (errno == EINVAL)) { + // EOPNOTSUPP may occur in case of wi-fi cards. + // For EINVAL see: + // https://github.com/giampaolo/psutil/issues/797 + // #issuecomment-202999532 + duplex = DUPLEX_UNKNOWN; + speed = 0; + } + else { + PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCETHTOOL)"); + goto error; + } + } + + py_retlist = Py_BuildValue("[ii]", duplex, speed); + if (!py_retlist) + goto error; + close(sock); + return py_retlist; + +error: + if (sock != -1) + close(sock); + return NULL; +} diff --git a/psutil/arch/linux/net.h b/psutil/arch/linux/net.h new file mode 100644 index 000000000..55095c06c --- /dev/null +++ b/psutil/arch/linux/net.h @@ -0,0 +1,9 @@ +/* + * 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 + +PyObject *psutil_net_if_duplex_speed(PyObject* self, PyObject* args); diff --git a/psutil/arch/linux/proc.c b/psutil/arch/linux/proc.c new file mode 100644 index 000000000..ac87af917 --- /dev/null +++ b/psutil/arch/linux/proc.c @@ -0,0 +1,200 @@ +/* + * 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 +#include +#include + +#include "proc.h" +#include "../../_psutil_common.h" + + +#ifdef PSUTIL_HAVE_IOPRIO +enum { + IOPRIO_WHO_PROCESS = 1, +}; + +static inline int +ioprio_get(int which, int who) { + return syscall(__NR_ioprio_get, which, who); +} + +static inline int +ioprio_set(int which, int who, int ioprio) { + return syscall(__NR_ioprio_set, which, who, ioprio); +} + +#define IOPRIO_CLASS_SHIFT 13 +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) +#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) + + +// Return a (ioclass, iodata) Python tuple representing process I/O +// priority. +PyObject * +psutil_proc_ioprio_get(PyObject *self, PyObject *args) { + pid_t pid; + int ioprio, ioclass, iodata; + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); + if (ioprio == -1) + return PyErr_SetFromErrno(PyExc_OSError); + ioclass = IOPRIO_PRIO_CLASS(ioprio); + iodata = IOPRIO_PRIO_DATA(ioprio); + return Py_BuildValue("ii", ioclass, iodata); +} + + +// A wrapper around ioprio_set(); sets process I/O priority. ioclass +// can be either IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE or +// 0. iodata goes from 0 to 7 depending on ioclass specified. +PyObject * +psutil_proc_ioprio_set(PyObject *self, PyObject *args) { + pid_t pid; + int ioprio, ioclass, iodata; + int retval; + + if (! PyArg_ParseTuple( + args, _Py_PARSE_PID "ii", &pid, &ioclass, &iodata)) { + return NULL; + } + ioprio = IOPRIO_PRIO_VALUE(ioclass, iodata); + retval = ioprio_set(IOPRIO_WHO_PROCESS, pid, ioprio); + if (retval == -1) + return PyErr_SetFromErrno(PyExc_OSError); + Py_RETURN_NONE; +} +#endif // PSUTIL_HAVE_IOPRIO + + +#ifdef PSUTIL_HAVE_CPU_AFFINITY + +// Return process CPU affinity as a Python list. +PyObject * +psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) { + int cpu, ncpus, count, cpucount_s; + pid_t pid; + size_t setsize; + cpu_set_t *mask = NULL; + PyObject *py_list = NULL; + + if (!PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + // The minimum number of CPUs allocated in a cpu_set_t. + ncpus = sizeof(unsigned long) * CHAR_BIT; + while (1) { + setsize = CPU_ALLOC_SIZE(ncpus); + mask = CPU_ALLOC(ncpus); + if (mask == NULL) { + psutil_debug("CPU_ALLOC() failed"); + return PyErr_NoMemory(); + } + if (sched_getaffinity(pid, setsize, mask) == 0) + break; + CPU_FREE(mask); + if (errno != EINVAL) + return PyErr_SetFromErrno(PyExc_OSError); + if (ncpus > INT_MAX / 2) { + PyErr_SetString(PyExc_OverflowError, "could not allocate " + "a large enough CPU set"); + return NULL; + } + ncpus = ncpus * 2; + } + + py_list = PyList_New(0); + if (py_list == NULL) + goto error; + + cpucount_s = CPU_COUNT_S(setsize, mask); + for (cpu = 0, count = cpucount_s; count; cpu++) { + if (CPU_ISSET_S(cpu, setsize, mask)) { +#if PY_MAJOR_VERSION >= 3 + PyObject *cpu_num = PyLong_FromLong(cpu); +#else + PyObject *cpu_num = PyInt_FromLong(cpu); +#endif + if (cpu_num == NULL) + goto error; + if (PyList_Append(py_list, cpu_num)) { + Py_DECREF(cpu_num); + goto error; + } + Py_DECREF(cpu_num); + --count; + } + } + CPU_FREE(mask); + return py_list; + +error: + if (mask) + CPU_FREE(mask); + Py_XDECREF(py_list); + return NULL; +} + + +// Set process CPU affinity; expects a bitmask. +PyObject * +psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { + cpu_set_t cpu_set; + size_t len; + pid_t pid; + Py_ssize_t i, seq_len; + PyObject *py_cpu_set; + + if (!PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &py_cpu_set)) + return NULL; + + if (!PySequence_Check(py_cpu_set)) { + return PyErr_Format( + PyExc_TypeError, +#if PY_MAJOR_VERSION >= 3 + "sequence argument expected, got %R", Py_TYPE(py_cpu_set) +#else + "sequence argument expected, got %s", Py_TYPE(py_cpu_set)->tp_name +#endif + ); + } + + seq_len = PySequence_Size(py_cpu_set); + if (seq_len < 0) { + return NULL; + } + CPU_ZERO(&cpu_set); + for (i = 0; i < seq_len; i++) { + PyObject *item = PySequence_GetItem(py_cpu_set, i); + if (!item) { + return NULL; + } +#if PY_MAJOR_VERSION >= 3 + long value = PyLong_AsLong(item); +#else + long value = PyInt_AsLong(item); +#endif + Py_XDECREF(item); + if ((value == -1) || PyErr_Occurred()) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_ValueError, "invalid CPU value"); + return NULL; + } + CPU_SET(value, &cpu_set); + } + + len = sizeof(cpu_set); + if (sched_setaffinity(pid, len, &cpu_set)) { + return PyErr_SetFromErrno(PyExc_OSError); + } + + Py_RETURN_NONE; +} +#endif // PSUTIL_HAVE_CPU_AFFINITY diff --git a/psutil/arch/linux/proc.h b/psutil/arch/linux/proc.h new file mode 100644 index 000000000..94a84c62e --- /dev/null +++ b/psutil/arch/linux/proc.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 +#include // __NR_* +#include // CPU_ALLOC + +// Linux >= 2.6.13 +#if defined(__NR_ioprio_get) && defined(__NR_ioprio_set) + #define PSUTIL_HAVE_IOPRIO + + PyObject *psutil_proc_ioprio_get(PyObject *self, PyObject *args); + PyObject *psutil_proc_ioprio_set(PyObject *self, PyObject *args); +#endif + +// Should exist starting from CentOS 6 (year 2011). +#ifdef CPU_ALLOC + #define PSUTIL_HAVE_CPU_AFFINITY + + PyObject *psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args); + PyObject *psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args); +#endif diff --git a/psutil/arch/linux/users.c b/psutil/arch/linux/users.c new file mode 100644 index 000000000..9ca010a19 --- /dev/null +++ b/psutil/arch/linux/users.c @@ -0,0 +1,72 @@ +/* + * 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 +#include + +#include "../../_psutil_common.h" + + +PyObject * +psutil_users(PyObject *self, PyObject *args) { + struct utmp *ut; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + PyObject *py_user_proc = NULL; + + if (py_retlist == NULL) + return NULL; + setutent(); + while (NULL != (ut = getutent())) { + py_tuple = NULL; + py_user_proc = NULL; + if (ut->ut_type == USER_PROCESS) + py_user_proc = Py_True; + else + py_user_proc = Py_False; + py_username = PyUnicode_DecodeFSDefault(ut->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); + if (! py_hostname) + goto error; + + py_tuple = Py_BuildValue( + "OOOdO" _Py_PARSE_PID, + py_username, // username + py_tty, // tty + py_hostname, // hostname + (double)ut->ut_tv.tv_sec, // tstamp + py_user_proc, // (bool) user process + ut->ut_pid // process id + ); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + } + endutent(); + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + endutent(); + return NULL; +} diff --git a/psutil/arch/linux/users.h b/psutil/arch/linux/users.h new file mode 100644 index 000000000..ba2735d1d --- /dev/null +++ b/psutil/arch/linux/users.h @@ -0,0 +1,9 @@ +/* + * 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 + +PyObject *psutil_users(PyObject* self, PyObject* args); diff --git a/setup.py b/setup.py index eef7bf455..6702268f8 100755 --- a/setup.py +++ b/setup.py @@ -297,7 +297,11 @@ def get_winver(): macros.append(("PSUTIL_LINUX", 1)) ext = Extension( 'psutil._psutil_linux', - sources=sources + ['psutil/_psutil_linux.c'], + sources=( + sources + + ["psutil/_psutil_linux.c"] + + glob.glob("psutil/arch/linux/*.c") + ), define_macros=macros, **py_limited_api)