Path: blob/master/tools/testing/selftests/drivers/net/stats.py
29270 views
#!/usr/bin/env python31# SPDX-License-Identifier: GPL-2.023"""4Tests related to standard netdevice statistics.5"""67import errno8import subprocess9import time10from lib.py import ksft_run, ksft_exit, ksft_pr11from lib.py import ksft_ge, ksft_eq, ksft_is, ksft_in, ksft_lt, ksft_true, ksft_raises12from lib.py import KsftSkipEx, KsftFailEx13from lib.py import ksft_disruptive14from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError15from lib.py import NetDrvEnv16from lib.py import cmd, ip, defer1718ethnl = EthtoolFamily()19netfam = NetdevFamily()20rtnl = RtnlFamily()212223def check_pause(cfg) -> None:24"""25Check that drivers which support Pause config also report standard26pause stats.27"""2829try:30ethnl.pause_get({"header": {"dev-index": cfg.ifindex}})31except NlError as e:32if e.error == errno.EOPNOTSUPP:33raise KsftSkipEx("pause not supported by the device") from e34raise3536data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex,37"flags": {'stats'}}})38ksft_true(data['stats'], "driver does not report stats")394041def check_fec(cfg) -> None:42"""43Check that drivers which support FEC config also report standard44FEC stats.45"""4647try:48ethnl.fec_get({"header": {"dev-index": cfg.ifindex}})49except NlError as e:50if e.error == errno.EOPNOTSUPP:51raise KsftSkipEx("FEC not supported by the device") from e52raise5354data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,55"flags": {'stats'}}})56ksft_true(data['stats'], "driver does not report stats")575859def check_fec_hist(cfg) -> None:60"""61Check that drivers which support FEC histogram statistics report62reasonable values.63"""6465try:66data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,67"flags": {'stats'}}})68except NlError as e:69if e.error == errno.EOPNOTSUPP:70raise KsftSkipEx("FEC not supported by the device") from e71raise72if 'stats' not in data:73raise KsftSkipEx("FEC stats not supported by the device")74if 'hist' not in data['stats']:75raise KsftSkipEx("FEC histogram not supported by the device")7677hist = data['stats']['hist']78for fec_bin in hist:79for key in ['bin-low', 'bin-high', 'bin-val']:80ksft_in(key, fec_bin,81"Drivers should always report FEC bin range and value")82ksft_ge(fec_bin['bin-high'], fec_bin['bin-low'],83"FEC bin range should be valid")84if 'bin-val-per-lane' in fec_bin:85ksft_eq(sum(fec_bin['bin-val-per-lane']), fec_bin['bin-val'],86"FEC bin value should be equal to sum of per-plane values")878889def pkt_byte_sum(cfg) -> None:90"""91Check that qstat and interface stats match in value.92"""9394def get_qstat(test):95stats = netfam.qstats_get({}, dump=True)96if stats:97for qs in stats:98if qs["ifindex"]== test.ifindex:99return qs100return None101102qstat = get_qstat(cfg)103if qstat is None:104raise KsftSkipEx("qstats not supported by the device")105106for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:107ksft_in(key, qstat, "Drivers should always report basic keys")108109# Compare stats, rtnl stats and qstats must match,110# but the interface may be up, so do a series of dumps111# each time the more "recent" stats must be higher or same.112def stat_cmp(rstat, qstat):113for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:114if rstat[key] != qstat[key]:115return rstat[key] - qstat[key]116return 0117118for _ in range(10):119rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']120if stat_cmp(rtstat, qstat) < 0:121raise KsftFailEx("RTNL stats are lower, fetched later")122qstat = get_qstat(cfg)123if stat_cmp(rtstat, qstat) > 0:124raise KsftFailEx("Qstats are lower, fetched later")125126127def qstat_by_ifindex(cfg) -> None:128""" Qstats Netlink API tests - querying by ifindex. """129130# Construct a map ifindex -> [dump, by-index, dump]131ifindexes = {}132stats = netfam.qstats_get({}, dump=True)133for entry in stats:134ifindexes[entry['ifindex']] = [entry, None, None]135136for ifindex in ifindexes:137entry = netfam.qstats_get({"ifindex": ifindex}, dump=True)138ksft_eq(len(entry), 1)139ifindexes[entry[0]['ifindex']][1] = entry[0]140141stats = netfam.qstats_get({}, dump=True)142for entry in stats:143ifindexes[entry['ifindex']][2] = entry144145if len(ifindexes) == 0:146raise KsftSkipEx("No ifindex supports qstats")147148# Now make sure the stats match/make sense149for ifindex, triple in ifindexes.items():150all_keys = triple[0].keys() | triple[1].keys() | triple[2].keys()151152for key in all_keys:153ksft_ge(triple[1][key], triple[0][key], comment="bad key: " + key)154ksft_ge(triple[2][key], triple[1][key], comment="bad key: " + key)155156# Sanity check the dumps157queues = NetdevFamily(recv_size=4096).qstats_get({"scope": "queue"}, dump=True)158# Reformat the output into {ifindex: {rx: [id, id, ...], tx: [id, id, ...]}}159parsed = {}160for entry in queues:161ifindex = entry["ifindex"]162if ifindex not in parsed:163parsed[ifindex] = {"rx":[], "tx": []}164parsed[ifindex][entry["queue-type"]].append(entry['queue-id'])165# Now, validate166for ifindex, queues in parsed.items():167for qtype in ['rx', 'tx']:168ksft_eq(len(queues[qtype]), len(set(queues[qtype])),169comment="repeated queue keys")170ksft_eq(len(queues[qtype]), max(queues[qtype]) + 1,171comment="missing queue keys")172173# Test invalid dumps174# 0 is invalid175with ksft_raises(NlError) as cm:176netfam.qstats_get({"ifindex": 0}, dump=True)177ksft_eq(cm.exception.nl_msg.error, -34)178ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')179180# loopback has no stats181with ksft_raises(NlError) as cm:182netfam.qstats_get({"ifindex": 1}, dump=True)183ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)184ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')185186# Try to get stats for lowest unused ifindex but not 0187devs = rtnl.getlink({}, dump=True)188all_ifindexes = set(dev["ifi-index"] for dev in devs)189lowest = 2190while lowest in all_ifindexes:191lowest += 1192193with ksft_raises(NlError) as cm:194netfam.qstats_get({"ifindex": lowest}, dump=True)195ksft_eq(cm.exception.nl_msg.error, -19)196ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')197198199@ksft_disruptive200def check_down(cfg) -> None:201""" Test statistics (interface and qstat) are not impacted by ifdown """202203try:204qstat = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]205except NlError as e:206if e.error == errno.EOPNOTSUPP:207raise KsftSkipEx("qstats not supported by the device") from e208raise209210ip(f"link set dev {cfg.dev['ifname']} down")211defer(ip, f"link set dev {cfg.dev['ifname']} up")212213qstat2 = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]214for k in qstat:215ksft_ge(qstat2[k], qstat[k], comment=f"{k} went backwards on device down")216217# exercise per-queue API to make sure that "device down" state218# is handled correctly and doesn't crash219netfam.qstats_get({"ifindex": cfg.ifindex, "scope": "queue"}, dump=True)220221222def __run_inf_loop(body):223body = body.strip()224if body[-1] != ';':225body += ';'226227return subprocess.Popen(f"while true; do {body} done", shell=True,228stdout=subprocess.PIPE, stderr=subprocess.PIPE)229230231def __stats_increase_sanely(old, new) -> None:232for k in old.keys():233ksft_ge(new[k], old[k])234ksft_lt(new[k] - old[k], 1 << 31, comment="likely wrapping error")235236237def procfs_hammer(cfg) -> None:238"""239Reading stats via procfs only holds the RCU lock, which is not an exclusive240lock, make sure drivers can handle parallel reads of stats.241"""242one = __run_inf_loop("cat /proc/net/dev")243defer(one.kill)244two = __run_inf_loop("cat /proc/net/dev")245defer(two.kill)246247time.sleep(1)248# Make sure the processes are running249ksft_is(one.poll(), None)250ksft_is(two.poll(), None)251252rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']253time.sleep(2)254rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']255__stats_increase_sanely(rtstat1, rtstat2)256# defers will kill the loops257258259@ksft_disruptive260def procfs_downup_hammer(cfg) -> None:261"""262Reading stats via procfs only holds the RCU lock, drivers often try263to sleep when reading the stats, or don't protect against races.264"""265# Max out the queues, we'll flip between max and 1266channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})267if channels['combined-count'] == 0:268rx_type = 'rx'269else:270rx_type = 'combined'271cur_queue_cnt = channels[f'{rx_type}-count']272max_queue_cnt = channels[f'{rx_type}-max']273274cmd(f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}")275defer(cmd, f"ethtool -L {cfg.ifname} {rx_type} {cur_queue_cnt}")276277# Real test stats278stats = __run_inf_loop("cat /proc/net/dev")279defer(stats.kill)280281ipset = f"ip link set dev {cfg.ifname}"282defer(ip, f"link set dev {cfg.ifname} up")283# The "echo -n 1" lets us count iterations below284updown = f"{ipset} down; sleep 0.05; {ipset} up; sleep 0.05; " + \285f"ethtool -L {cfg.ifname} {rx_type} 1; " + \286f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}; " + \287"echo -n 1"288updown = __run_inf_loop(updown)289kill_updown = defer(updown.kill)290291time.sleep(1)292# Make sure the processes are running293ksft_is(stats.poll(), None)294ksft_is(updown.poll(), None)295296rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']297# We're looking for crashes, give it extra time298time.sleep(9)299rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']300__stats_increase_sanely(rtstat1, rtstat2)301302kill_updown.exec()303stdout, _ = updown.communicate(timeout=5)304ksft_pr("completed up/down cycles:", len(stdout.decode('utf-8')))305306307def main() -> None:308""" Ksft boiler plate main """309310with NetDrvEnv(__file__, queue_count=100) as cfg:311ksft_run([check_pause, check_fec, check_fec_hist, pkt_byte_sum,312qstat_by_ifindex, check_down, procfs_hammer,313procfs_downup_hammer],314args=(cfg, ))315ksft_exit()316317318if __name__ == "__main__":319main()320321322