[RFC net-next 3/6] selftests: drv-net: add a system performance monitor
flat view
From: Stanislav Fomichev <hidden>
Date: 2026-09-16 19:04:17
Subsystem:
kernel selftest framework, networking drivers, networking [general], the rest · Maintainers:
Shuah Khan, Shuah Khan, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Add a context manager which emits per-CPU time, utilization, and NIC drop
counters for both endpoints. Use one persistent remote process for
one-second sampling and enable benchmark metrics while it is active.
Tested on two mlx5 hosts. First local CPU sample:
# # ktap-metric-policy-json: {"aggregation":"busy-core-equivalents","display_label":"Percent of one CPU","display_range":{"max":100.0,"min":0.0},"display_scale":100.0,"name":"cpu.time.usr","summarize":"p90","transform":"rate"}
# # ktap-metric-policy-json: {"aggregation":"busy-core-equivalents","display_label":"Percent of one CPU","display_range":{"max":100.0,"min":0.0},"display_scale":100.0,"name":"cpu.time.sys","summarize":"p90","transform":"rate"}
# # ktap-metric-policy-json: {"aggregation":"busy-core-equivalents","display_label":"Percent of one CPU","display_range":{"max":100.0,"min":0.0},"display_scale":100.0,"name":"cpu.time.sirq","summarize":"p90","transform":"rate"}
# # ktap-metric-policy-json: {"aggregation":"busy-core-equivalents","display_label":"Percent of one CPU","display_range":{"max":100.0,"min":0.0},"name":"cpu.utilization","summarize":"p90"}
# # ktap-metric-json: {"host":"local","name":"cpu.time.usr","shape":"per-cpu","time":1.615669,"value":[0.0,0.0,0.02,0.0,0.0,0.01,0.0,0.0,0.01,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.06,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.02,0.0,0.16,0.0,0.01,0.01,0.0,0.0,0.0,0.01,0.01,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.01,0.0,0.0,0.02,0.01,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.01,0.0,0.0,0.01,0.0,0.03,0.0,0.0,0.0,0.0,0.01,0.02,0.06,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.02,0.01,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.02,0.0,0.0,0.0,0.0,0.02,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.04,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.02,0.0,0.0,0.0,0.0,0.01]}
# # ktap-metric-json: {"host":"local","name":"cpu.time.sys","shape":"per-cpu","time":1.615747,"value":[0.0,0.0,0.0,0.0,0.0,0.02,0.0,0.0,0.0,0.01,0.01,0.0,0.0,0.01,0.0,0.01,0.01,0.0,0.0,0.04,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0.02,0.0,0.0,0.0,0.01,0.13,0.0,0.02,0.01,0.0,0.02,0.01,0.01,0.02,0.0,0.01,0.02,0.0,0.04,0.0,0.0,0.01,0.01,0.01,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0.06,0.02,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.01,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0.02,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.01,0.01,0.02,0.0,0.0,0.0,0.02,0.0,0.0,0.0,0.0,0.0,0.02,0.01,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.15,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.01]}
# # ktap-metric-json: {"host":"local","name":"cpu.time.sirq","shape":"per-cpu","time":1.615814,"value":[0.09,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.08,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.01,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]}
# # ktap-metric-json: {"host":"local","name":"cpu.utilization","shape":"per-cpu","time":1.615881,"value":[9.0909,0.0,2.0202,0.0,0.0,3.0,0.0,0.9901,0.9901,1.0,2.9412,0.0,0.0,0.9901,0.0,1.0,0.9901,0.0,0.0,3.9604,0.0,0.0,0.0,0.0,0.0,0.0,0.9901,0.0,0.0,0.0,0.0,1.0,0.0,6.0,0.0,8.9109,0.0,1.0,2.0,0.9901,0.0,2.0,2.9412,30.0,0.0,3.9604,2.9412,0.0,2.0202,1.0,2.0,2.9703,0.0,1.0101,2.0,0.0,5.0,0.0,0.0,1.0,2.9126,0.9901,0.9901,2.0,0.9901,0.0,0.0,0.9901,0.0,0.0,0.0,0.0,0.0,0.0,0.9901,2.0,0.9901,0.0,0.0,0.9901,0.0,3.9604,0.0,0.0,0.0,0.0,1.0,3.0,12.0,3.0,0.0,0.0,1.0,0.0,0.0,1.9802,0.0,0.0,2.0,0.0,0.9901,0.0,0.9901,0.0,0.0,0.9901,0.0,1.9802,0.9901,0.0,0.0,0.9901,1.0,0.9901,0.9901,0.0,0.0,0.0,0.0,0.9901,0.9901,3.8835,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.9901,0.9901,0.0,0.9901,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.9608,0.0,0.0,0.0,1.9802,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.9802,0.0,0.0,1.0,1.0,2.9412,2.0,0.0,1.0,0.0,2.0202,0.0,0.0,0.0,0.0,0.0,2.9703,0.9901,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.9901,0.0,0.0,0.0,0.0,1.9802,0.0,0.0,0.0,0.9901,0.0,0.0,19.1919,0.0,0.0,0.0,0.0,1.0,0.9901,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.9901,0.9901,1.9802,0.0,0.0,0.0,0.0,1.9802]}
# ok 1 iperf.test_iperf
# # Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
.../drivers/net/hw/lib/py/__init__.py | 11 +-
.../selftests/drivers/net/lib/py/__init__.py | 9 +-
.../drivers/net/lib/py/system_monitor.py | 240 ++++++++++++++++++
.../testing/selftests/net/lib/py/__init__.py | 5 +-
tools/testing/selftests/net/lib/py/ksft.py | 14 +-
5 files changed, 269 insertions(+), 10 deletions(-)
create mode 100644 tools/testing/selftests/drivers/net/lib/py/system_monitor.py
diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
index 349647c2307d..1077930d1677 100644
--- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py@@ -28,11 +28,13 @@ KSFT_DIR = (Path(__file__).parent / "../../../../..").resolve() from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx from net.lib.py import ksft_disruptive, ksft_exit, ksft_metric, \ - ksft_metric_aggregate, ksft_pr, ksft_run, ksft_setup, \ + ksft_metric_aggregate, ksft_metric_reporting, ksft_pr, ksft_run, \ + ksft_setup, \ ksft_variants, KsftNamedVariant from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \ ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none - from drivers.net.lib.py import GenerateTraffic, Remote, Iperf3Runner + from drivers.net.lib.py import GenerateTraffic, Remote, SystemMonitor, \ + Iperf3Runner from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev", "UserNetNS",
@@ -45,14 +47,15 @@ KSFT_DIR = (Path(__file__).parent / "../../../../..").resolve() "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", "ksft_disruptive", "ksft_exit", "ksft_metric", - "ksft_metric_aggregate", "ksft_pr", "ksft_run", + "ksft_metric_aggregate", "ksft_metric_reporting", "ksft_pr", + "ksft_run", "ksft_setup", "ksft_variants", "KsftNamedVariant", "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt", "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt", "ksft_not_none", "ksft_not_none", "NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic", - "Remote", "Iperf3Runner"] + "Remote", "Iperf3Runner", "SystemMonitor"] except ModuleNotFoundError as e: print("Failed importing `net` library from kernel sources") print(str(e))
diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
index afad9d5392ca..d779f48ea6c5 100644
--- a/tools/testing/selftests/drivers/net/lib/py/__init__.py
+++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py@@ -28,7 +28,8 @@ KSFT_DIR = (Path(__file__).parent / "../../../..").resolve() from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx from net.lib.py import ksft_disruptive, ksft_exit, ksft_metric, \ - ksft_metric_aggregate, ksft_pr, ksft_run, ksft_setup, \ + ksft_metric_aggregate, ksft_metric_reporting, ksft_pr, ksft_run, \ + ksft_setup, \ ksft_variants, KsftNamedVariant from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \ ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
@@ -43,7 +44,8 @@ KSFT_DIR = (Path(__file__).parent / "../../../..").resolve() "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", "ksft_disruptive", "ksft_exit", "ksft_metric", - "ksft_metric_aggregate", "ksft_pr", "ksft_run", + "ksft_metric_aggregate", "ksft_metric_reporting", "ksft_pr", + "ksft_run", "ksft_setup", "ksft_variants", "KsftNamedVariant", "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt",
@@ -52,10 +54,11 @@ KSFT_DIR = (Path(__file__).parent / "../../../..").resolve() from .env import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv from .load import GenerateTraffic, Iperf3Runner + from .system_monitor import SystemMonitor from .remote import Remote __all__ += ["NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic", - "Remote", "Iperf3Runner"] + "Remote", "Iperf3Runner", "SystemMonitor"] except ModuleNotFoundError as e: print("Failed importing `net` library from kernel sources") print(str(e))
diff --git a/tools/testing/selftests/drivers/net/lib/py/system_monitor.py b/tools/testing/selftests/drivers/net/lib/py/system_monitor.py
new file mode 100644
index 000000000000..81ded4bea1b9
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/lib/py/system_monitor.py@@ -0,0 +1,240 @@ +# SPDX-License-Identifier: GPL-2.0 + +"""Performance measurement helpers for network driver selftests.""" + +import shlex +import subprocess +import threading +import time + +from lib.py import cmd, ksft_metric, ksft_metric_aggregate, \ + ksft_metric_reporting, ksft_pr + + +class SystemMonitor: + """Emit CPU and NIC metrics for the local and remote test endpoints.""" + + _CPU_FIELDS = ("user", "nice", "sys", "idle", "iowait", "irq", "sirq", + "steal") + _CPU_TIME_METRICS = { + "cpu.time.usr": ("user", "nice"), + "cpu.time.sys": ("sys",), + "cpu.time.sirq": ("sirq",), + } + _NIC_METRICS = { + "rx_dropped": "nic.rx.dropped", + "rx_missed_errors": "nic.rx.missed_errors", + "tx_dropped": "nic.tx.dropped", + } + _SNAPSHOT_END = "__KSFT_SYSTEM_STAT_END__" + _NIC_STATS = "__KSFT_NIC_STATS__" + _CLOCK_TICKS = 100 + _SAMPLE_INTERVAL = 1 + + def __init__(self, env): + self.env = env + self._remote_sampler = None + + @classmethod + def _parse_cpu(cls, text): + stats = {} + for line in text.splitlines(): + fields = line.split() + if not fields or not fields[0].startswith("cpu") or \ + not fields[0][3:].isdigit(): + continue + stats[int(fields[0][3:])] = { + name: int(value) + for name, value in zip(cls._CPU_FIELDS, fields[1:]) + } + return stats + + @classmethod + def _parse_nic(cls, text): + fields = next(line.split() for line in text.splitlines() + if line.startswith(cls._NIC_STATS)) + return {name: int(value) + for name, value in zip(cls._NIC_METRICS, fields[1:])} + + @classmethod + def _read_nic(cls, ifname): + stats = {} + for name in cls._NIC_METRICS: + path = f"/sys/class/net/{ifname}/statistics/{name}" + with open(path, encoding="utf-8") as stat_file: + stats[name] = int(stat_file.read()) + return stats + + @classmethod + def _cpu_deltas(cls, before, after): + return { + cpu: { + field: after[cpu][field] - before[cpu][field] + for field in cls._CPU_FIELDS + } + for cpu in before.keys() & after.keys() + } + + @staticmethod + def _per_cpu_array(values): + if not values: + return None + result = [None] * (max(values) + 1) + for cpu, value in values.items(): + result[cpu] = value + return result + + def _start_remote_sampler(self): + nic_reads = "" + for name in self._NIC_METRICS: + path = shlex.quote( + f"/sys/class/net/{self.env.remote_ifname}/statistics/{name}") + nic_reads += (f"read value < {path} || exit; " + f"printf ' %s' \"$value\"; ") + command = ( + "while :; do cat /proc/stat || exit; " + f"printf '{self._NIC_STATS}'; {nic_reads}" + f"printf '\\n{self._SNAPSHOT_END}\\n'; sleep 1 || exit; done" + ) + self._remote_sampler = cmd(command, host=self.env.remote, + background=True) + + def _read_remote(self): + lines = [] + while True: + line = self._remote_sampler.proc.stdout.readline() + if not line: + status = self._remote_sampler.proc.poll() + raise RuntimeError( + f"Remote system sampler stopped unexpectedly ({status})") + line = line.decode("utf-8", "replace").rstrip("\n") + if line == self._SNAPSHOT_END: + return "\n".join(lines) + lines.append(line) + + def _stop_remote_sampler(self): + sampler = self._remote_sampler + self._remote_sampler = None + if sampler is None: + return + try: + sampler.process(terminate=True, fail=False, timeout=5) + except subprocess.TimeoutExpired: + sampler.proc.kill() + sampler.process(terminate=False, fail=False, timeout=5) + + def _snapshot(self): + remote = self._read_remote() + with open("/proc/stat", encoding="utf-8") as stat_file: + local_cpu = self._parse_cpu(stat_file.read()) + return { + "local": { + "cpu": local_cpu, + "nic": self._read_nic(self.env.ifname), + }, + "remote": { + "cpu": self._parse_cpu(remote), + "nic": self._parse_nic(remote), + }, + } + + def _record_sample(self, snapshots): + for host, current in snapshots.items(): + baseline = self._baseline[host] + previous = self._previous[host] + elapsed = self._cpu_deltas(baseline["cpu"], current["cpu"]) + + for metric, fields in self._CPU_TIME_METRICS.items(): + values = self._per_cpu_array({ + cpu: sum(delta[field] for field in fields) / + self._CLOCK_TICKS + for cpu, delta in elapsed.items() + }) + if values is not None: + ksft_metric(metric, values, shape="per-cpu", host=host) + + interval = self._cpu_deltas(previous["cpu"], current["cpu"]) + utilization = {} + for cpu, delta in interval.items(): + total = sum(delta.values()) + if total > 0: + busy = total - delta["idle"] - delta["iowait"] + utilization[cpu] = round(100 * busy / total, 4) + values = self._per_cpu_array(utilization) + if values is not None: + ksft_metric("cpu.utilization", values, shape="per-cpu", + host=host) + + for counter, metric in self._NIC_METRICS.items(): + value = current["nic"][counter] - baseline["nic"][counter] + ksft_metric(metric, value, shape="scalar", host=host) + + self._previous = snapshots + + def _register_aggregations(self): + for metric in self._CPU_TIME_METRICS: + ksft_metric_aggregate( + metric, summarize="p90", transform="rate", + aggregation="busy-core-equivalents", + display_range={"min": 0, "max": 100}, + display_scale=100, + display_label="Percent of one CPU") + ksft_metric_aggregate( + "cpu.utilization", summarize="p90", + aggregation="busy-core-equivalents", + display_range={"min": 0, "max": 100}, + display_label="Percent of one CPU") + for metric in self._NIC_METRICS.values(): + ksft_metric_aggregate( + metric, summarize="distribution", transform="rate", + display_range={"min": 0}) + + def _sample_loop(self): + next_sample = time.monotonic() + self._SAMPLE_INTERVAL + try: + while not self._stop_event.wait( + max(0, next_sample - time.monotonic())): + self._record_sample(self._snapshot()) + next_sample += self._SAMPLE_INTERVAL + while next_sample <= time.monotonic(): + next_sample += self._SAMPLE_INTERVAL + except Exception as error: + self._sample_error = error + + def __enter__(self): + ksft_metric_reporting(True) + try: + self._register_aggregations() + self._start_remote_sampler() + self._baseline = self._snapshot() + self._previous = self._baseline + for host in self._baseline: + for metric in self._NIC_METRICS.values(): + ksft_metric(metric, 0, shape="scalar", host=host) + + self._stop_event = threading.Event() + self._sample_error = None + self._thread = threading.Thread(target=self._sample_loop, + daemon=True) + self._thread.start() + return self + except Exception: + ksft_metric_reporting(False) + self._stop_remote_sampler() + raise + + def __exit__(self, exc_type, _exc_value, _exc_tb): + ksft_metric_reporting(False) + try: + self._stop_event.set() + self._thread.join() + if self._sample_error is not None: + raise self._sample_error + self._record_sample(self._snapshot()) + except Exception as error: + if exc_type is None: + raise + ksft_pr(f"System metric collection failed: {error}") + finally: + self._stop_remote_sampler() + return False
diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index 21d89ae76b49..9795a83d662a 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py@@ -9,7 +9,7 @@ from .ksft import KsftFailEx, KsftSkipEx, KsftXfailEx, ksft_pr, ksft_eq, \ ksft_ne, ksft_true, ksft_not_none, ksft_in, ksft_not_in, ksft_is, \ ksft_ge, ksft_gt, ksft_lt, ksft_raises, ksft_busy_wait, \ ktap_result, ksft_disruptive, ksft_metric, ksft_metric_aggregate, \ - ksft_setup, ksft_run, \ + ksft_metric_reporting, ksft_setup, ksft_run, \ ksft_exit, ksft_variants, KsftNamedVariant from .netns import NetNS, NetNSEnter, UserNetNS from .nsim import NetdevSim, NetdevSimDev
@@ -26,7 +26,8 @@ __all__ = ["KSRC", "ksft_ne", "ksft_true", "ksft_not_none", "ksft_in", "ksft_not_in", "ksft_is", "ksft_ge", "ksft_gt", "ksft_lt", "ksft_raises", "ksft_busy_wait", "ktap_result", "ksft_disruptive", "ksft_metric", - "ksft_metric_aggregate", "ksft_setup", "ksft_run", + "ksft_metric_aggregate", "ksft_metric_reporting", "ksft_setup", + "ksft_run", "ksft_exit", "ksft_variants", "KsftNamedVariant", "NetNS", "NetNSEnter", "UserNetNS",
diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py
index f0cbc7307117..e44029213e4f 100644
--- a/tools/testing/selftests/net/lib/py/ksft.py
+++ b/tools/testing/selftests/net/lib/py/ksft.py@@ -21,6 +21,7 @@ KSFT_DISRUPTIVE = True KSFT_METRICS = None KSFT_METRICS_START = None KSFT_METRIC_AGGREGATES = None +KSFT_METRIC_REPORTING = 0 KSFT_METRICS_LOCK = threading.Lock()
@@ -99,6 +100,14 @@ KSFT_METRICS_LOCK = threading.Lock() print(pfx, prefixed, **kwargs) +def ksft_metric_reporting(enable): + """Enter or leave a scope which enables benchmark metric producers.""" + global KSFT_METRIC_REPORTING + + with KSFT_METRICS_LOCK: + KSFT_METRIC_REPORTING += 1 if enable else -1 + + def ksft_metric_aggregate(name, summarize="total", *, aggregation=None, transform=None, regression=None, display_range=None, display_scale=None,
@@ -219,6 +228,7 @@ def ksft_metric_aggregate(name, summarize="total", *, aggregation=None, def _ksft_flush_metrics(): global KSFT_METRICS, KSFT_METRICS_START, KSFT_METRIC_AGGREGATES + global KSFT_METRIC_REPORTING with KSFT_METRICS_LOCK: metrics = KSFT_METRICS
@@ -226,6 +236,7 @@ def ksft_metric_aggregate(name, summarize="total", *, aggregation=None, KSFT_METRICS = None KSFT_METRICS_START = None KSFT_METRIC_AGGREGATES = None + KSFT_METRIC_REPORTING = 0 metric_names = {metric["name"] for metric in metrics or []} for name, metadata in (aggregates or {}).items():
@@ -549,7 +560,7 @@ KsftCaseFunction = namedtuple("KsftCaseFunction", totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0} global KSFT_RESULT, KSFT_METRICS, KSFT_METRICS_START - global KSFT_METRIC_AGGREGATES + global KSFT_METRIC_AGGREGATES, KSFT_METRIC_REPORTING if KSFT_RESULT is not None: raise RuntimeError("ksft_run() can't be called multiple times.")
@@ -564,6 +575,7 @@ KsftCaseFunction = namedtuple("KsftCaseFunction", KSFT_METRICS = [] KSFT_METRICS_START = time.monotonic() KSFT_METRIC_AGGREGATES = {} + KSFT_METRIC_REPORTING = 0 cnt += 1 comment = "" cnt_key = ""
--
2.53.0-Meta