Skip to content

Commit

Permalink
#250: linux implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
giampaolo committed Feb 11, 2015
1 parent 543225c commit 5e0e458
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 3 deletions.
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
=================
Expand Down
39 changes: 38 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------------
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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*
23 changes: 21 additions & 2 deletions psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
STATUS_WAKING,
STATUS_LOCKED,
STATUS_IDLE, # bsd
STATUS_WAITING, # bsd
)
STATUS_WAITING) # bsd

from ._common import (CONN_ESTABLISHED,
CONN_SYN_SENT,
Expand All @@ -59,6 +58,10 @@
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
Expand Down Expand Up @@ -152,6 +155,7 @@
"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
]
Expand Down Expand Up @@ -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
# =====================================================================
Expand Down
14 changes: 14 additions & 0 deletions psutil/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@
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

Expand Down Expand Up @@ -240,6 +252,8 @@ def socktype_to_enum(num):
'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
Expand Down
15 changes: 15 additions & 0 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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):
Expand Down Expand Up @@ -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


Expand Down
96 changes: 96 additions & 0 deletions psutil/_psutil_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -475,6 +480,92 @@ psutil_users(PyObject *self, PyObject *args)
}


/*
* 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(&ethcmd, 0, sizeof ethcmd);
ethcmd.cmd = ETHTOOL_GSET;
ifr.ifr_data = (caddr_t)&ethcmd;
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.
*/
Expand All @@ -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

Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions psutil/_psutil_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
3 changes: 3 additions & 0 deletions test/test_memory_leaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ def test_net_connections(self):
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()
Expand Down
21 changes: 21 additions & 0 deletions test/test_psutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,27 @@ def test_net_if_addrs(self):
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):
Expand Down

0 comments on commit 5e0e458

Please sign in to comment.