diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2015-02-11 16:10:23 +0100 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2015-02-11 16:10:23 +0100 |
commit | 5e0e458aa09f6dc84b89cdd801671536d89e8306 (patch) | |
tree | 9a4a653cbb8967443b9e89165fa467112a144b07 | |
parent | 543225c5e768aa45e00827c46002a1186ed53e66 (diff) | |
download | psutil-5e0e458aa09f6dc84b89cdd801671536d89e8306.tar.gz |
#250: linux implementation
-rw-r--r-- | README.rst | 3 | ||||
-rw-r--r-- | docs/index.rst | 39 | ||||
-rw-r--r-- | psutil/__init__.py | 23 | ||||
-rw-r--r-- | psutil/_common.py | 14 | ||||
-rw-r--r-- | psutil/_pslinux.py | 15 | ||||
-rw-r--r-- | psutil/_psutil_linux.c | 96 | ||||
-rw-r--r-- | psutil/_psutil_linux.h | 1 | ||||
-rw-r--r-- | test/test_memory_leaks.py | 3 | ||||
-rw-r--r-- | test/test_psutil.py | 21 |
9 files changed, 212 insertions, 3 deletions
@@ -156,6 +156,9 @@ Network snic(family=<AddressFamily.AF_INET6: 10>, address='fe80::c685:8ff:fe45:641%wlan0', netmask='ffff:ffff:ffff:ffff::', broadcast=None), snic(family=<AddressFamily.AF_PACKET: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff')]} >>> + >>> psutil.net_if_stats() + {'eth0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500), + 'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536)} Other system info ================= diff --git a/docs/index.rst b/docs/index.rst index f860cce7..781a216f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -444,6 +444,30 @@ Network *New in 3.0.0* +.. function:: net_if_stats() + + Return information about each NIC (network interface card) installed on the + system as a dictionary whose keys are the NIC names and value is a namedtuple + with the following fields: + + - **isup** + - **duplex** + - **speed** + - **mtu** + + *isup* is a boolean indicating whether the NIC is up and running, *duplex* + can be either :const:`NIC_DUPLEX_FULL`, :const:`NIC_DUPLEX_HALF` or + :const:`NIC_DUPLEX_UNKNOWN`, *speed* is the NIC speed expressed in mega bits + (MB), if it can't be determined (e.g. 'localhost') it will be set to ``0``, + *mtu* is the maximum transmission unit expressed in bytes. Example: + + >>> import psutil + >>> psutil.net_if_stats() + {'eth0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500), + 'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536)} + + *New in 3.0.0* + Other system info ----------------- @@ -1252,7 +1276,7 @@ Constants Availability: Windows - .. versionchanged:: 3.0.0 on Python >= 3.4 thse constants are + .. versionchanged:: 3.0.0 on Python >= 3.4 these constants are `enums <https://docs.python.org/3/library/enum.html#module-enum>`__ instead of a plain integer. @@ -1316,3 +1340,16 @@ Constants To be used in conjunction with :func:`psutil.net_if_addrs()`. *New in 3.0.0* + +.. _const-duplex: +.. data:: NIC_DUPLEX_FULL + NIC_DUPLEX_HALF + NIC_DUPLEX_UNKNOWN + + Constants which identifies whether a NIC (network interface card) has full or + half mode speed. NIC_DUPLEX_FULL means the NIC is able to send and receive + data (files) simultaneously, NIC_DUPLEX_FULL means the NIC can either send or + receive data at a time. + To be used in conjunction with :func:`psutil.net_if_stats()`. + + *New in 3.0.0* diff --git a/psutil/__init__.py b/psutil/__init__.py index 7b474c09..af2482d6 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -43,8 +43,7 @@ from ._common import (STATUS_RUNNING, # NOQA STATUS_WAKING, STATUS_LOCKED, STATUS_IDLE, # bsd - STATUS_WAITING, # bsd -) + STATUS_WAITING) # bsd from ._common import (CONN_ESTABLISHED, CONN_SYN_SENT, @@ -59,6 +58,10 @@ from ._common import (CONN_ESTABLISHED, CONN_CLOSING, CONN_NONE) +from ._common import (NIC_DUPLEX_FULL, # NOQA + NIC_DUPLEX_HALF, + NIC_DUPLEX_UNKNOWN) + if sys.platform.startswith("linux"): from . import _pslinux as _psplatform from ._pslinux import (phymem_buffers, # NOQA @@ -152,6 +155,7 @@ __all__ = [ "virtual_memory", "swap_memory", # memory "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu "net_io_counters", "net_connections", "net_if_addrs", # network + "net_if_stats", "disk_io_counters", "disk_partitions", "disk_usage", # disk "users", "boot_time", # others ] @@ -1853,6 +1857,21 @@ def net_if_addrs(): return dict(ret) +def net_if_stats(): + """Return information about each NIC (network interface card) + installed on the system as a dictionary whose keys are the + NIC names and value is a namedtuple with the following fields: + + - isup: whether the interface is up (bool) + - duplex: can be either NIC_DUPLEX_FULL, NIC_DUPLEX_HALF or + NIC_DUPLEX_UNKNOWN + - speed: the NIC speed expressed in mega bits (MB); if it can't + be determined (e.g. 'localhost') it will be set to 0. + - mtu: the maximum transmission unit expressed in bytes. + """ + return _psplatform.net_if_stats() + + # ===================================================================== # --- other system related functions # ===================================================================== diff --git a/psutil/_common.py b/psutil/_common.py index cc223cf8..4298c7ee 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -56,6 +56,18 @@ CONN_LISTEN = "LISTEN" CONN_CLOSING = "CLOSING" CONN_NONE = "NONE" +if enum is None: + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 +else: + class NicDuplex(enum.IntEnum): + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 + + globals().update(NicDuplex.__members__) + # --- functions @@ -240,6 +252,8 @@ sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', 'status', 'pid']) # psutil.net_if_addrs() snic = namedtuple('snic', ['family', 'address', 'netmask', 'broadcast']) +# psutil.net_if_stats +snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu']) # --- namedtuples for psutil.Process methods diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index de77e859..960ec598 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -24,6 +24,7 @@ from . import _psposix from . import _psutil_linux as cext from . import _psutil_posix as cext_posix from ._common import isfile_strict, usage_percent, deprecated +from ._common import NIC_DUPLEX_FULL, NIC_DUPLEX_HALF, NIC_DUPLEX_UNKNOWN from ._compat import PY3 if sys.version_info >= (3, 4): @@ -570,6 +571,20 @@ def net_io_counters(): return retdict +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + duplex_map = {cext.DUPLEX_FULL: NIC_DUPLEX_FULL, + cext.DUPLEX_HALF: NIC_DUPLEX_HALF, + cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN} + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext.net_if_stats(name) + duplex = duplex_map[duplex] + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + net_if_addrs = cext_posix.net_if_addrs diff --git a/psutil/_psutil_linux.c b/psutil/_psutil_linux.c index 921e03fe..8ea0e303 100644 --- a/psutil/_psutil_linux.c +++ b/psutil/_psutil_linux.c @@ -19,6 +19,11 @@ #include <linux/version.h> #include <sys/syscall.h> #include <sys/sysinfo.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <linux/sockios.h> +#include <linux/if.h> +#include <linux/ethtool.h> #include "_psutil_linux.h" @@ -476,6 +481,92 @@ error: /* + * Return stats (isup?, duplex, speed) 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_stats(PyObject* self, PyObject* args) +{ + char *nic_name; + int sock = 0; + int ret; + int duplex; + int speed; + struct ifreq ifr; + struct ethtool_cmd ethcmd; + PyObject *py_is_up = NULL; + PyObject *py_mtu = NULL; + PyObject *py_ret = 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; + py_mtu = Py_BuildValue("i", ifr.ifr_mtu); + if (!py_mtu) + goto error; + Py_INCREF(py_mtu); + + // duplex and speed + memset(ðcmd, 0, sizeof ethcmd); + ethcmd.cmd = ETHTOOL_GSET; + ifr.ifr_data = (caddr_t)ðcmd; + ret = ioctl(sock, SIOCETHTOOL, &ifr); + + if (ret != -1) { + duplex = ethcmd.duplex; + speed = ethcmd.speed; + } + else { + if (errno == EOPNOTSUPP) { + // we typically get here in case of wi-fi cards + duplex = DUPLEX_UNKNOWN; + speed = 0; + } + else { + goto error; + } + } + + close(sock); + py_ret = Py_BuildValue("[OiiO]", py_is_up, duplex, speed, py_mtu); + if (!py_ret) + goto error; + Py_DECREF(py_is_up); + Py_DECREF(py_mtu); + return py_ret; + +error: + Py_XDECREF(py_is_up); + Py_XDECREF(py_mtu); + if (sock != 0) + close(sock); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +/* * Define the psutil C module methods and initialize the module. */ static PyMethodDef @@ -501,6 +592,8 @@ PsutilMethods[] = "device, mount point and filesystem type"}, {"users", psutil_users, METH_VARARGS, "Return currently connected users as a list of tuples"}, + {"net_if_stats", psutil_net_if_stats, METH_VARARGS, + "Return NIC stats (isup, duplex, speed, mtu)"}, // --- linux specific @@ -599,6 +692,9 @@ void init_psutil_linux(void) PyModule_AddIntConstant(module, "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING); #endif #endif + PyModule_AddIntConstant(module, "DUPLEX_HALF", DUPLEX_HALF); + PyModule_AddIntConstant(module, "DUPLEX_FULL", DUPLEX_FULL); + PyModule_AddIntConstant(module, "DUPLEX_UNKNOWN", DUPLEX_UNKNOWN); if (module == NULL) { INITERROR; diff --git a/psutil/_psutil_linux.h b/psutil/_psutil_linux.h index 04ffec3d..ec6a3387 100644 --- a/psutil/_psutil_linux.h +++ b/psutil/_psutil_linux.h @@ -18,3 +18,4 @@ static PyObject* psutil_proc_ioprio_get(PyObject* self, PyObject* args); static PyObject* psutil_disk_partitions(PyObject* self, PyObject* args); static PyObject* psutil_linux_sysinfo(PyObject* self, PyObject* args); static PyObject* psutil_users(PyObject* self, PyObject* args); +static PyObject* psutil_net_if_stats(PyObject* self, PyObject* args); diff --git a/test/test_memory_leaks.py b/test/test_memory_leaks.py index 1441daa1..acfc3864 100644 --- a/test/test_memory_leaks.py +++ b/test/test_memory_leaks.py @@ -412,6 +412,9 @@ class TestModuleFunctionsLeaks(Base): def test_net_if_addrs(self): self.execute('net_if_addrs') + def test_net_if_stats(self): + self.execute('net_if_stats') + def test_main(): test_suite = unittest.TestSuite() diff --git a/test/test_psutil.py b/test/test_psutil.py index 979c77a5..dad4ddc7 100644 --- a/test/test_psutil.py +++ b/test/test_psutil.py @@ -1090,6 +1090,27 @@ class TestSystemAPIs(unittest.TestCase): elif WINDOWS: self.assertEqual(psutil.AF_LINK, -1) + def test_net_if_stats(self): + nics = psutil.net_if_stats() + assert nics, nics + all_duplexes = (psutil.NIC_DUPLEX_FULL, + psutil.NIC_DUPLEX_HALF, + psutil.NIC_DUPLEX_UNKNOWN) + for nic, stats in nics.items(): + isup, duplex, speed, mtu = stats + self.assertIsInstance(isup, bool) + self.assertIn(duplex, all_duplexes) + self.assertIn(duplex, all_duplexes) + self.assertGreaterEqual(speed, 0) + self.assertGreaterEqual(mtu, 0) + + def test_net_functions_names(self): + a = psutil.net_io_counters(pernic=True).keys() + b = psutil.net_if_addrs().keys() + c = psutil.net_if_stats().keys() + self.assertEqual(sorted(a), sorted(b)) + self.assertEqual(sorted(b), sorted(c)) + @unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'), '/proc/diskstats not available on this linux version') def test_disk_io_counters(self): |