xref: /linux/tools/testing/selftests/drivers/net/stats.py (revision 8faabc041a001140564f718dabe37753e88b37fa)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4import errno
5from lib.py import ksft_run, ksft_exit, ksft_pr
6from lib.py import ksft_ge, ksft_eq, ksft_in, ksft_true, ksft_raises, KsftSkipEx, KsftXfailEx
7from lib.py import ksft_disruptive
8from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
9from lib.py import NetDrvEnv
10from lib.py import ip, defer
11
12ethnl = EthtoolFamily()
13netfam = NetdevFamily()
14rtnl = RtnlFamily()
15
16
17def check_pause(cfg) -> None:
18    global ethnl
19
20    try:
21        ethnl.pause_get({"header": {"dev-index": cfg.ifindex}})
22    except NlError as e:
23        if e.error == errno.EOPNOTSUPP:
24            raise KsftXfailEx("pause not supported by the device")
25        raise
26
27    data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex,
28                                       "flags": {'stats'}}})
29    ksft_true(data['stats'], "driver does not report stats")
30
31
32def check_fec(cfg) -> None:
33    global ethnl
34
35    try:
36        ethnl.fec_get({"header": {"dev-index": cfg.ifindex}})
37    except NlError as e:
38        if e.error == errno.EOPNOTSUPP:
39            raise KsftXfailEx("FEC not supported by the device")
40        raise
41
42    data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,
43                                     "flags": {'stats'}}})
44    ksft_true(data['stats'], "driver does not report stats")
45
46
47def pkt_byte_sum(cfg) -> None:
48    global netfam, rtnl
49
50    def get_qstat(test):
51        global netfam
52        stats = netfam.qstats_get({}, dump=True)
53        if stats:
54            for qs in stats:
55                if qs["ifindex"]== test.ifindex:
56                    return qs
57
58    qstat = get_qstat(cfg)
59    if qstat is None:
60        raise KsftSkipEx("qstats not supported by the device")
61
62    for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
63        ksft_in(key, qstat, "Drivers should always report basic keys")
64
65    # Compare stats, rtnl stats and qstats must match,
66    # but the interface may be up, so do a series of dumps
67    # each time the more "recent" stats must be higher or same.
68    def stat_cmp(rstat, qstat):
69        for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
70            if rstat[key] != qstat[key]:
71                return rstat[key] - qstat[key]
72        return 0
73
74    for _ in range(10):
75        rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
76        if stat_cmp(rtstat, qstat) < 0:
77            raise Exception("RTNL stats are lower, fetched later")
78        qstat = get_qstat(cfg)
79        if stat_cmp(rtstat, qstat) > 0:
80            raise Exception("Qstats are lower, fetched later")
81
82
83def qstat_by_ifindex(cfg) -> None:
84    global netfam
85    global rtnl
86
87    # Construct a map ifindex -> [dump, by-index, dump]
88    ifindexes = {}
89    stats = netfam.qstats_get({}, dump=True)
90    for entry in stats:
91        ifindexes[entry['ifindex']] = [entry, None, None]
92
93    for ifindex in ifindexes.keys():
94        entry = netfam.qstats_get({"ifindex": ifindex}, dump=True)
95        ksft_eq(len(entry), 1)
96        ifindexes[entry[0]['ifindex']][1] = entry[0]
97
98    stats = netfam.qstats_get({}, dump=True)
99    for entry in stats:
100        ifindexes[entry['ifindex']][2] = entry
101
102    if len(ifindexes) == 0:
103        raise KsftSkipEx("No ifindex supports qstats")
104
105    # Now make sure the stats match/make sense
106    for ifindex, triple in ifindexes.items():
107        all_keys = triple[0].keys() | triple[1].keys() | triple[2].keys()
108
109        for key in all_keys:
110            ksft_ge(triple[1][key], triple[0][key], comment="bad key: " + key)
111            ksft_ge(triple[2][key], triple[1][key], comment="bad key: " + key)
112
113    # Sanity check the dumps
114    queues = NetdevFamily(recv_size=4096).qstats_get({"scope": "queue"}, dump=True)
115    # Reformat the output into {ifindex: {rx: [id, id, ...], tx: [id, id, ...]}}
116    parsed = {}
117    for entry in queues:
118        ifindex = entry["ifindex"]
119        if ifindex not in parsed:
120            parsed[ifindex] = {"rx":[], "tx": []}
121        parsed[ifindex][entry["queue-type"]].append(entry['queue-id'])
122    # Now, validate
123    for ifindex, queues in parsed.items():
124        for qtype in ['rx', 'tx']:
125            ksft_eq(len(queues[qtype]), len(set(queues[qtype])),
126                    comment="repeated queue keys")
127            ksft_eq(len(queues[qtype]), max(queues[qtype]) + 1,
128                    comment="missing queue keys")
129
130    # Test invalid dumps
131    # 0 is invalid
132    with ksft_raises(NlError) as cm:
133        netfam.qstats_get({"ifindex": 0}, dump=True)
134    ksft_eq(cm.exception.nl_msg.error, -34)
135    ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
136
137    # loopback has no stats
138    with ksft_raises(NlError) as cm:
139        netfam.qstats_get({"ifindex": 1}, dump=True)
140    ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)
141    ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
142
143    # Try to get stats for lowest unused ifindex but not 0
144    devs = rtnl.getlink({}, dump=True)
145    all_ifindexes = set([dev["ifi-index"] for dev in devs])
146    lowest = 2
147    while lowest in all_ifindexes:
148        lowest += 1
149
150    with ksft_raises(NlError) as cm:
151        netfam.qstats_get({"ifindex": lowest}, dump=True)
152    ksft_eq(cm.exception.nl_msg.error, -19)
153    ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
154
155
156@ksft_disruptive
157def check_down(cfg) -> None:
158    try:
159        qstat = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
160    except NlError as e:
161        if e.error == errno.EOPNOTSUPP:
162            raise KsftSkipEx("qstats not supported by the device")
163        raise
164
165    ip(f"link set dev {cfg.dev['ifname']} down")
166    defer(ip, f"link set dev {cfg.dev['ifname']} up")
167
168    qstat2 = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
169    for k, v in qstat.items():
170        ksft_ge(qstat2[k], qstat[k], comment=f"{k} went backwards on device down")
171
172    # exercise per-queue API to make sure that "device down" state
173    # is handled correctly and doesn't crash
174    netfam.qstats_get({"ifindex": cfg.ifindex, "scope": "queue"}, dump=True)
175
176
177def main() -> None:
178    with NetDrvEnv(__file__, queue_count=100) as cfg:
179        ksft_run([check_pause, check_fec, pkt_byte_sum, qstat_by_ifindex,
180                  check_down],
181                 args=(cfg, ))
182    ksft_exit()
183
184
185if __name__ == "__main__":
186    main()
187