diff --git a/examples/ethtool/ethtool-ioctl_get_infos.py b/examples/ethtool/ethtool-ioctl_get_infos.py index cb7417055..cf6f9a33c 100644 --- a/examples/ethtool/ethtool-ioctl_get_infos.py +++ b/examples/ethtool/ethtool-ioctl_get_infos.py @@ -26,3 +26,7 @@ print("\n=== Device coalesce: ===") for name, value in dev.get_coalesce().items(): print("\t{}: {}".format(name, value)) + +print("\n=== Device statistics: ===") +for name, value in dev.get_statistics(): + print("\t{}: {}".format(name, value)) diff --git a/pyroute2/ethtool/ioctl.py b/pyroute2/ethtool/ioctl.py index e539f14f3..6afff77da 100644 --- a/pyroute2/ethtool/ioctl.py +++ b/pyroute2/ethtool/ioctl.py @@ -20,6 +20,7 @@ ETHTOOL_GLINKSETTINGS = 0x0000004C ETHTOOL_GSTRINGS = 0x0000001B +ETHTOOL_GSTATS = 0x0000001D ETH_GSTRING_LEN = 32 ETHTOOL_GRXCSUM = 0x00000014 @@ -39,6 +40,7 @@ SOPASS_MAX = 6 +ETH_SS_STATS = 1 ETH_SS_FEATURES = 4 ETH_FLAG_RXCSUM = 1 << 0 @@ -299,6 +301,17 @@ class EthtoolSetFeaturesBlock(ctypes.Structure): _fields_ = [("changed", ctypes.c_uint32), ("active", ctypes.c_uint32)] +def generate_EthtoolGStats(stats_length): + class EthtoolGStats(ctypes.Structure): + _fields_ = [ + ("cmd", ctypes.c_uint32), + ("size", ctypes.c_uint32), + ("data", ctypes.c_uint64 * stats_length), + ] + + return EthtoolGStats + + def div_round_up(n, d): return int(((n) + (d) - 1) / (d)) @@ -327,34 +340,26 @@ class FeatureState(ctypes.Structure): _fields_ = [("off_flags", ctypes.c_uint32), ("features", EthtoolGfeatures)] -def generate_IfReqData(gstrings_length): - EthtoolGstrings = generate_EthtoolGstrings(gstrings_length) - - class IfReqData(ctypes.Union): - _fields_ = [ - ("ifr_data", ctypes.POINTER(EthtoolCmd)), - ("coalesce", ctypes.POINTER(EthtoolCoalesce)), - ("value", ctypes.POINTER(EthtoolValue)), - ("sset_info", ctypes.POINTER(EthtoolSsetInfo)), - ("gstrings", ctypes.POINTER(EthtoolGstrings)), - ("gfeatures", ctypes.POINTER(EthtoolGfeatures)), - ("sfeatures", ctypes.POINTER(EthtoolSfeatures)), - ("glinksettings", ctypes.POINTER(IoctlEthtoolLinkSettings)), - ("wolinfo", ctypes.POINTER(EthtoolWolInfo)), - ] - - return IfReqData, EthtoolGstrings - - -def generate_IfReq(gstrings_length): - IfReqData, EthtoolGstrings = generate_IfReqData(gstrings_length) +class IfReqData(ctypes.Union): + dummy = generate_EthtoolGstrings(0) + _fields_ = [ + ("ifr_data", ctypes.POINTER(EthtoolCmd)), + ("coalesce", ctypes.POINTER(EthtoolCoalesce)), + ("value", ctypes.POINTER(EthtoolValue)), + ("sset_info", ctypes.POINTER(EthtoolSsetInfo)), + ("gstrings", ctypes.POINTER(None)), + ("gstats", ctypes.POINTER(None)), + ("gfeatures", ctypes.POINTER(EthtoolGfeatures)), + ("sfeatures", ctypes.POINTER(EthtoolSfeatures)), + ("glinksettings", ctypes.POINTER(IoctlEthtoolLinkSettings)), + ("wolinfo", ctypes.POINTER(EthtoolWolInfo)), + ] - class IfReq(ctypes.Structure): - _pack_ = 1 - _anonymous_ = ("u",) - _fields_ = [("ifr_name", ctypes.c_uint8 * IFNAMSIZ), ("u", IfReqData)] - return IfReq, EthtoolGstrings +class IfReq(ctypes.Structure): + _pack_ = 1 + _anonymous_ = ("u",) + _fields_ = [("ifr_name", ctypes.c_uint8 * IFNAMSIZ), ("u", IfReqData)] class IfReqSsetInfo(ctypes.Structure): @@ -448,6 +453,7 @@ def __init__(self, ifname=None): self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.ifname = None self.ifreq = None + self.stat_names = None if ifname is not None: self.change_ifname(ifname) @@ -458,22 +464,9 @@ def close(self): def change_ifname(self, ifname): self.ifname = bytearray(ifname, 'utf-8') self.ifname.extend(b"\0" * (IFNAMSIZ - len(self.ifname))) - sset_info = EthtoolSsetInfo( - cmd=ETHTOOL_GSSET_INFO, reserved=0, sset_mask=1 << ETH_SS_FEATURES - ) - ifreq_sset = IfReqSsetInfo() - ifreq_sset.ifr_name = (ctypes.c_uint8 * IFNAMSIZ)(*self.ifname) - ifreq_sset.info = ctypes.pointer(sset_info) - fcntl.ioctl(self.sock, SIOCETHTOOL, ifreq_sset) - if sset_info.sset_mask: - self.gstrings_length = sset_info.data - else: - self.gstrings_length = 256 - IfReq, self.EthtoolGstringsType = generate_IfReq( - gstrings_length=self.gstrings_length - ) self.ifreq = IfReq() self.ifreq.ifr_name = (ctypes.c_uint8 * IFNAMSIZ)(*self.ifname) + self.stat_names = None def ioctl(self): try: @@ -486,17 +479,42 @@ def ioctl(self): raise NoSuchDevice(self.ifname.decode("utf-8")) raise + def get_statistics(self): + """Statistics in raw format, without names""" + if not self.stat_names: + self.stat_names = self.get_stringset(set_id=ETH_SS_STATS) + gstats = generate_EthtoolGStats(len(self.stat_names))(cmd=ETHTOOL_GSTATS) + self.ifreq.gstats = ctypes.cast(ctypes.pointer(gstats), ctypes.POINTER(None)) + self.ioctl() + assert len(self.stat_names) == len(gstats.data) + return list(zip(self.stat_names, gstats.data)) + + def get_stringset_length(self, set_id): + sset_info = EthtoolSsetInfo( + cmd=ETHTOOL_GSSET_INFO, reserved=0, sset_mask=1 << set_id + ) + ifreq_sset = IfReqSsetInfo() + ifreq_sset.ifr_name = (ctypes.c_uint8 * IFNAMSIZ)(*self.ifname) + ifreq_sset.info = ctypes.pointer(sset_info) + fcntl.ioctl(self.sock, SIOCETHTOOL, ifreq_sset) + assert sset_info.sset_mask + return sset_info.data + def get_stringset( self, set_id=ETH_SS_FEATURES, drvinfo_offset=0, null_terminate=1 ): - strings_found = [] - gstrings = self.EthtoolGstringsType( - cmd=ETHTOOL_GSTRINGS, string_set=set_id, len=self.gstrings_length + # different sets have potentially different lengthts, + # obtain size dynamically + gstrings_length = self.get_stringset_length(set_id) + EthtoolGstringsType = generate_EthtoolGstrings(gstrings_length) + gstrings = EthtoolGstringsType( + cmd=ETHTOOL_GSTRINGS, string_set=set_id, len=gstrings_length ) - self.ifreq.gstrings = ctypes.pointer(gstrings) + self.ifreq.gstrings = ctypes.cast(ctypes.pointer(gstrings), ctypes.POINTER(None)) self.ioctl() - for i in range(self.gstrings_length): + strings_found = [] + for i in range(gstrings_length): buf = '' for j in range(ETH_GSTRING_LEN): code = gstrings.strings[i][j] @@ -507,7 +525,7 @@ def get_stringset( return strings_found def get_features(self): - stringsset = self.get_stringset() + stringsset = self.get_stringset(set_id=ETH_SS_FEATURES) cmd = EthtoolGfeatures() cmd.cmd = ETHTOOL_GFEATURES cmd.size = feature_bits_to_blocks(len(stringsset))