From cf62c50b7122f436c5e82782d17097e42f7d74ae Mon Sep 17 00:00:00 2001 From: Frey Alfredsson Date: Tue, 9 Apr 2024 01:50:04 +0200 Subject: [PATCH] Add ethtool monitoring support to Flent This commit adds ethtool monitoring to Flent. The "common.inc" file includes this capability in most Flent tests. The ethtool support adds three new test parameters: "ethtool_hosts", "ethtool_devices", and "ethtool_fields". Behind the scenes, Flent runs the "ethtool -S command on all devices that support it via SSH. A breakdown of these new parameters is as follows: - ethtool_hosts: Specifies the hosts to monitor ethtool fields. The hosts are specified using a comma-separated list. - ethtool_devices: This parameter specifies which network devices to monitor instead of all of them. This parameter is also a comma-separated list. - ethtool_fields: This parameter defines a comma-separated list of fields to monitor. If this field is not set, it will default to monitor the "rx_packets" and "tx_packets" fields. The fields can also be prefixed with the network card if you only want to see that field for that particular device. Example: ethtool_devices=eth0,eth1 ethtool_fields=tx_packets_phy,rx_packets_phy,eth2:rx_bytes In this example, the fields "tx_packets_phy" and "rx_packets_phy" will be monitored on eth0 and eth1; however, only field rx_bytes will be monitored on eth2. One limitation of this version of the ethtool capability is that all hosts will share the same values of devices and fields. Signed-off-by: Frey Alfredsson --- doc/tests.rst | 56 +++--- flent/runners.py | 80 ++++++++ flent/scripts/ethtool_iterate.sh | 178 ++++++++++++++++++ flent/tests/common.inc | 1 + flent/tests/ethtool_stats.inc | 36 ++++ .../test_data/test-ping-ethtool.flent.gz | Bin 0 -> 20554 bytes unittests/test_plotters.py | 8 + 7 files changed, 334 insertions(+), 25 deletions(-) create mode 100755 flent/scripts/ethtool_iterate.sh create mode 100644 flent/tests/ethtool_stats.inc create mode 100644 unittests/test_data/test-ping-ethtool.flent.gz diff --git a/doc/tests.rst b/doc/tests.rst index 6886a2b5..a58f3508 100644 --- a/doc/tests.rst +++ b/doc/tests.rst @@ -85,34 +85,40 @@ behave. These are: .. envvar:: cpu_stats_hosts .. envvar:: netstat_hosts +.. envvar:: ethtool_hosts .. envvar:: qdisc_stats_hosts .. envvar:: wifi_stats_hosts - These set hostnames to gather statistics from from during the test. The - hostnames are passed to SSH, so can be anything understood by SSH (including - using ``username@host`` syntax, or using hosts defined in ``~/.ssh/config``). - This will attempt to run remote commands on these hosts to gather the - required statistics, so passwordless login has to be enabled for. Multiple - hostnames can be specified, separated by commas. - - CPU stats and netstat output is global to the machine being connected to. The - qdisc and WiFi stats need extra parameters to work. These are - ``qdisc_stats_interfaces``, ``wifi_stats_interfaces`` and - ``wifi_stats_stations``. The two former specify which interfaces to gather - statistics from. These are paired with the hostnames, and so must contain the - same number of elements (also comma-separated) as the ``_hosts`` variables. - To specify multiple interfaces on the same host, duplicate the hostname. The - ``wifi_stats_stations`` parameter specifies MAC addresses of stations to - gather statistics for. This list is the same for all hosts, but only stations - present in debugfs on each host are actually captured. - - The qdisc stats gather statistics output from ``tc -s``, while the WiFi stats - gather statistics from debugfs. These are gathered by looping in a shell - script; however, for better performance, the ``tc_iterate`` and - ``wifistats_iterate`` programmes available in the ``misc/`` directory of the - source code tarball can be installed. On low-powered systems this can be - critical to get correct statistics. The helper programmes are packaged for - LEDE/OpenWrt in the ``flent-tools`` package. + These specify the hostnames from which to gather statistics during the test. + Flent passes the hostnames to SSH; therefore, the hostnames follow all the + traditional SSH hostname declarations, including using the ``username@host`` + syntax or hosts defined in ``~/.ssh/config``. Flent will attempt to run + remote commands on these hosts to gather the required statistics. For this to + work, the hosts must have passwordless login enabled. You can specify + multiple hostnames by separating them by commas. + + While CPU stats, ethtool, and netstat output are global to the machine being + connected to, the qdisc and WiFi stats are more specific and require extra + parameters to work effectively. These parameters, namely + ``qdisc_stats_interfaces``, ``wifi_stats_interfaces``, and + ``wifi_stats_stations``, play a crucial role in specifying which interfaces + to gather statistics from and which MAC addresses of stations to gather + statistics for. Remember, these parameters are paired with the hostnames, so + they must contain the same number of elements as the ``_hosts`` variables. To + specify multiple interfaces on the same host, simply duplicate the hostname. + The ``wifi_stats_stations`` parameter specifies the MAC addresses of stations + for which statistics are to be gathered. This list is the same for all hosts, + but only stations present in debugfs on each host are actually captured. + The ``ethtool_hosts`` parameter lets you finetune which devices and fields to + monitor using the ``ethtool_devices`` and ``ethtool_fields`` parameters. By + default, Flent will monitor all network devices from which it can get values. + However, the ``ethtool_devices`` parameter lets you filter which devices to + monitor. If no fields are specified, Flent will monitor the ``rx_packets`` + and ``tx_packets`` fields unless you specify other fields in the + ``ethtool_fields`` parameter. You can create a comma-separated list of fields + to monitor; however, if you prefix the field with a network device name + separated by a colon, Flent will only monitor that field for that particular + device. Example: ``ethtool_fields=tx_bytes,eth0:rx_packets,eth1:tx_packets`` .. envvar:: ping_hosts .. envvar:: ping_local_binds diff --git a/flent/runners.py b/flent/runners.py index 5a85a042..02a5d1e6 100644 --- a/flent/runners.py +++ b/flent/runners.py @@ -2399,6 +2399,86 @@ def find_binary(self, interval, length, host='localhost'): host=host) +class EthtoolStatsRunner(ProcessRunner): + time_re = re.compile(r"^Time: (?P\d+\.\d+)", re.MULTILINE) + value_re = re.compile(r"^(?P[^:]+):(?P[^:]+): (?P\d+)", re.MULTILINE) + + def __init__(self, interval, length, host='localhost', + devices=None, fields=None, **kwargs): + self.interval = interval + self.length = length + self.host = normalise_host(host) + self.devices = devices + self.fields = fields + super(EthtoolStatsRunner, self).__init__(**kwargs) + + def parse(self, output, error): + results = {} + raw_values = [] + metadata = {} + for part in self.split_stream(output): + timestamp = self.time_re.search(part) + if timestamp is None: + continue + timestamp = float(timestamp.group('timestamp')) + value = self.value_re.search(part) + + if value is None: + continue + + matches = {} + + for m in self.value_re.finditer(part): + ifname = m.group("ifname") + field = m.group("field") + value = m.group("value") + k = f'{ifname}-{field}' + v = float(value) + if k not in matches: + matches[k] = v + else: + matches[k] += v + + for k, v in matches.items(): + if not isinstance(v, float): + continue + if k not in results: + results[k] = [[timestamp, v]] + else: + results[k].append([timestamp, v]) + matches['t'] = timestamp + raw_values.append(matches) + return results, raw_values, metadata + + def check(self): + self.command = self.find_binary(self.interval, + self.length, self.host) + super(EthtoolStatsRunner, self).check() + + def find_binary(self, interval, length, host='localhost'): + script = os.path.join(DATA_DIR, 'scripts', 'ethtool_iterate.sh') + if not os.path.exists(script): + raise RunnerCheckError("Cannot find ethtool_iterate.sh.") + + bash = util.which('bash') + if not bash: + raise RunnerCheckError("Ethtool stats requires a Bash shell.") + + devices = f"-d {self.devices}" if self.devices else "" + fields = f"-f {self.fields}" if self.fields else "" + + return "{bash} {script} -I {interval:.2f} " \ + "-c {count:.0f} -H {host} " \ + "{devices} {fields}".format( + bash=bash, + script=script, + interval=interval, + count=length // interval + 1, + devices=devices, + fields=fields, + host=host) + + class WifiStatsRunner(ProcessRunner): """Runner for getting WiFi debug stats from /sys/kernel/debug. Expects iterations to be separated by '\n---\n and a timestamp to be present in the diff --git a/flent/scripts/ethtool_iterate.sh b/flent/scripts/ethtool_iterate.sh new file mode 100755 index 00000000..69886233 --- /dev/null +++ b/flent/scripts/ethtool_iterate.sh @@ -0,0 +1,178 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0 +# SPDX-FileContributor: Freysteinn Alfredsson +# SPDX-FileType: SOURCE + +count=10 +interval=0.1 +host=localhost +devices="" + +while getopts "c:I:H:d:f:" opt; do + case $opt in + c) count=$OPTARG ;; + I) interval=$OPTARG ;; + H) host=$OPTARG ;; + d) devices=$OPTARG ;; + f) fields=$OPTARG ;; + esac +done + +command_string=$(cat < 0) + devices[device_id_count++] = device; + close(ethtool_cmd); +} + +function add_field(device, field) +{ + if (entries[device, field] != 1) + device_entry_count[device]++; + entries[device, field] = 1; + + for (field_id in fields) + if (field == fields[field_id]) + return; + fields[field_id_count++] = field; +} + +# Startup +BEGIN { + ETHTOOL_CMD_TEMPLATE = "ethtool -S " + DEVICE_LIST_CMD = "ls /sys/class/net"; + device_id_count = 0; + field_id_count = 0; + + # Set default fields if not specified + if (FIELDS == "") { + FIELDS = "rx_packets,tx_packets"; + } + + split(DEVICES, device_args, ","); + split(FIELDS, field_params, ","); + + # Get devices + while (((DEVICE_LIST_CMD) | getline) > 0) { + if (\$1 == "lo") + continue; + # Only add devices specified in DEVICES + if (DEVICES != "") { + for (device_arg_id in device_args) { + if (\$1 == device_args[device_arg_id]) + add_device(\$1); + } + } else { + # Add all devices if DEVICES is not specified + add_device(\$1); + } + } + close(DEVICE_LIST_CMD); + + # Get fields + for (field_param_id in field_params) { + field_count = 0; + split(field_params[field_param_id], field_pair, ":"); + for (pair_id in field_pair) + field_count++; + field = field_pair[field_count]; + + # Add device specific fields + if (field_count == 2) { + device = field_pair[1]; + add_device(device); + add_field(device, field); + continue + } + + # Add global fields to all devices + for (device_id in devices) { + device = devices[device_id]; + add_field(device, field); + } + } +} + +function update_ethtool_stat(device) +{ + FS = ":"; + ethtool_cmd = ETHTOOL_CMD_TEMPLATE " " device; + found_fields = 0; + + while (((ethtool_cmd) | getline) > 0) { + for (field_id in fields) { + field = fields[field_id]; + field_regexp = "^ *" field "\$"; + if (\$1 !~ field_regexp) + continue; + value = \$2; + + entry_value_prev[device, field] = entry_value[device, field]; + entry_value[device, field] = value; + + found_fields++; + if (found_fields == device_entry_count[device]) + break; + } + } + close(ethtool_cmd); +} + +function print_values(device) +{ + for (field_id in fields) { + field = fields[field_id]; + if (entries[device, field] != 1) + continue; + + value_prev = entry_value_prev[device, field]; + value = entry_value[device, field]; + + result = value - value_prev; + print(device ":" field ": " result); + } +} + +# Main loop +BEGIN { + DATE_CMD = "date \"+Time: %s.%N\"" + + # Update initial values + for (device_id in devices) { + device = devices[device_id]; + update_ethtool_stat(device); + } + + # Print interval values + for (i = 0; i < COUNT; i++) { + print("---"); + (DATE_CMD) | getline date; + print(date); + close(DATE_CMD); + + for (device_id in devices) { + device = devices[device_id]; + update_ethtool_stat(device); + print_values(device); + } + system("sleep " INTERVAL); + } +}' +EOF +) + +if [ "$host" == "localhost" ]; then + eval "$command_string" +else + echo "$command_string" | ssh "$host" sh +fi diff --git a/flent/tests/common.inc b/flent/tests/common.inc index 30d42586..5f4372b4 100644 --- a/flent/tests/common.inc +++ b/flent/tests/common.inc @@ -1,5 +1,6 @@ include("qdisc_stats.inc") include("cpu_stats.inc") +include("ethtool_stats.inc") include("wifi_stats.inc") include("ping.inc") include("voip_mixin.inc") diff --git a/flent/tests/ethtool_stats.inc b/flent/tests/ethtool_stats.inc new file mode 100644 index 00000000..dfee14cd --- /dev/null +++ b/flent/tests/ethtool_stats.inc @@ -0,0 +1,36 @@ +## -*- mode: python; coding: utf-8 -*- + +# Mixin include file to add cpu stats to a test + + +ETHTOOL_HOSTS=get_test_parameter('ethtool_hosts', default=[], split=True) +ETHTOOL_DEVICES=get_test_parameter('ethtool_devices', default=None) +ETHTOOL_FIELDS=get_test_parameter('ethtool_fields', default=None) + +for host in ETHTOOL_HOSTS: + DATA_SETS['ethtool_stats_%s' % host] = {'interval': STEP_SIZE, + 'length': TOTAL_LENGTH, + 'host': host, + 'units': 'misc', + 'id': host, + 'devices': ETHTOOL_DEVICES, + 'fields': ETHTOOL_FIELDS, + 'runner': 'ethtool_stats'} + +if ETHTOOL_HOSTS: + PLOTS['ethtool'] = {'description': 'Per ethtool field stats', + 'type': 'timeseries', + 'axis_labels': ['value'], + 'series': [ + {'data': glob('ethtool_stats_*'), + 'raw_key': glob('*', exclude=["t"]), + 'label': 'Ethtool field stats'}, + ]} + + PLOTS['ethtool_box'] = {'description': 'Per ethtool stats (box plot)', + 'type': 'box', + 'parent': 'ethtool'} + + PLOTS['ethtool_bar'] = {'description': 'Per ethtool stats (bar plot)', + 'type': 'bar', + 'parent': 'ethtool'} diff --git a/unittests/test_data/test-ping-ethtool.flent.gz b/unittests/test_data/test-ping-ethtool.flent.gz new file mode 100644 index 0000000000000000000000000000000000000000..105b173126aca42325cd1f0899d39246fc055caa GIT binary patch literal 20554 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 HoTK?aFPzou literal 0 HcmV?d00001 diff --git a/unittests/test_plotters.py b/unittests/test_plotters.py index 60b57e21..64c7a0f4 100644 --- a/unittests/test_plotters.py +++ b/unittests/test_plotters.py @@ -91,6 +91,14 @@ 'tcp_rtt_box_combine', 'tcp_rtt_cdf', )), + 'test-ping-ethtool.flent.gz': set(( + 'tcp_cwnd', + 'tcp_pacing', + 'tcp_rtt', + 'tcp_rtt_bar_combine', + 'tcp_rtt_box_combine', + 'tcp_rtt_cdf', + )), 'test-rrul-icmp.flent.gz': set(( 'tcp_cwnd', 'tcp_delivery_rate',