"""
Ethtool parsers
===============
Classes to parse ``ethtool`` command information.
The interface information for a machine is stored as lists. Each interface
is accessed by iterating through the shared parser list.
The interface classes all provide the following properties:
* ``iface`` and ``ifname``: the interface name (derived from the output file).
* ``data``: the data for that interface
Parsers provided by this module include:
CoalescingInfo - command ``/sbin/ethtool -c {interface}``
---------------------------------------------------------
Driver - command ``/sbin/ethtool -i {interface}``
-------------------------------------------------
Ethtool - command ``/sbin/ethtool {interface}``
-----------------------------------------------
Features - command ``/sbin/ethtool -k {interface}``
---------------------------------------------------
Pause - command ``/sbin/ethtool -a {interface}``
------------------------------------------------
Ring - command ``/sbin/ethtool -g {interface}``
-----------------------------------------------
Statistics - command ``/sbin/ethtool -S {interface}``
-----------------------------------------------------
TimeStamp - command ``/sbin/ethtool -T {interface}``
----------------------------------------------------
"""
import os
import re
import sys
from collections import namedtuple
from insights.core import CommandParser, LegacyItemAccess
from insights.core.exceptions import ParseException
from insights.core.plugins import parser
from insights.specs import Specs
if sys.version_info[0] == 3:
# Python 3
unicode = str
[docs]@parser(Specs.ethtool_i)
class Driver(CommandParser):
"""
Parse information for the ``ethtool -i`` command.
All the ``ethtool -i`` outputs are stored as a list, in no particular
order.
Each driver is stored as a dictionary in the ``data`` property. If the
key starts with 'supports', then the value is a boolean test of whether
the string is 'yes'. If the value is not given on the string (e.g.
'bus-info:'), the value is set to None.
All data is also set as attributes of the object with the attribute name
being the key name with hyphens replaced with underscores.
Attributes:
data (dict): Dictionary of keys with values in a list.
iface (str): Interface name.
driver (str): The driver providing the interface.
version (str): The version of the interface driver.
firmware_version (str): The firmware version of the interface.
supports_statistics (bool): Does the interface support statistics
gathering?
supports_test (bool): Does the interface support internal self-tests?
supports_eeprom_access (bool): Does the interface support access to
the EEPROM?
supports_register_dump (bool): Does the interface support dumping the
internal registers?
supports_priv_flags (bool): Does the interface support use of
privileged flags?
Sample input for ``/sbin/ethtool -i bond0``::
driver: bonding
version: 3.6.0
firmware-version: 2
bus-info:
supports-statistics: no
supports-test: no
supports-eeprom-access: no
supports-register-dump: no
supports-priv-flags: no
Examples::
>>> len(interfaces) # All interfaces in a list
1
>>> type(interfaces[0])
<class 'insights.parsers.ethtool.Driver'>
>>> bond0 = interfaces[0] # Would normally iterate through interfaces
>>> bond0.iface
'bond0'
>>> bond0.ifname
'bond0'
>>> bond0.data['driver'] # Old-style access
'bonding'
>>> bond0.driver # New-style access
'bonding'
>>> hasattr(bond0, 'bus_info')
True
>>> bond0.bus_info is None
True
>>> bond0.supports_statistics
False
"""
@property
def ifname(self):
"""(str): the interface name"""
return self.iface
[docs] def parse_content(self, content):
self.iface = extract_iface_name_from_path(self.file_path, "ethtool_-i_")
self.data = {}
self.driver = None
self.version = None
self.firmware_version = None
self.bus_info = None
self.supports_statistics = None
self.supports_test = None
self.supports_eeprom_access = None
self.supports_register_dump = None
self.supports_priv_flags = None
for line in content:
if ":" in line:
key, value = [s.strip() for s in line.split(":", 1)]
value = value if value else None
if key.startswith("supports"):
value = value == "yes"
self.data[key] = value
setattr(self, key.replace("-", "_"), value)
[docs]@parser(Specs.ethtool_k)
class Features(LegacyItemAccess, CommandParser):
"""
Parse information for the ``ethtool -k`` command.
Features are stored as a flat set of key: value pairs, with no hierarchy
that the indentation of the input might imply. This means, that the
output below will provide data for 'tx-checksumming' and
'tx-checksum-ipv4'.
Each key stores a two-key dictionary:
* 'on' (boolean) - whether the value (before any '[fixed]') is 'on'.
* 'fixed' (boolean) - whether the value contains 'fixed'.
Attributes:
data (dict): Dictionary of keys with values in a list.
iface (str): Interface name.
Sample input for ``/sbin/ethtool -k bond0``::
Features for bond0:
rx-checksumming: off [fixed]
tx-checksumming: on
tx-checksum-ipv4: off [fixed]
tx-checksum-unneeded: on [fixed]
tx-checksum-ip-generic: off [fixed]
tx-checksum-ipv6: off [fixed]
tx-checksum-fcoe-crc: off [fixed]
tx-checksum-sctp: off [fixed]
scatter-gather: on
tx-scatter-gather: on [fixed]
tx-scatter-gather-fraglist: off [fixed]
tcp-segmentation-offload: on
tx-tcp-segmentation: on [fixed]
tx-tcp-ecn-segmentation: on [fixed]
tx-tcp6-segmentation: on [fixed]
udp-fragmentation-offload: off [fixed]
generic-segmentation-offload: off [requested on]
generic-receive-offload: on
large-receive-offload: on
rx-vlan-offload: on
tx-vlan-offload: on
ntuple-filters: off
receive-hashing: off
highdma: on [fixed]
rx-vlan-filter: on [fixed]
vlan-challenged: off [fixed]
tx-lockless: on [fixed]
netns-local: off [fixed]
tx-gso-robust: off [fixed]
tx-fcoe-segmentation: off [fixed]
tx-gre-segmentation: on [fixed]
tx-udp_tnl-segmentation: on [fixed]
fcoe-mtu: off [fixed]
loopback: off [fixed]
Examples:
>>> len(features) # All interfaces in a list
1
>>> type(features[0])
<class 'insights.parsers.ethtool.Features'>
>>> bond0 = features[0] # Would normally iterate through interfaces
>>> bond0.iface
'bond0'
>>> bond0.ifname
'bond0'
>>> bond0.data['rx-vlan-offload']['on'] # Traditional access
True
>>> bond0.data['rx-vlan-offload']['fixed']
False
>>> bond0.data['tx-checksum-sctp']['on']
False
>>> bond0.data['tx-checksum-sctp']['fixed']
True
>>> bond0.is_on('ntuple-filters')
False
>>> bond0.is_on('large-receive-offload')
True
>>> bond0.is_fixed('receive-hashing')
False
>>> bond0.is_fixed('fcoe-mtu')
True
"""
@property
def ifname(self):
"""(str): the interface name"""
return self.iface
[docs] def is_on(self, feature):
"""(bool): Does this feature exist and is it on?"""
return self.get(feature, {}).get('on', False)
[docs] def is_fixed(self, feature):
"""(bool): Does this feature exist and is it fixed?"""
return self.get(feature, {}).get('fixed', False)
[docs] def parse_content(self, content):
self.data = {}
self.iface = extract_iface_name_from_path(self.file_path, "ethtool_-k_")
# Handle e.g. '/sbin/ethtool: file not found'
if "ethtool" in content[0]:
return
# Handle 'Cannot get feature settings: Operation not supported'
if "Cannot get" in content[0]:
return
# Need to strip header line that's only on -k
for line in content[1:]:
if ":" in line:
key, value = [s.strip() for s in line.strip().split(":", 1)]
fixed = "fixed" in value
if fixed:
value = value.split()[0].strip()
self.data[key.strip()] = {
"on": value == "on",
"fixed": fixed
}
[docs]@parser(Specs.ethtool_a)
class Pause(CommandParser):
"""
Parse information for the ``ethtool -a`` command.
Each parameter in the input is stored as a key in a dictionary, with the
value being whether the found string equals 'on'.
Attributes:
data (dict): Dictionary of keys with values in a list.
iface (str): Interface name.
autonegotiate (bool): Is autonegotiate present and set to 'on'?
rx (bool): Is receive pausing present and set to 'on'?
tx (bool): Is transmit pausing present and set to 'on'?
rx_negotiated (bool): Is receive pause autonegotiate present and set to 'on'?
tx_negotiated (bool): Is transmit pause autonegotiate present and set to 'on'?
Sample input from ``/sbin/ethtool -a 0``::
Pause parameters for eth0:
Autonegotiate: on
RX: on
TX: on
RX negotiated: off
TX negotiated: off
Examples:
>>> len(pause) # All interfaces in a list
1
>>> type(pause[0])
<class 'insights.parsers.ethtool.Pause'>
>>> eth0 = pause[0] # Would normally iterate through interfaces
>>> eth0.iface
'eth0'
>>> eth0.ifname
'eth0'
>>> eth0.data['RX'] # Old-style accessor
True
>>> eth0.autonegotiate # New-style accessor
True
>>> eth0.rx_negotiated
False
"""
@property
def ifname(self):
return self.iface
@property
def autonegotiate(self):
return self.data.get("Autonegotiate", False)
@property
def rx(self):
return self.data.get("RX", False)
@property
def tx(self):
return self.data.get("TX", False)
@property
def rx_negotiated(self):
return self.data.get("RX Autonegotiate", False)
@property
def tx_negotiated(self):
return self.data.get("TX Autonegotiate", False)
[docs] def parse_content(self, content):
"""
Return ethtool -a result as a dict.
If ethtool -a outputs an error or could not get the pause state for
the device, the "iface" property will be set but the data dictionary
will be blank and all properties will return False.
"""
self.data = {}
self.iface = extract_iface_name_from_path(self.file_path, "ethtool_-a_")
# Handle e.g. '/sbin/ethtool: file not found'
if "ethtool" in content[0]:
return
# Handle 'Cannot get device pause settings: Operation not supported'
if "Cannot get" in content[0]:
return
for line in content[1:]:
if line.strip():
(key, value) = [s.strip() for s in line.split(":", 1)]
self.data[key] = (value == "on")
# Can't use key if it has a space in it, and we provide these
# as properties anyway.
# setattr(self, key, value == "on")
[docs]@parser(Specs.ethtool_c)
class CoalescingInfo(CommandParser):
"""
Parse information for the ``ethtool -c`` command.
The parsing is fairly similar to other ``ethtool`` parsers - the
interface name is available as the ``ifname`` and ``iface`` properties,
and the data about the coalescing information is stored in the ``data``
property as a dictionary. The one difference is the 'Adaptive RX' data,
which is stored as two keys - 'adaptive-rx' and 'adaptive-tx', for RX
and TX respectively. Both these return a boolean for whether the
respective state equals 'on'.
Otherwise, all values are made available as keys in the ``data``
dictionary, and as properties with the hyphen transmuted to an underscore
- e.g. ``obj.data['tx-usecs']`` is available as ``obj.tx_usecs``.
Attributes:
data (dict): Dictionary of keys with values in a list.
iface (str): Interface name.
Sample input for ``/sbin/ethtool -c eth0``::
Coalesce parameters for eth0:
Adaptive RX: off TX: off
stats-block-usecs: 0
sample-interval: 0
pkt-rate-low: 0
pkt-rate-high: 0
rx-usecs: 20
rx-frames: 5
rx-usecs-irq: 0
rx-frames-irq: 5
tx-usecs: 72
tx-frames: 53
tx-usecs-irq: 0
tx-frames-irq: 5
rx-usecs-low: 0
rx-frame-low: 0
tx-usecs-low: 0
tx-frame-low: 0
rx-usecs-high: 0
rx-frame-high: 0
tx-usecs-high: 0
tx-frame-high: 0
Examples:
>>> len(coalesce) # All interfaces in a list
1
>>> type(coalesce[0])
<class 'insights.parsers.ethtool.CoalescingInfo'>
>>> eth0 = coalesce[0] # Would normally iterate through interfaces
>>> eth0.iface
'eth0'
>>> eth0.ifname
'eth0'
>>> eth0.data['adaptive-rx'] # Old-style accessor
False
>>> eth0.adaptive_rx # New-style accessor
False
>>> eth0.rx_usecs # Note integer value
20
"""
@property
def ifname(self):
"""(str): the interface name"""
return self.iface
[docs] def parse_content(self, content):
"""
Parse the output of ``ethtool -c`` into a dictionary.
If ethtool -c outputs an error or could not get the pause state for
the device, the "iface" property will be set but the data dictionary
will be blank.
"""
content = list(content)
self.data = {}
self.iface = extract_iface_name_from_path(self.file_path, "ethtool_-c_")
if "ethtool" in content[0]:
return
if "Cannot get" in content[0]:
# cannot got pause param in ethtool
self.iface = extract_iface_name_from_content(content[1])
return
self.iface = extract_iface_name_from_content(content[0])
if len(content) <= 1:
raise ParseException("Command output missing value data")
second_line_content = content[1].split(" ")
self.data["adaptive-rx"] = (second_line_content[2] == "on")
self.adaptive_rx = (second_line_content[2] == "on")
self.data["adaptive-tx"] = (second_line_content[5] == "on")
self.adaptive_tx = (second_line_content[5] == "on")
for line in content[2:]:
if line.strip():
(key, value) = [s.strip() for s in line.split(":", 1)]
if unicode(value).isnumeric():
value = int(value)
self.data[key] = value
setattr(self, key.replace("-", "_"), value)
[docs]@parser(Specs.ethtool_g)
class Ring(CommandParser):
"""
Parse information for the ``ethtool -g`` command.
In addition to the standard ``iface`` and ``ifname`` parameters, as well
as being available in the ``data`` property, the interface statistics
are accessed using two parameters: ``max`` and ``current``.
Within each the interface settings are available as four parameters -
``rx``, ``rx_mini``, ``rx_jumbo`` and ``tx``.
Attributes:
data (dict): Dictionary of keys with values in a list.
iface (str): Interface name.
max (dict): Dictonary of maximum ring buffer settings.
current (dict): Dictionary of current ring buffer settings.
Sample input for ``/sbin/ethtool -g eth0``::
Ring parameters for eth0:
Pre-set maximums:
RX: 2047
RX Mini: 0
RX Jumbo: 0
TX: 511
Current hardware settings:
RX: 200
RX Mini: 0
RX Jumbo: 0
TX: 511
Examples:
>>> len(ring) # All interfaces in a list
1
>>> type(ring[0])
<class 'insights.parsers.ethtool.Ring'>
>>> eth0 = ring[0] # Would normally iterate through interfaces
>>> eth0.iface
'eth0'
>>> eth0.ifname
'eth0'
>>> eth0.data['max'].rx # Old-style access
2047
>>> eth0.max.rx # New-style access
2047
"""
Parameters = namedtuple("Parameters", ["rx", "rx_mini", "rx_jumbo", "tx"])
@property
def ifname(self):
"""
Return the name of network interface in content.
"""
return self.iface
[docs] def parse_content(self, content):
"""
Parse ``ethtool -g`` info into a dictionary.
"""
self.data = {}
# First guess from path information
self.iface = extract_iface_name_from_path(self.file_path, "ethtool_-g_")
self.max = self.current = None
if "ethtool" in content[0]:
return
if "Cannot get" in content[0]:
# cannot got pause param in ethtool
self.iface = extract_iface_name_from_content(content[1])
return
self.iface = extract_iface_name_from_content(content[0])
def set_section(section, data):
if section:
ringdata = Ring.Parameters(**section_data)
setattr(self, section, ringdata)
self.data[section] = ringdata
section = None
sections = {'Pre-set maximums:': 'max', 'Current hardware settings:': 'current'}
section_data = {}
# Skip "Ring parameters for interface:"
for line in content[1:]:
if line in sections:
set_section(section, section_data)
section = sections[line]
section_data = {}
elif ':' in line:
# key: value, store in section data for now
key, value = (s.strip() for s in line.split(":", 1))
if unicode(value).isnumeric():
section_data[key.replace(" ", "_").lower()] = int(value)
# Handle last found section, if any
set_section(section, section_data)
[docs]@parser(Specs.ethtool_S)
class Statistics(CommandParser):
"""
Parse information for the ``ethtool -S`` command.
All values are made available as keys in the ``data`` dictionary, and as
properties - e.g. ``obj.data['rx_jabbers']`` is available as
``obj.rx_jabbers``.
Attributes:
data (dict): Dictionary of keys with values in a list.
iface (str): Interface name.
Sample partial input for ``/sbin/ethtool -S eth0``::
NIC statistics:
rx_octets: 808488730
rx_fragments: 0
rx_ucast_packets: 1510830
rx_mcast_packets: 678653
rx_bcast_packets: 9921
rx_fcs_errors: 0
rx_align_errors: 0
rx_xon_pause_rcvd: 0
rx_xoff_pause_rcvd: 0
rx_mac_ctrl_rcvd: 0
rx_xoff_entered: 0
rx_frame_too_long_errors: 0
rx_jabbers: 0
Examples:
>>> len(stats) # All interfaces in a list
1
>>> type(stats[0])
<class 'insights.parsers.ethtool.Statistics'>
>>> eth0 = stats[0] # Would normally iterate through interfaces
>>> eth0.iface
'eth0'
>>> eth0.ifname
'eth0'
>>> eth0.data['rx_octets'] # Data as integers
808488730
>>> eth0.data['rx_fcs_errors']
0
"""
@property
def ifname(self):
"""
Return the name of network interface in content.
"""
return self.iface
[docs] def search(self, pattern, flags=0):
"""
Finds all the parameters matching a given regular expression.
Parameters:
pattern (raw): A regular expression
flags (int): Regular expression flags summed from ``re`` constants.
Returns:
(dict): A dictionary of the key/value pairs where the key matches
the given regular expression. An empty dictionary is returned if
no keys matched.
"""
results = {}
for k, v in self.data.items():
if re.search(pattern, k, flags):
results[k] = v
return results
[docs] def parse_content(self, content):
'''
Parse the output of ``ethtool -S``.
'''
self.data = {}
self.iface = extract_iface_name_from_path(self.file_path, "ethtool_-S_")
if "NIC statistics:" not in content[0]:
# if there's no data, then return self.data immediately.
# in this situation, content may looks like:
# "no stats available" or
# "Cannot get stats strings self.datarmation: Operation not supported"
return
for line in content[1:]: # ignore 'NIC statistics' line
if ':' not in line:
continue
# Some interfaces can report keys with colons in them, e.g.
# "rxq0: rx_pkts", so look for the last colon and strip from
# there.
i = line.rfind(':')
key = line[:i].strip()
value = line[i + 2:].strip()
value = int(value)
self.data[key] = value
[docs]@parser(Specs.ethtool_T)
class TimeStamp(CommandParser):
"""
Parse information for the ``ethtool -T`` command.
Each parameter in the input is stored as a key in a dictionary.
Attributes:
data (dict): Dictionary of keys with values.
Raises:
ParseException: Raised when any problem parsing the command output.
Sample partial input for ``/sbin/ethtool -T eno1``::
Time stamping parameters for eno1:
Capabilities:
hardware-transmit (SOF_TIMESTAMPING_TX_HARDWARE)
software-transmit (SOF_TIMESTAMPING_TX_SOFTWARE)
hardware-receive (SOF_TIMESTAMPING_RX_HARDWARE)
software-receive (SOF_TIMESTAMPING_RX_SOFTWARE)
software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
hardware-raw-clock (SOF_TIMESTAMPING_RAW_HARDWARE)
PTP Hardware Clock: 0
Hardware Transmit Timestamp Modes:
off (HWTSTAMP_TX_OFF)
on (HWTSTAMP_TX_ON)
Hardware Receive Filter Modes:
none (HWTSTAMP_FILTER_NONE)
all (HWTSTAMP_FILTER_ALL)
Examples:
>>> len(timestamp)
1
>>> type(timestamp[0])
<class 'insights.parsers.ethtool.TimeStamp'>
>>> eno1 = timestamp[0] # Would normally iterate through interfaces
>>> eno1.ifname
'eno1'
>>> eno1.data['Capabilities']['hardware-transmit']
'SOF_TIMESTAMPING_TX_HARDWARE'
>>> eno1.data['Capabilities']['hardware-raw-clock']
'SOF_TIMESTAMPING_RAW_HARDWARE'
>>> eno1.data['PTP Hardware Clock']
'0'
>>> eno1.data['Hardware Transmit Timestamp Modes']['off']
'HWTSTAMP_TX_OFF'
>>> eno1.data['Hardware Receive Filter Modes']['all']
'HWTSTAMP_FILTER_ALL'
"""
@property
def ifname(self):
"""(str): the interface name"""
return self.iface
[docs] def parse_content(self, content):
self.data = {}
self.iface = extract_iface_name_from_path(self.file_path, "ethtool_-T_")
group = None
for line in content[1:]:
line = line.strip()
if ":" in line:
key, val = [i.strip() for i in line.split(':', 1)]
group = {}
self.data[key] = val if val else group
elif line.endswith(')') and group is not None:
key, val = [i.strip() for i in line.split(None, 1)]
group[key] = val.strip('()')
elif line:
group[line] = None