Path: blob/master/tools/testing/selftests/drivers/net/stats.py
26288 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 pkt_byte_sum(cfg) -> None:60"""61Check that qstat and interface stats match in value.62"""6364def get_qstat(test):65stats = netfam.qstats_get({}, dump=True)66if stats:67for qs in stats:68if qs["ifindex"]== test.ifindex:69return qs70return None7172qstat = get_qstat(cfg)73if qstat is None:74raise KsftSkipEx("qstats not supported by the device")7576for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:77ksft_in(key, qstat, "Drivers should always report basic keys")7879# Compare stats, rtnl stats and qstats must match,80# but the interface may be up, so do a series of dumps81# each time the more "recent" stats must be higher or same.82def stat_cmp(rstat, qstat):83for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:84if rstat[key] != qstat[key]:85return rstat[key] - qstat[key]86return 08788for _ in range(10):89rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']90if stat_cmp(rtstat, qstat) < 0:91raise KsftFailEx("RTNL stats are lower, fetched later")92qstat = get_qstat(cfg)93if stat_cmp(rtstat, qstat) > 0:94raise KsftFailEx("Qstats are lower, fetched later")959697def qstat_by_ifindex(cfg) -> None:98""" Qstats Netlink API tests - querying by ifindex. """99100# Construct a map ifindex -> [dump, by-index, dump]101ifindexes = {}102stats = netfam.qstats_get({}, dump=True)103for entry in stats:104ifindexes[entry['ifindex']] = [entry, None, None]105106for ifindex in ifindexes:107entry = netfam.qstats_get({"ifindex": ifindex}, dump=True)108ksft_eq(len(entry), 1)109ifindexes[entry[0]['ifindex']][1] = entry[0]110111stats = netfam.qstats_get({}, dump=True)112for entry in stats:113ifindexes[entry['ifindex']][2] = entry114115if len(ifindexes) == 0:116raise KsftSkipEx("No ifindex supports qstats")117118# Now make sure the stats match/make sense119for ifindex, triple in ifindexes.items():120all_keys = triple[0].keys() | triple[1].keys() | triple[2].keys()121122for key in all_keys:123ksft_ge(triple[1][key], triple[0][key], comment="bad key: " + key)124ksft_ge(triple[2][key], triple[1][key], comment="bad key: " + key)125126# Sanity check the dumps127queues = NetdevFamily(recv_size=4096).qstats_get({"scope": "queue"}, dump=True)128# Reformat the output into {ifindex: {rx: [id, id, ...], tx: [id, id, ...]}}129parsed = {}130for entry in queues:131ifindex = entry["ifindex"]132if ifindex not in parsed:133parsed[ifindex] = {"rx":[], "tx": []}134parsed[ifindex][entry["queue-type"]].append(entry['queue-id'])135# Now, validate136for ifindex, queues in parsed.items():137for qtype in ['rx', 'tx']:138ksft_eq(len(queues[qtype]), len(set(queues[qtype])),139comment="repeated queue keys")140ksft_eq(len(queues[qtype]), max(queues[qtype]) + 1,141comment="missing queue keys")142143# Test invalid dumps144# 0 is invalid145with ksft_raises(NlError) as cm:146netfam.qstats_get({"ifindex": 0}, dump=True)147ksft_eq(cm.exception.nl_msg.error, -34)148ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')149150# loopback has no stats151with ksft_raises(NlError) as cm:152netfam.qstats_get({"ifindex": 1}, dump=True)153ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)154ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')155156# Try to get stats for lowest unused ifindex but not 0157devs = rtnl.getlink({}, dump=True)158all_ifindexes = set(dev["ifi-index"] for dev in devs)159lowest = 2160while lowest in all_ifindexes:161lowest += 1162163with ksft_raises(NlError) as cm:164netfam.qstats_get({"ifindex": lowest}, dump=True)165ksft_eq(cm.exception.nl_msg.error, -19)166ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')167168169@ksft_disruptive170def check_down(cfg) -> None:171""" Test statistics (interface and qstat) are not impacted by ifdown """172173try:174qstat = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]175except NlError as e:176if e.error == errno.EOPNOTSUPP:177raise KsftSkipEx("qstats not supported by the device") from e178raise179180ip(f"link set dev {cfg.dev['ifname']} down")181defer(ip, f"link set dev {cfg.dev['ifname']} up")182183qstat2 = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]184for k in qstat:185ksft_ge(qstat2[k], qstat[k], comment=f"{k} went backwards on device down")186187# exercise per-queue API to make sure that "device down" state188# is handled correctly and doesn't crash189netfam.qstats_get({"ifindex": cfg.ifindex, "scope": "queue"}, dump=True)190191192def __run_inf_loop(body):193body = body.strip()194if body[-1] != ';':195body += ';'196197return subprocess.Popen(f"while true; do {body} done", shell=True,198stdout=subprocess.PIPE, stderr=subprocess.PIPE)199200201def __stats_increase_sanely(old, new) -> None:202for k in old.keys():203ksft_ge(new[k], old[k])204ksft_lt(new[k] - old[k], 1 << 31, comment="likely wrapping error")205206207def procfs_hammer(cfg) -> None:208"""209Reading stats via procfs only holds the RCU lock, which is not an exclusive210lock, make sure drivers can handle parallel reads of stats.211"""212one = __run_inf_loop("cat /proc/net/dev")213defer(one.kill)214two = __run_inf_loop("cat /proc/net/dev")215defer(two.kill)216217time.sleep(1)218# Make sure the processes are running219ksft_is(one.poll(), None)220ksft_is(two.poll(), None)221222rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']223time.sleep(2)224rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']225__stats_increase_sanely(rtstat1, rtstat2)226# defers will kill the loops227228229@ksft_disruptive230def procfs_downup_hammer(cfg) -> None:231"""232Reading stats via procfs only holds the RCU lock, drivers often try233to sleep when reading the stats, or don't protect against races.234"""235# Max out the queues, we'll flip between max and 1236channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})237if channels['combined-count'] == 0:238rx_type = 'rx'239else:240rx_type = 'combined'241cur_queue_cnt = channels[f'{rx_type}-count']242max_queue_cnt = channels[f'{rx_type}-max']243244cmd(f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}")245defer(cmd, f"ethtool -L {cfg.ifname} {rx_type} {cur_queue_cnt}")246247# Real test stats248stats = __run_inf_loop("cat /proc/net/dev")249defer(stats.kill)250251ipset = f"ip link set dev {cfg.ifname}"252defer(ip, f"link set dev {cfg.ifname} up")253# The "echo -n 1" lets us count iterations below254updown = f"{ipset} down; sleep 0.05; {ipset} up; sleep 0.05; " + \255f"ethtool -L {cfg.ifname} {rx_type} 1; " + \256f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}; " + \257"echo -n 1"258updown = __run_inf_loop(updown)259kill_updown = defer(updown.kill)260261time.sleep(1)262# Make sure the processes are running263ksft_is(stats.poll(), None)264ksft_is(updown.poll(), None)265266rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']267# We're looking for crashes, give it extra time268time.sleep(9)269rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']270__stats_increase_sanely(rtstat1, rtstat2)271272kill_updown.exec()273stdout, _ = updown.communicate(timeout=5)274ksft_pr("completed up/down cycles:", len(stdout.decode('utf-8')))275276277def main() -> None:278""" Ksft boiler plate main """279280with NetDrvEnv(__file__, queue_count=100) as cfg:281ksft_run([check_pause, check_fec, pkt_byte_sum, qstat_by_ifindex,282check_down, procfs_hammer, procfs_downup_hammer],283args=(cfg, ))284ksft_exit()285286287if __name__ == "__main__":288main()289290291