1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4""" 5Tests related to standard netdevice statistics. 6""" 7 8import errno 9import subprocess 10import time 11from lib.py import ksft_run, ksft_exit, ksft_pr 12from lib.py import ksft_ge, ksft_eq, ksft_is, ksft_in, ksft_lt, ksft_true, ksft_raises 13from lib.py import KsftSkipEx, KsftFailEx 14from lib.py import ksft_disruptive 15from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError 16from lib.py import NetDrvEnv 17from lib.py import cmd, ip, defer 18 19ethnl = EthtoolFamily() 20netfam = NetdevFamily() 21rtnl = RtnlFamily() 22 23 24def check_pause(cfg) -> None: 25 """ 26 Check that drivers which support Pause config also report standard 27 pause stats. 28 """ 29 30 try: 31 ethnl.pause_get({"header": {"dev-index": cfg.ifindex}}) 32 except NlError as e: 33 if e.error == errno.EOPNOTSUPP: 34 raise KsftSkipEx("pause not supported by the device") from e 35 raise 36 37 data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex, 38 "flags": {'stats'}}}) 39 ksft_true(data['stats'], "driver does not report stats") 40 41 42def check_fec(cfg) -> None: 43 """ 44 Check that drivers which support FEC config also report standard 45 FEC stats. 46 """ 47 48 try: 49 ethnl.fec_get({"header": {"dev-index": cfg.ifindex}}) 50 except NlError as e: 51 if e.error == errno.EOPNOTSUPP: 52 raise KsftSkipEx("FEC not supported by the device") from e 53 raise 54 55 data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex, 56 "flags": {'stats'}}}) 57 ksft_true(data['stats'], "driver does not report stats") 58 59 60def pkt_byte_sum(cfg) -> None: 61 """ 62 Check that qstat and interface stats match in value. 63 """ 64 65 def get_qstat(test): 66 stats = netfam.qstats_get({}, dump=True) 67 if stats: 68 for qs in stats: 69 if qs["ifindex"]== test.ifindex: 70 return qs 71 return None 72 73 qstat = get_qstat(cfg) 74 if qstat is None: 75 raise KsftSkipEx("qstats not supported by the device") 76 77 for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']: 78 ksft_in(key, qstat, "Drivers should always report basic keys") 79 80 # Compare stats, rtnl stats and qstats must match, 81 # but the interface may be up, so do a series of dumps 82 # each time the more "recent" stats must be higher or same. 83 def stat_cmp(rstat, qstat): 84 for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']: 85 if rstat[key] != qstat[key]: 86 return rstat[key] - qstat[key] 87 return 0 88 89 for _ in range(10): 90 rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64'] 91 if stat_cmp(rtstat, qstat) < 0: 92 raise KsftFailEx("RTNL stats are lower, fetched later") 93 qstat = get_qstat(cfg) 94 if stat_cmp(rtstat, qstat) > 0: 95 raise KsftFailEx("Qstats are lower, fetched later") 96 97 98def qstat_by_ifindex(cfg) -> None: 99 """ Qstats Netlink API tests - querying by ifindex. """ 100 101 # Construct a map ifindex -> [dump, by-index, dump] 102 ifindexes = {} 103 stats = netfam.qstats_get({}, dump=True) 104 for entry in stats: 105 ifindexes[entry['ifindex']] = [entry, None, None] 106 107 for ifindex in ifindexes: 108 entry = netfam.qstats_get({"ifindex": ifindex}, dump=True) 109 ksft_eq(len(entry), 1) 110 ifindexes[entry[0]['ifindex']][1] = entry[0] 111 112 stats = netfam.qstats_get({}, dump=True) 113 for entry in stats: 114 ifindexes[entry['ifindex']][2] = entry 115 116 if len(ifindexes) == 0: 117 raise KsftSkipEx("No ifindex supports qstats") 118 119 # Now make sure the stats match/make sense 120 for ifindex, triple in ifindexes.items(): 121 all_keys = triple[0].keys() | triple[1].keys() | triple[2].keys() 122 123 for key in all_keys: 124 ksft_ge(triple[1][key], triple[0][key], comment="bad key: " + key) 125 ksft_ge(triple[2][key], triple[1][key], comment="bad key: " + key) 126 127 # Sanity check the dumps 128 queues = NetdevFamily(recv_size=4096).qstats_get({"scope": "queue"}, dump=True) 129 # Reformat the output into {ifindex: {rx: [id, id, ...], tx: [id, id, ...]}} 130 parsed = {} 131 for entry in queues: 132 ifindex = entry["ifindex"] 133 if ifindex not in parsed: 134 parsed[ifindex] = {"rx":[], "tx": []} 135 parsed[ifindex][entry["queue-type"]].append(entry['queue-id']) 136 # Now, validate 137 for ifindex, queues in parsed.items(): 138 for qtype in ['rx', 'tx']: 139 ksft_eq(len(queues[qtype]), len(set(queues[qtype])), 140 comment="repeated queue keys") 141 ksft_eq(len(queues[qtype]), max(queues[qtype]) + 1, 142 comment="missing queue keys") 143 144 # Test invalid dumps 145 # 0 is invalid 146 with ksft_raises(NlError) as cm: 147 netfam.qstats_get({"ifindex": 0}, dump=True) 148 ksft_eq(cm.exception.nl_msg.error, -34) 149 ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex') 150 151 # loopback has no stats 152 with ksft_raises(NlError) as cm: 153 netfam.qstats_get({"ifindex": 1}, dump=True) 154 ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP) 155 ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex') 156 157 # Try to get stats for lowest unused ifindex but not 0 158 devs = rtnl.getlink({}, dump=True) 159 all_ifindexes = set(dev["ifi-index"] for dev in devs) 160 lowest = 2 161 while lowest in all_ifindexes: 162 lowest += 1 163 164 with ksft_raises(NlError) as cm: 165 netfam.qstats_get({"ifindex": lowest}, dump=True) 166 ksft_eq(cm.exception.nl_msg.error, -19) 167 ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex') 168 169 170@ksft_disruptive 171def check_down(cfg) -> None: 172 """ Test statistics (interface and qstat) are not impacted by ifdown """ 173 174 try: 175 qstat = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] 176 except NlError as e: 177 if e.error == errno.EOPNOTSUPP: 178 raise KsftSkipEx("qstats not supported by the device") from e 179 raise 180 181 ip(f"link set dev {cfg.dev['ifname']} down") 182 defer(ip, f"link set dev {cfg.dev['ifname']} up") 183 184 qstat2 = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] 185 for k in qstat: 186 ksft_ge(qstat2[k], qstat[k], comment=f"{k} went backwards on device down") 187 188 # exercise per-queue API to make sure that "device down" state 189 # is handled correctly and doesn't crash 190 netfam.qstats_get({"ifindex": cfg.ifindex, "scope": "queue"}, dump=True) 191 192 193def __run_inf_loop(body): 194 body = body.strip() 195 if body[-1] != ';': 196 body += ';' 197 198 return subprocess.Popen(f"while true; do {body} done", shell=True, 199 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 200 201 202def __stats_increase_sanely(old, new) -> None: 203 for k in old.keys(): 204 ksft_ge(new[k], old[k]) 205 ksft_lt(new[k] - old[k], 1 << 31, comment="likely wrapping error") 206 207 208def procfs_hammer(cfg) -> None: 209 """ 210 Reading stats via procfs only holds the RCU lock, which is not an exclusive 211 lock, make sure drivers can handle parallel reads of stats. 212 """ 213 one = __run_inf_loop("cat /proc/net/dev") 214 defer(one.kill) 215 two = __run_inf_loop("cat /proc/net/dev") 216 defer(two.kill) 217 218 time.sleep(1) 219 # Make sure the processes are running 220 ksft_is(one.poll(), None) 221 ksft_is(two.poll(), None) 222 223 rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64'] 224 time.sleep(2) 225 rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64'] 226 __stats_increase_sanely(rtstat1, rtstat2) 227 # defers will kill the loops 228 229 230@ksft_disruptive 231def procfs_downup_hammer(cfg) -> None: 232 """ 233 Reading stats via procfs only holds the RCU lock, drivers often try 234 to sleep when reading the stats, or don't protect against races. 235 """ 236 # Max out the queues, we'll flip between max and 1 237 channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 238 if channels['combined-count'] == 0: 239 rx_type = 'rx' 240 else: 241 rx_type = 'combined' 242 cur_queue_cnt = channels[f'{rx_type}-count'] 243 max_queue_cnt = channels[f'{rx_type}-max'] 244 245 cmd(f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}") 246 defer(cmd, f"ethtool -L {cfg.ifname} {rx_type} {cur_queue_cnt}") 247 248 # Real test stats 249 stats = __run_inf_loop("cat /proc/net/dev") 250 defer(stats.kill) 251 252 ipset = f"ip link set dev {cfg.ifname}" 253 defer(ip, f"link set dev {cfg.ifname} up") 254 # The "echo -n 1" lets us count iterations below 255 updown = f"{ipset} down; sleep 0.05; {ipset} up; sleep 0.05; " + \ 256 f"ethtool -L {cfg.ifname} {rx_type} 1; " + \ 257 f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}; " + \ 258 "echo -n 1" 259 updown = __run_inf_loop(updown) 260 kill_updown = defer(updown.kill) 261 262 time.sleep(1) 263 # Make sure the processes are running 264 ksft_is(stats.poll(), None) 265 ksft_is(updown.poll(), None) 266 267 rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64'] 268 # We're looking for crashes, give it extra time 269 time.sleep(9) 270 rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64'] 271 __stats_increase_sanely(rtstat1, rtstat2) 272 273 kill_updown.exec() 274 stdout, _ = updown.communicate(timeout=5) 275 ksft_pr("completed up/down cycles:", len(stdout.decode('utf-8'))) 276 277 278def main() -> None: 279 """ Ksft boiler plate main """ 280 281 with NetDrvEnv(__file__, queue_count=100) as cfg: 282 ksft_run([check_pause, check_fec, pkt_byte_sum, qstat_by_ifindex, 283 check_down, procfs_hammer, procfs_downup_hammer], 284 args=(cfg, )) 285 ksft_exit() 286 287 288if __name__ == "__main__": 289 main() 290