File Index
Files and directories
Sign in
FILE

load.py

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/drivers/net/lib/py/load.py PYTHON
  1# SPDX-License-Identifier: GPL-2.0
  2
  3import re
  4import time
  5import json
  6
  7from lib.py import ksft_pr, cmd, ip, rand_port, wait_port_listen
  8
  9
 10class Iperf3Runner:
 11    """
 12    Sets up and runs iperf3 traffic.
 13    """
 14    def __init__(self, env, port=None, server_ip=None, client_ip=None):
 15        env.require_cmd("iperf3", local=True, remote=True)
 16        self.env = env
 17        self.port = rand_port() if port is None else port
 18        self.server_ip = server_ip
 19        self.client_ip = client_ip
 20
 21    def _build_server(self):
 22        cmdline = f"iperf3 -s -1 -p {self.port}"
 23        if self.server_ip:
 24            cmdline += f" -B {self.server_ip}"
 25        return cmdline
 26
 27    def _build_client(self, streams, duration, reverse):
 28        host = self.env.addr if self.server_ip is None else self.server_ip
 29        cmdline = f"iperf3 -c {host} -p {self.port} -P {streams} -t {duration} -J"
 30        if self.client_ip:
 31            cmdline += f" -B {self.client_ip}"
 32        if reverse:
 33            cmdline += " --reverse"
 34        return cmdline
 35
 36    def start_server(self):
 37        """
 38        Starts an iperf3 server with optional bind IP.
 39        """
 40        cmdline = self._build_server()
 41        proc = cmd(cmdline, background=True)
 42        wait_port_listen(self.port)
 43        time.sleep(0.1)
 44        return proc
 45
 46    def start_client(self, background=False, streams=1, duration=10, reverse=False):
 47        """
 48        Starts the iperf3 client with the configured options.
 49        """
 50        cmdline = self._build_client(streams, duration, reverse)
 51        return cmd(cmdline, background=background, host=self.env.remote)
 52
 53    def measure_bandwidth(self, reverse=False):
 54        """
 55        Runs an iperf3 measurement and returns the average bandwidth (Gbps).
 56        Discards the first and last few reporting intervals and uses only the
 57        middle part of the run where throughput is typically stable.
 58        """
 59        self.start_server()
 60        result = self.start_client(duration=10, reverse=reverse)
 61
 62        if result.ret != 0:
 63            raise RuntimeError("iperf3 failed to run successfully")
 64        try:
 65            out = json.loads(result.stdout)
 66        except json.JSONDecodeError as exc:
 67            raise ValueError("Failed to parse iperf3 JSON output") from exc
 68
 69        intervals = out.get("intervals", [])
 70        samples = [i["sum"]["bits_per_second"] / 1e9 for i in intervals]
 71        if len(samples) < 10:
 72            raise ValueError(f"iperf3 returned too few intervals: {len(samples)}")
 73        # Discard potentially unstable first and last 3 seconds.
 74        stable = samples[3:-3]
 75
 76        avg = sum(stable) / len(stable)
 77
 78        return avg
 79
 80
 81class GenerateTraffic:
 82    def __init__(self, env, port=None):
 83        self.env = env
 84        self.runner = Iperf3Runner(env, port)
 85
 86        self._iperf_server = self.runner.start_server()
 87        self._iperf_client = self.runner.start_client(background=True, streams=16, duration=86400)
 88
 89        # Wait for traffic to ramp up
 90        if not self._wait_pkts(pps=1000):
 91            self.stop(verbose=True)
 92            raise Exception("iperf3 traffic did not ramp up")
 93
 94    def _wait_pkts(self, pkt_cnt=None, pps=None):
 95        """
 96        Wait until we've seen pkt_cnt or until traffic ramps up to pps.
 97        Only one of pkt_cnt or pss can be specified.
 98        """
 99        pkt_start = ip("-s link show dev " + self.env.ifname, json=True)[0]["stats64"]["rx"]["packets"]
100        for _ in range(50):
101            time.sleep(0.1)
102            pkt_now = ip("-s link show dev " + self.env.ifname, json=True)[0]["stats64"]["rx"]["packets"]
103            if pps:
104                if pkt_now - pkt_start > pps / 10:
105                    return True
106                pkt_start = pkt_now
107            elif pkt_cnt:
108                if pkt_now - pkt_start > pkt_cnt:
109                    return True
110        return False
111
112    def wait_pkts_and_stop(self, pkt_cnt):
113        failed = not self._wait_pkts(pkt_cnt=pkt_cnt)
114        self.stop(verbose=failed)
115
116    def stop(self, verbose=None):
117        self._iperf_client.process(terminate=True)
118        if verbose:
119            ksft_pr(">> Client:")
120            ksft_pr(self._iperf_client.stdout)
121            ksft_pr(self._iperf_client.stderr)
122        self._iperf_server.process(terminate=True)
123        if verbose:
124            ksft_pr(">> Server:")
125            ksft_pr(self._iperf_server.stdout)
126            ksft_pr(self._iperf_server.stderr)
127        self._wait_client_stopped()
128
129    def _wait_client_stopped(self, sleep=0.005, timeout=5):
130        end = time.monotonic() + timeout
131
132        live_port_pattern = re.compile(fr":{self.runner.port:04X} 0[^6] ")
133
134        while time.monotonic() < end:
135            data = cmd("cat /proc/net/tcp*", host=self.env.remote).stdout
136            if not live_port_pattern.search(data):
137                return
138            time.sleep(sleep)
139        raise Exception(f"Waiting for client to stop timed out after {timeout}s")
~/ Read-only file preview.