187 lines
6.2 KiB
Python
Executable File
187 lines
6.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
import errno
|
|
from lib.py import ksft_run, ksft_exit, ksft_pr
|
|
from lib.py import ksft_ge, ksft_eq, ksft_in, ksft_true, ksft_raises, KsftSkipEx, KsftXfailEx
|
|
from lib.py import ksft_disruptive
|
|
from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
|
|
from lib.py import NetDrvEnv
|
|
from lib.py import ip, defer
|
|
|
|
ethnl = EthtoolFamily()
|
|
netfam = NetdevFamily()
|
|
rtnl = RtnlFamily()
|
|
|
|
|
|
def check_pause(cfg) -> None:
|
|
global ethnl
|
|
|
|
try:
|
|
ethnl.pause_get({"header": {"dev-index": cfg.ifindex}})
|
|
except NlError as e:
|
|
if e.error == errno.EOPNOTSUPP:
|
|
raise KsftXfailEx("pause not supported by the device")
|
|
raise
|
|
|
|
data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex,
|
|
"flags": {'stats'}}})
|
|
ksft_true(data['stats'], "driver does not report stats")
|
|
|
|
|
|
def check_fec(cfg) -> None:
|
|
global ethnl
|
|
|
|
try:
|
|
ethnl.fec_get({"header": {"dev-index": cfg.ifindex}})
|
|
except NlError as e:
|
|
if e.error == errno.EOPNOTSUPP:
|
|
raise KsftXfailEx("FEC not supported by the device")
|
|
raise
|
|
|
|
data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,
|
|
"flags": {'stats'}}})
|
|
ksft_true(data['stats'], "driver does not report stats")
|
|
|
|
|
|
def pkt_byte_sum(cfg) -> None:
|
|
global netfam, rtnl
|
|
|
|
def get_qstat(test):
|
|
global netfam
|
|
stats = netfam.qstats_get({}, dump=True)
|
|
if stats:
|
|
for qs in stats:
|
|
if qs["ifindex"]== test.ifindex:
|
|
return qs
|
|
|
|
qstat = get_qstat(cfg)
|
|
if qstat is None:
|
|
raise KsftSkipEx("qstats not supported by the device")
|
|
|
|
for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
|
|
ksft_in(key, qstat, "Drivers should always report basic keys")
|
|
|
|
# Compare stats, rtnl stats and qstats must match,
|
|
# but the interface may be up, so do a series of dumps
|
|
# each time the more "recent" stats must be higher or same.
|
|
def stat_cmp(rstat, qstat):
|
|
for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
|
|
if rstat[key] != qstat[key]:
|
|
return rstat[key] - qstat[key]
|
|
return 0
|
|
|
|
for _ in range(10):
|
|
rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
|
|
if stat_cmp(rtstat, qstat) < 0:
|
|
raise Exception("RTNL stats are lower, fetched later")
|
|
qstat = get_qstat(cfg)
|
|
if stat_cmp(rtstat, qstat) > 0:
|
|
raise Exception("Qstats are lower, fetched later")
|
|
|
|
|
|
def qstat_by_ifindex(cfg) -> None:
|
|
global netfam
|
|
global rtnl
|
|
|
|
# Construct a map ifindex -> [dump, by-index, dump]
|
|
ifindexes = {}
|
|
stats = netfam.qstats_get({}, dump=True)
|
|
for entry in stats:
|
|
ifindexes[entry['ifindex']] = [entry, None, None]
|
|
|
|
for ifindex in ifindexes.keys():
|
|
entry = netfam.qstats_get({"ifindex": ifindex}, dump=True)
|
|
ksft_eq(len(entry), 1)
|
|
ifindexes[entry[0]['ifindex']][1] = entry[0]
|
|
|
|
stats = netfam.qstats_get({}, dump=True)
|
|
for entry in stats:
|
|
ifindexes[entry['ifindex']][2] = entry
|
|
|
|
if len(ifindexes) == 0:
|
|
raise KsftSkipEx("No ifindex supports qstats")
|
|
|
|
# Now make sure the stats match/make sense
|
|
for ifindex, triple in ifindexes.items():
|
|
all_keys = triple[0].keys() | triple[1].keys() | triple[2].keys()
|
|
|
|
for key in all_keys:
|
|
ksft_ge(triple[1][key], triple[0][key], comment="bad key: " + key)
|
|
ksft_ge(triple[2][key], triple[1][key], comment="bad key: " + key)
|
|
|
|
# Sanity check the dumps
|
|
queues = NetdevFamily(recv_size=4096).qstats_get({"scope": "queue"}, dump=True)
|
|
# Reformat the output into {ifindex: {rx: [id, id, ...], tx: [id, id, ...]}}
|
|
parsed = {}
|
|
for entry in queues:
|
|
ifindex = entry["ifindex"]
|
|
if ifindex not in parsed:
|
|
parsed[ifindex] = {"rx":[], "tx": []}
|
|
parsed[ifindex][entry["queue-type"]].append(entry['queue-id'])
|
|
# Now, validate
|
|
for ifindex, queues in parsed.items():
|
|
for qtype in ['rx', 'tx']:
|
|
ksft_eq(len(queues[qtype]), len(set(queues[qtype])),
|
|
comment="repeated queue keys")
|
|
ksft_eq(len(queues[qtype]), max(queues[qtype]) + 1,
|
|
comment="missing queue keys")
|
|
|
|
# Test invalid dumps
|
|
# 0 is invalid
|
|
with ksft_raises(NlError) as cm:
|
|
netfam.qstats_get({"ifindex": 0}, dump=True)
|
|
ksft_eq(cm.exception.nl_msg.error, -34)
|
|
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
|
|
|
|
# loopback has no stats
|
|
with ksft_raises(NlError) as cm:
|
|
netfam.qstats_get({"ifindex": 1}, dump=True)
|
|
ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)
|
|
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
|
|
|
|
# Try to get stats for lowest unused ifindex but not 0
|
|
devs = rtnl.getlink({}, dump=True)
|
|
all_ifindexes = set([dev["ifi-index"] for dev in devs])
|
|
lowest = 2
|
|
while lowest in all_ifindexes:
|
|
lowest += 1
|
|
|
|
with ksft_raises(NlError) as cm:
|
|
netfam.qstats_get({"ifindex": lowest}, dump=True)
|
|
ksft_eq(cm.exception.nl_msg.error, -19)
|
|
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
|
|
|
|
|
|
@ksft_disruptive
|
|
def check_down(cfg) -> None:
|
|
try:
|
|
qstat = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
|
|
except NlError as e:
|
|
if e.error == errno.EOPNOTSUPP:
|
|
raise KsftSkipEx("qstats not supported by the device")
|
|
raise
|
|
|
|
ip(f"link set dev {cfg.dev['ifname']} down")
|
|
defer(ip, f"link set dev {cfg.dev['ifname']} up")
|
|
|
|
qstat2 = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
|
|
for k, v in qstat.items():
|
|
ksft_ge(qstat2[k], qstat[k], comment=f"{k} went backwards on device down")
|
|
|
|
# exercise per-queue API to make sure that "device down" state
|
|
# is handled correctly and doesn't crash
|
|
netfam.qstats_get({"ifindex": cfg.ifindex, "scope": "queue"}, dump=True)
|
|
|
|
|
|
def main() -> None:
|
|
with NetDrvEnv(__file__, queue_count=100) as cfg:
|
|
ksft_run([check_pause, check_fec, pkt_byte_sum, qstat_by_ifindex,
|
|
check_down],
|
|
args=(cfg, ))
|
|
ksft_exit()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|