diff options
author | David Ahern <dsahern@kernel.org> | 2022-04-27 20:13:35 -0600 |
---|---|---|
committer | David Ahern <dsahern@kernel.org> | 2022-04-27 20:13:35 -0600 |
commit | 39888ed09935661a453f9c02244d5fd44da8e317 (patch) | |
tree | 8e79c85b7ccc97f756c0f490fea4be509d082d77 | |
parent | 38ae12d39632c164835a4b2954344189a58af830 (diff) | |
parent | b28eb051b3213ca60167dda751e06d63a1d7bc8a (diff) | |
download | iproute2-39888ed09935661a453f9c02244d5fd44da8e317.tar.gz |
Merge branch 'ip-stats' into next
Petr Machata says:
====================
A new rtnetlink message, RTM_SETSTATS, has been added recently in kernel
commit ca0a53dcec94 ("Merge branch 'net-hw-counters-for-soft-devices'").
At the same time, RTM_GETSTATS has been around for a while. The users of
this API are spread in a couple different places: "ip link xstats" reads
stats from the IFLA_STATS_LINK_XSTATS and _XSTATS_SLAVE subgroups, "ip
link afstats" then reads IFLA_STATS_AF_SPEC.
Finally, to read IFLA_STATS_LINK_OFFLOAD_XSTATS, one would use ifstats.
This does not seem to be a good fit for IFLA_OFFLOAD_XSTATS_HW_S_INFO in
particular.
The obvious place to expose all these offload stats suites would be
under a new link subcommand "ip link offload_xstats", or similar, which
would then have syntax for both showing stats and setting them.
However, this looks like a good opportunity to introduce a new top-level
command, "ip stats", that would be the go-to place to access anything
backed by RTM_GETSTATS and RTM_SETSTATS.
This patchset therefore does the following:
- It adds the new "stats" infrastructure
- It adds specifically the ability to toggle and show the suites that
were recently added to Linux, IFLA_OFFLOAD_XSTATS_HW_S_INFO and
IFLA_OFFLOAD_XSTATS_L3_STATS.
- It adds support to dump IFLA_OFFLOAD_XSTATS_CPU_HIT, which was not
available under "ip" at all.
- Does all this in a way that is easy to extend for new stats suites.
The patchset proceeds as follows:
- Patches #1 and #2 lay some groundwork and tweak existing code.
- Patch #3 adds the shell of the new "ip stats" command.
- Patch #4 adds "ip stats set" and the ability to toggle l3_stats in
particular.
- Patch #5 adds "ip stats show", but no actual stats suites.
- Patches #6-#9 add support for showing individual stats suites:
respectively, IFLA_STATS_LINK_64, IFLA_OFFLOAD_XSTATS_CPU_HIT,
IFLA_OFFLOAD_XSTATS_HW_S_INFO and IFLA_OFFLOAD_XSTATS_L3_STATS.
- Patch #10 adds support for monitoring stats events to "ip monitor".
- Patch #11 adds man page verbiage for the above.
The plan is to contribute support for afstats and xstats in a follow-up
patch set.
====================
Signed-off-by: David Ahern <dsahern@kernel.org>
-rw-r--r-- | bridge/vlan.c | 6 | ||||
-rw-r--r-- | include/libnetlink.h | 11 | ||||
-rw-r--r-- | ip/Makefile | 3 | ||||
-rw-r--r-- | ip/ip.c | 1 | ||||
-rw-r--r-- | ip/ip_common.h | 31 | ||||
-rw-r--r-- | ip/ipaddress.c | 33 | ||||
-rw-r--r-- | ip/iplink.c | 3 | ||||
-rw-r--r-- | ip/iplink_xstats.c | 3 | ||||
-rw-r--r-- | ip/ipmonitor.c | 16 | ||||
-rw-r--r-- | ip/ipstats.c | 1241 | ||||
-rw-r--r-- | lib/libnetlink.c | 19 | ||||
-rw-r--r-- | man/man8/ip-monitor.8 | 2 | ||||
-rw-r--r-- | man/man8/ip-stats.8 | 160 | ||||
-rw-r--r-- | man/man8/ip.8 | 7 | ||||
-rw-r--r-- | misc/ifstat.c | 2 |
15 files changed, 1512 insertions, 26 deletions
diff --git a/bridge/vlan.c b/bridge/vlan.c index 8300f353f..390a2037d 100644 --- a/bridge/vlan.c +++ b/bridge/vlan.c @@ -1179,7 +1179,8 @@ static int vlan_show(int argc, char **argv, int subject) __u32 filt_mask; filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS); - if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) { + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { perror("Cannot send dump request"); exit(1); } @@ -1194,7 +1195,8 @@ static int vlan_show(int argc, char **argv, int subject) } filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS_SLAVE); - if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) { + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { perror("Cannot send slave dump request"); exit(1); } diff --git a/include/libnetlink.h b/include/libnetlink.h index 9e4cc101a..372c37062 100644 --- a/include/libnetlink.h +++ b/include/libnetlink.h @@ -37,6 +37,12 @@ struct nlmsg_chain { struct nlmsg_list *tail; }; +struct ipstats_req { + struct nlmsghdr nlh; + struct if_stats_msg ifsm; + char buf[128]; +}; + extern int rcvbuf; int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions) @@ -88,7 +94,10 @@ int rtnl_fdb_linkdump_req_filter_fn(struct rtnl_handle *rth, int rtnl_nsiddump_req_filter_fn(struct rtnl_handle *rth, int family, req_filter_fn_t filter_fn) __attribute__((warn_unused_result)); -int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask) +int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask, + int (*filter_fn)(struct ipstats_req *req, + void *data), + void *filter_data) __attribute__((warn_unused_result)); int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) diff --git a/ip/Makefile b/ip/Makefile index e06a7c846..6c2e07204 100644 --- a/ip/Makefile +++ b/ip/Makefile @@ -12,7 +12,8 @@ IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \ iplink_geneve.o iplink_vrf.o iproute_lwtunnel.o ipmacsec.o ipila.o \ ipvrf.o iplink_xstats.o ipseg6.o iplink_netdevsim.o iplink_rmnet.o \ ipnexthop.o ipmptcp.o iplink_bareudp.o iplink_wwan.o ipioam6.o \ - iplink_amt.o iplink_batadv.o iplink_gtp.o iplink_virt_wifi.o + iplink_amt.o iplink_batadv.o iplink_gtp.o iplink_virt_wifi.o \ + ipstats.o RTMONOBJ=rtmon.o @@ -123,6 +123,7 @@ static const struct cmd { { "mptcp", do_mptcp }, { "ioam", do_ioam6 }, { "help", do_help }, + { "stats", do_ipstats }, { 0 } }; diff --git a/ip/ip_common.h b/ip/ip_common.h index ea04c8ff3..9eeeb3877 100644 --- a/ip/ip_common.h +++ b/ip/ip_common.h @@ -57,6 +57,7 @@ int print_nexthop_bucket(struct nlmsghdr *n, void *arg); void netns_map_init(void); void netns_nsid_socket_init(void); int print_nsid(struct nlmsghdr *n, void *arg); +int ipstats_print(struct nlmsghdr *n, void *arg); char *get_name_from_nsid(int nsid); int get_netnsid_from_name(const char *name); int set_netnsid_from_name(const char *name, int nsid); @@ -90,6 +91,7 @@ int do_seg6(int argc, char **argv); int do_ipnh(int argc, char **argv); int do_mptcp(int argc, char **argv); int do_ioam6(int argc, char **argv); +int do_ipstats(int argc, char **argv); int iplink_get(char *name, __u32 filt_mask); int iplink_ifla_xstats(int argc, char **argv); @@ -157,6 +159,32 @@ void xdp_dump(FILE *fp, struct rtattr *tb, bool link, bool details); __u32 ipvrf_get_table(const char *name); int name_is_vrf(const char *name); +/* ipstats.c */ +enum ipstats_stat_desc_kind { + IPSTATS_STAT_DESC_KIND_LEAF, + IPSTATS_STAT_DESC_KIND_GROUP, +}; + +struct ipstats_stat_dump_filters; +struct ipstats_stat_show_attrs; + +struct ipstats_stat_desc { + const char *name; + enum ipstats_stat_desc_kind kind; + union { + struct { + const struct ipstats_stat_desc **subs; + size_t nsubs; + }; + struct { + void (*pack)(struct ipstats_stat_dump_filters *filters, + const struct ipstats_stat_desc *desc); + int (*show)(struct ipstats_stat_show_attrs *attrs, + const struct ipstats_stat_desc *desc); + }; + }; +}; + #ifndef INFINITY_LIFE_TIME #define INFINITY_LIFE_TIME 0xFFFFFFFFU #endif @@ -171,4 +199,7 @@ void print_rta_ifidx(FILE *fp, __u32 ifidx, const char *prefix); void __print_rta_gateway(FILE *fp, unsigned char family, const char *gateway); void print_rta_gateway(FILE *fp, unsigned char family, const struct rtattr *rta); +void size_columns(unsigned int cols[], unsigned int n, ...); +void print_stats64(FILE *fp, struct rtnl_link_stats64 *s, + const struct rtattr *carrier_changes, const char *what); #endif /* _IP_COMMON_H_ */ diff --git a/ip/ipaddress.c b/ip/ipaddress.c index a80996efd..17341d287 100644 --- a/ip/ipaddress.c +++ b/ip/ipaddress.c @@ -546,7 +546,7 @@ static void print_vfinfo(FILE *fp, struct ifinfomsg *ifi, struct rtattr *vfinfo) print_vf_stats64(fp, vf[IFLA_VF_STATS]); } -static void size_columns(unsigned int cols[], unsigned int n, ...) +void size_columns(unsigned int cols[], unsigned int n, ...) { unsigned int i, len; uint64_t val, powi; @@ -680,10 +680,10 @@ static void print_vf_stats64(FILE *fp, struct rtattr *vfstats) } } -static void __print_link_stats(FILE *fp, struct rtattr *tb[]) +void print_stats64(FILE *fp, struct rtnl_link_stats64 *s, + const struct rtattr *carrier_changes, + const char *what) { - const struct rtattr *carrier_changes = tb[IFLA_CARRIER_CHANGES]; - struct rtnl_link_stats64 _s, *s = &_s; unsigned int cols[] = { strlen("*X errors:"), strlen("packets"), @@ -693,14 +693,10 @@ static void __print_link_stats(FILE *fp, struct rtattr *tb[]) strlen("overrun"), strlen("compressed"), }; - int ret; - - ret = get_rtnl_link_stats_rta(s, tb); - if (ret < 0) - return; if (is_json_context()) { - open_json_object((ret == sizeof(*s)) ? "stats64" : "stats"); + if (what) + open_json_object(what); /* RX stats */ open_json_object("rx"); @@ -771,7 +767,8 @@ static void __print_link_stats(FILE *fp, struct rtattr *tb[]) } close_json_object(); - close_json_object(); + if (what) + close_json_object(); } else { size_columns(cols, ARRAY_SIZE(cols), s->rx_bytes, s->rx_packets, s->rx_errors, @@ -870,6 +867,20 @@ static void __print_link_stats(FILE *fp, struct rtattr *tb[]) } } +static void __print_link_stats(FILE *fp, struct rtattr *tb[]) +{ + const struct rtattr *carrier_changes = tb[IFLA_CARRIER_CHANGES]; + struct rtnl_link_stats64 _s, *s = &_s; + int ret; + + ret = get_rtnl_link_stats_rta(s, tb); + if (ret < 0) + return; + + print_stats64(fp, s, carrier_changes, + (ret == sizeof(*s)) ? "stats64" : "stats"); +} + static void print_link_stats(FILE *fp, struct nlmsghdr *n) { struct ifinfomsg *ifi = NLMSG_DATA(n); diff --git a/ip/iplink.c b/ip/iplink.c index dc76a12b5..23eb6c6e2 100644 --- a/ip/iplink.c +++ b/ip/iplink.c @@ -1644,7 +1644,8 @@ static int iplink_afstats(int argc, char **argv) } } - if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) { + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { perror("Cannont send dump request"); return 1; } diff --git a/ip/iplink_xstats.c b/ip/iplink_xstats.c index c64e68856..1d180b0bd 100644 --- a/ip/iplink_xstats.c +++ b/ip/iplink_xstats.c @@ -65,7 +65,8 @@ int iplink_ifla_xstats(int argc, char **argv) else filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS); - if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) { + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { perror("Cannont send dump request"); return -1; } diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c index 0badda4e7..a4326d2ae 100644 --- a/ip/ipmonitor.c +++ b/ip/ipmonitor.c @@ -34,7 +34,7 @@ static void usage(void) "Usage: ip monitor [ all | OBJECTS ] [ FILE ] [ label ] [ all-nsid ]\n" " [ dev DEVICE ]\n" "OBJECTS := address | link | mroute | neigh | netconf |\n" - " nexthop | nsid | prefix | route | rule\n" + " nexthop | nsid | prefix | route | rule | stats\n" "FILE := file FILENAME\n"); exit(-1); } @@ -158,6 +158,11 @@ static int accept_msg(struct rtnl_ctrl_data *ctrl, print_nsid(n, arg); return 0; + case RTM_NEWSTATS: + print_headers(fp, "[STATS]", ctrl); + ipstats_print(n, arg); + return 0; + case NLMSG_ERROR: case NLMSG_NOOP: case NLMSG_DONE: @@ -185,6 +190,7 @@ int do_ipmonitor(int argc, char **argv) int lprefix = 0; int lneigh = 0; int lnetconf = 0; + int lstats = 0; int lrule = 0; int lnsid = 0; int ifindex = 0; @@ -253,6 +259,9 @@ int do_ipmonitor(int argc, char **argv) } else if (matches(*argv, "nexthop") == 0) { lnexthop = 1; groups = 0; + } else if (strcmp(*argv, "stats") == 0) { + lstats = 1; + groups = 0; } else if (strcmp(*argv, "all") == 0) { prefix_banner = 1; } else if (matches(*argv, "all-nsid") == 0) { @@ -349,6 +358,11 @@ int do_ipmonitor(int argc, char **argv) exit(1); } + if (lstats && rtnl_add_nl_group(&rth, RTNLGRP_STATS) < 0) { + fprintf(stderr, "Failed to add stats group to list\n"); + exit(1); + } + if (listen_all_nsid && rtnl_listen_all_nsid(&rth) < 0) exit(1); diff --git a/ip/ipstats.c b/ip/ipstats.c new file mode 100644 index 000000000..39ddca01f --- /dev/null +++ b/ip/ipstats.c @@ -0,0 +1,1241 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include <assert.h> +#include <errno.h> + +#include "utils.h" +#include "ip_common.h" + +struct ipstats_stat_dump_filters { + /* mask[0] filters outer attributes. Then individual nests have their + * filtering mask at the index of the nested attribute. + */ + __u32 mask[IFLA_STATS_MAX + 1]; +}; + +static void +ipstats_stat_desc_enable_bit(struct ipstats_stat_dump_filters *filters, + unsigned int group, unsigned int subgroup) +{ + filters->mask[0] |= IFLA_STATS_FILTER_BIT(group); + if (subgroup) + filters->mask[group] |= IFLA_STATS_FILTER_BIT(subgroup); +} + +struct ipstats_stat_show_attrs { + struct if_stats_msg *ifsm; + int len; + + /* tbs[0] contains top-level attribute table. Then individual nests have + * their attribute tables at the index of the nested attribute. + */ + struct rtattr **tbs[IFLA_STATS_MAX + 1]; +}; + +static const char *const ipstats_levels[] = { + "group", + "subgroup", +}; + +enum { + IPSTATS_LEVELS_COUNT = ARRAY_SIZE(ipstats_levels), +}; + +struct ipstats_sel { + const char *sel[IPSTATS_LEVELS_COUNT]; +}; + +struct ipstats_stat_enabled_one { + const struct ipstats_stat_desc *desc; + struct ipstats_sel sel; +}; + +struct ipstats_stat_enabled { + struct ipstats_stat_enabled_one *enabled; + size_t nenabled; +}; + +static const unsigned int ipstats_stat_ifla_max[] = { + [0] = IFLA_STATS_MAX, + [IFLA_STATS_LINK_XSTATS] = LINK_XSTATS_TYPE_MAX, + [IFLA_STATS_LINK_XSTATS_SLAVE] = LINK_XSTATS_TYPE_MAX, + [IFLA_STATS_LINK_OFFLOAD_XSTATS] = IFLA_OFFLOAD_XSTATS_MAX, + [IFLA_STATS_AF_SPEC] = AF_MAX - 1, +}; + +static_assert(ARRAY_SIZE(ipstats_stat_ifla_max) == IFLA_STATS_MAX + 1, + "An IFLA_STATS attribute is missing from the ifla_max table"); + +static int +ipstats_stat_show_attrs_alloc_tb(struct ipstats_stat_show_attrs *attrs, + unsigned int group) +{ + unsigned int ifla_max; + int err; + + assert(group < ARRAY_SIZE(ipstats_stat_ifla_max)); + assert(group < ARRAY_SIZE(attrs->tbs)); + ifla_max = ipstats_stat_ifla_max[group]; + assert(ifla_max != 0); + + if (attrs->tbs[group]) + return 0; + + attrs->tbs[group] = calloc(ifla_max + 1, sizeof(*attrs->tbs[group])); + if (attrs->tbs[group] == NULL) + return -ENOMEM; + + if (group == 0) + err = parse_rtattr(attrs->tbs[group], ifla_max, + IFLA_STATS_RTA(attrs->ifsm), attrs->len); + else + err = parse_rtattr_nested(attrs->tbs[group], ifla_max, + attrs->tbs[0][group]); + + if (err != 0) { + free(attrs->tbs[group]); + attrs->tbs[group] = NULL; + } + return err; +} + +static const struct rtattr * +ipstats_stat_show_get_attr(struct ipstats_stat_show_attrs *attrs, + int group, int subgroup, int *err) +{ + int tmp_err; + + if (err == NULL) + err = &tmp_err; + + *err = 0; + if (subgroup == 0) + return attrs->tbs[0][group]; + + if (attrs->tbs[0][group] == NULL) + return NULL; + + *err = ipstats_stat_show_attrs_alloc_tb(attrs, group); + if (*err != 0) + return NULL; + + return attrs->tbs[group][subgroup]; +} + +static void +ipstats_stat_show_attrs_free(struct ipstats_stat_show_attrs *attrs) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(attrs->tbs); i++) + free(attrs->tbs[i]); +} + +#define IPSTATS_RTA_PAYLOAD(TYPE, AT) \ + ({ \ + const struct rtattr *__at = (AT); \ + TYPE *__ret = NULL; \ + \ + if (__at != NULL && \ + __at->rta_len - RTA_LENGTH(0) >= sizeof(TYPE)) \ + __ret = RTA_DATA(__at); \ + __ret; \ + }) + +static int ipstats_show_64(struct ipstats_stat_show_attrs *attrs, + unsigned int group, unsigned int subgroup) +{ + struct rtnl_link_stats64 *stats; + const struct rtattr *at; + int err; + + at = ipstats_stat_show_get_attr(attrs, group, subgroup, &err); + if (at == NULL) + return err; + + stats = IPSTATS_RTA_PAYLOAD(struct rtnl_link_stats64, at); + if (stats == NULL) { + fprintf(stderr, "Error: attribute payload too short"); + return -EINVAL; + } + + open_json_object("stats64"); + print_stats64(stdout, stats, NULL, NULL); + close_json_object(); + return 0; +} + +static void print_hw_stats64(FILE *fp, struct rtnl_hw_stats64 *s) +{ + unsigned int cols[] = { + strlen("*X: bytes"), + strlen("packets"), + strlen("errors"), + strlen("dropped"), + strlen("overrun"), + }; + + if (is_json_context()) { + /* RX stats */ + open_json_object("rx"); + print_u64(PRINT_JSON, "bytes", NULL, s->rx_bytes); + print_u64(PRINT_JSON, "packets", NULL, s->rx_packets); + print_u64(PRINT_JSON, "errors", NULL, s->rx_errors); + print_u64(PRINT_JSON, "dropped", NULL, s->rx_dropped); + print_u64(PRINT_JSON, "multicast", NULL, s->multicast); + close_json_object(); + + /* TX stats */ + open_json_object("tx"); + print_u64(PRINT_JSON, "bytes", NULL, s->tx_bytes); + print_u64(PRINT_JSON, "packets", NULL, s->tx_packets); + print_u64(PRINT_JSON, "errors", NULL, s->tx_errors); + print_u64(PRINT_JSON, "dropped", NULL, s->tx_dropped); + close_json_object(); + } else { + size_columns(cols, ARRAY_SIZE(cols), + s->rx_bytes, s->rx_packets, s->rx_errors, + s->rx_dropped, s->multicast); + size_columns(cols, ARRAY_SIZE(cols), + s->tx_bytes, s->tx_packets, s->tx_errors, + s->tx_dropped, 0); + + /* RX stats */ + fprintf(fp, " RX: %*s %*s %*s %*s %*s%s", + cols[0] - 4, "bytes", cols[1], "packets", + cols[2], "errors", cols[3], "dropped", + cols[4], "mcast", _SL_); + + fprintf(fp, " "); + print_num(fp, cols[0], s->rx_bytes); + print_num(fp, cols[1], s->rx_packets); + print_num(fp, cols[2], s->rx_errors); + print_num(fp, cols[3], s->rx_dropped); + print_num(fp, cols[4], s->multicast); + fprintf(fp, "%s", _SL_); + + /* TX stats */ + fprintf(fp, " TX: %*s %*s %*s %*s%s", + cols[0] - 4, "bytes", cols[1], "packets", + cols[2], "errors", cols[3], "dropped", _SL_); + + fprintf(fp, " "); + print_num(fp, cols[0], s->tx_bytes); + print_num(fp, cols[1], s->tx_packets); + print_num(fp, cols[2], s->tx_errors); + print_num(fp, cols[3], s->tx_dropped); + } +} + +static int ipstats_show_hw64(const struct rtattr *at) +{ + struct rtnl_hw_stats64 *stats; + + stats = IPSTATS_RTA_PAYLOAD(struct rtnl_hw_stats64, at); + if (stats == NULL) { + fprintf(stderr, "Error: attribute payload too short"); + return -EINVAL; + } + + print_hw_stats64(stdout, stats); + return 0; +} + +enum ipstats_maybe_on_off { + IPSTATS_MOO_OFF = -1, + IPSTATS_MOO_INVALID, + IPSTATS_MOO_ON, +}; + +static bool ipstats_moo_to_bool(enum ipstats_maybe_on_off moo) +{ + assert(moo != IPSTATS_MOO_INVALID); + return moo + 1; +} + +static int ipstats_print_moo(enum output_type t, const char *key, + const char *fmt, enum ipstats_maybe_on_off moo) +{ + if (!moo) + return 0; + return print_on_off(t, key, fmt, ipstats_moo_to_bool(moo)); +} + +struct ipstats_hw_s_info_one { + enum ipstats_maybe_on_off request; + enum ipstats_maybe_on_off used; +}; + +enum ipstats_hw_s_info_idx { + IPSTATS_HW_S_INFO_IDX_L3_STATS, + IPSTATS_HW_S_INFO_IDX_COUNT +}; + +static const char *const ipstats_hw_s_info_name[] = { + "l3_stats", +}; + +static_assert(ARRAY_SIZE(ipstats_hw_s_info_name) == + IPSTATS_HW_S_INFO_IDX_COUNT, + "mismatch: enum ipstats_hw_s_info_idx x ipstats_hw_s_info_name"); + +struct ipstats_hw_s_info { + /* Indexed by enum ipstats_hw_s_info_idx. */ + struct ipstats_hw_s_info_one *infos[IPSTATS_HW_S_INFO_IDX_COUNT]; +}; + +static enum ipstats_maybe_on_off ipstats_dissect_01(int value, const char *what) +{ + switch (value) { + case 0: + return IPSTATS_MOO_OFF; + case 1: + return IPSTATS_MOO_ON; + default: + fprintf(stderr, "Invalid value for %s: expected 0 or 1, got %d.\n", + what, value); + return IPSTATS_MOO_INVALID; + } +} + +static int ipstats_dissect_hw_s_info_one(const struct rtattr *at, + struct ipstats_hw_s_info_one *p_hwsio, + const char *what) +{ + int attr_id_request = IFLA_OFFLOAD_XSTATS_HW_S_INFO_REQUEST; + struct rtattr *tb[IFLA_OFFLOAD_XSTATS_HW_S_INFO_MAX + 1]; + int attr_id_used = IFLA_OFFLOAD_XSTATS_HW_S_INFO_USED; + struct ipstats_hw_s_info_one hwsio = {}; + int err; + int v; + + err = parse_rtattr_nested(tb, IFLA_OFFLOAD_XSTATS_HW_S_INFO_MAX, at); + if (err) + return err; + + if (tb[attr_id_request]) { + v = rta_getattr_u8(tb[attr_id_request]); + hwsio.request = ipstats_dissect_01(v, "request"); + + /* This has to be present & valid. */ + if (!hwsio.request) + return -EINVAL; + } + + if (tb[attr_id_used]) { + v = rta_getattr_u8(tb[attr_id_used]); + hwsio.used = ipstats_dissect_01(v, "used"); + } + + *p_hwsio = hwsio; + return 0; +} + +static int ipstats_dissect_hw_s_info(const struct rtattr *at, + struct ipstats_hw_s_info *hwsi) +{ + struct rtattr *tb[IFLA_OFFLOAD_XSTATS_MAX + 1]; + int attr_id_l3 = IFLA_OFFLOAD_XSTATS_L3_STATS; + struct ipstats_hw_s_info_one *hwsio = NULL; + int err; + + err = parse_rtattr_nested(tb, IFLA_OFFLOAD_XSTATS_MAX, at); + if (err) + return err; + + *hwsi = (struct ipstats_hw_s_info){}; + + if (tb[attr_id_l3]) { + hwsio = malloc(sizeof(*hwsio)); + if (!hwsio) { + err = -ENOMEM; + goto out; + } + + err = ipstats_dissect_hw_s_info_one(tb[attr_id_l3], hwsio, "l3"); + if (err) + goto out; + + hwsi->infos[IPSTATS_HW_S_INFO_IDX_L3_STATS] = hwsio; + hwsio = NULL; + } + + return 0; + +out: + free(hwsio); + return err; +} + +static void ipstats_fini_hw_s_info(struct ipstats_hw_s_info *hwsi) +{ + int i; + + for (i = 0; i < IPSTATS_HW_S_INFO_IDX_COUNT; i++) + free(hwsi->infos[i]); +} + +static void +__ipstats_show_hw_s_info_one(const struct ipstats_hw_s_info_one *hwsio) +{ + if (hwsio == NULL) + return; + + ipstats_print_moo(PRINT_ANY, "request", " %s", hwsio->request); + ipstats_print_moo(PRINT_ANY, "used", " used %s", hwsio->used); +} + +static void +ipstats_show_hw_s_info_one(const struct ipstats_hw_s_info *hwsi, + enum ipstats_hw_s_info_idx idx) +{ + const struct ipstats_hw_s_info_one *hwsio = hwsi->infos[idx]; + const char *name = ipstats_hw_s_info_name[idx]; + + if (hwsio == NULL) + return; + + print_string(PRINT_FP, NULL, " %s", name); + open_json_object(name); + __ipstats_show_hw_s_info_one(hwsio); + close_json_object(); +} + +static int __ipstats_show_hw_s_info(const struct rtattr *at) +{ + struct ipstats_hw_s_info hwsi = {}; + int err; + + err = ipstats_dissect_hw_s_info(at, &hwsi); + if (err) + return err; + + open_json_object("info"); + ipstats_show_hw_s_info_one(&hwsi, IPSTATS_HW_S_INFO_IDX_L3_STATS); + close_json_object(); + + ipstats_fini_hw_s_info(&hwsi); + return 0; +} + +static int ipstats_show_hw_s_info(struct ipstats_stat_show_attrs *attrs, + unsigned int group, unsigned int subgroup) +{ + const struct rtattr *at; + int err; + + at = ipstats_stat_show_get_attr(attrs, group, subgroup, &err); + if (at == NULL) + return err; + + print_nl(); + return __ipstats_show_hw_s_info(at); +} + +static int __ipstats_show_hw_stats(const struct rtattr *at_hwsi, + const struct rtattr *at_stats, + enum ipstats_hw_s_info_idx idx) +{ + int err = 0; + + if (at_hwsi != NULL) { + struct ipstats_hw_s_info hwsi = {}; + + err = ipstats_dissect_hw_s_info(at_hwsi, &hwsi); + if (err) + return err; + + open_json_object("info"); + __ipstats_show_hw_s_info_one(hwsi.infos[idx]); + close_json_object(); + + ipstats_fini_hw_s_info(&hwsi); + } + + if (at_stats != NULL) { + print_nl(); + open_json_object("stats64"); + err = ipstats_show_hw64(at_stats); + close_json_object(); + } + + return err; +} + +static int ipstats_show_hw_stats(struct ipstats_stat_show_attrs *attrs, + unsigned int group, + unsigned int hw_s_info, + unsigned int hw_stats, + enum ipstats_hw_s_info_idx idx) +{ + const struct rtattr *at_stats; + const struct rtattr *at_hwsi; + int err = 0; + + at_hwsi = ipstats_stat_show_get_attr(attrs, group, hw_s_info, &err); + if (at_hwsi == NULL) + return err; + + at_stats = ipstats_stat_show_get_attr(attrs, group, hw_stats, &err); + if (at_stats == NULL && err != 0) + return err; + + return __ipstats_show_hw_stats(at_hwsi, at_stats, idx); +} + +static void +ipstats_stat_desc_pack_cpu_hit(struct ipstats_stat_dump_filters *filters, + const struct ipstats_stat_desc *desc) +{ + ipstats_stat_desc_enable_bit(filters, + IFLA_STATS_LINK_OFFLOAD_XSTATS, + IFLA_OFFLOAD_XSTATS_CPU_HIT); +} + +static int ipstats_stat_desc_show_cpu_hit(struct ipstats_stat_show_attrs *attrs, + const struct ipstats_stat_desc *desc) +{ + print_nl(); + return ipstats_show_64(attrs, + IFLA_STATS_LINK_OFFLOAD_XSTATS, + IFLA_OFFLOAD_XSTATS_CPU_HIT); +} + +static const struct ipstats_stat_desc ipstats_stat_desc_offload_cpu_hit = { + .name = "cpu_hit", + .kind = IPSTATS_STAT_DESC_KIND_LEAF, + .pack = &ipstats_stat_desc_pack_cpu_hit, + .show = &ipstats_stat_desc_show_cpu_hit, +}; + +static void +ipstats_stat_desc_pack_hw_stats_info(struct ipstats_stat_dump_filters *filters, + const struct ipstats_stat_desc *desc) +{ + ipstats_stat_desc_enable_bit(filters, + IFLA_STATS_LINK_OFFLOAD_XSTATS, + IFLA_OFFLOAD_XSTATS_HW_S_INFO); +} + +static int +ipstats_stat_desc_show_hw_stats_info(struct ipstats_stat_show_attrs *attrs, + const struct ipstats_stat_desc *desc) +{ + return ipstats_show_hw_s_info(attrs, + IFLA_STATS_LINK_OFFLOAD_XSTATS, + IFLA_OFFLOAD_XSTATS_HW_S_INFO); +} + +static const struct ipstats_stat_desc ipstats_stat_desc_offload_hw_s_info = { + .name = "hw_stats_info", + .kind = IPSTATS_STAT_DESC_KIND_LEAF, + .pack = &ipstats_stat_desc_pack_hw_stats_info, + .show = &ipstats_stat_desc_show_hw_stats_info, +}; + +static void +ipstats_stat_desc_pack_l3_stats(struct ipstats_stat_dump_filters *filters, + const struct ipstats_stat_desc *desc) +{ + ipstats_stat_desc_enable_bit(filters, + IFLA_STATS_LINK_OFFLOAD_XSTATS, + IFLA_OFFLOAD_XSTATS_L3_STATS); + ipstats_stat_desc_enable_bit(filters, + IFLA_STATS_LINK_OFFLOAD_XSTATS, + IFLA_OFFLOAD_XSTATS_HW_S_INFO); +} + +static int +ipstats_stat_desc_show_l3_stats(struct ipstats_stat_show_attrs *attrs, + const struct ipstats_stat_desc *desc) +{ + return ipstats_show_hw_stats(attrs, + IFLA_STATS_LINK_OFFLOAD_XSTATS, + IFLA_OFFLOAD_XSTATS_HW_S_INFO, + IFLA_OFFLOAD_XSTATS_L3_STATS, + IPSTATS_HW_S_INFO_IDX_L3_STATS); +} + +static const struct ipstats_stat_desc ipstats_stat_desc_offload_l3_stats = { + .name = "l3_stats", + .kind = IPSTATS_STAT_DESC_KIND_LEAF, + .pack = &ipstats_stat_desc_pack_l3_stats, + .show = &ipstats_stat_desc_show_l3_stats, +}; + +static const struct ipstats_stat_desc *ipstats_stat_desc_offload_subs[] = { + &ipstats_stat_desc_offload_cpu_hit, + &ipstats_stat_desc_offload_hw_s_info, + &ipstats_stat_desc_offload_l3_stats, +}; + +static const struct ipstats_stat_desc ipstats_stat_desc_offload_group = { + .name = "offload", + .kind = IPSTATS_STAT_DESC_KIND_GROUP, + .subs = ipstats_stat_desc_offload_subs, + .nsubs = ARRAY_SIZE(ipstats_stat_desc_offload_subs), +}; + +static void +ipstats_stat_desc_pack_link(struct ipstats_stat_dump_filters *filters, + const struct ipstats_stat_desc *desc) +{ + ipstats_stat_desc_enable_bit(filters, + IFLA_STATS_LINK_64, 0); +} + +static int +ipstats_stat_desc_show_link(struct ipstats_stat_show_attrs *attrs, + const struct ipstats_stat_desc *desc) +{ + print_nl(); + return ipstats_show_64(attrs, IFLA_STATS_LINK_64, 0); +} + +static const struct ipstats_stat_desc ipstats_stat_desc_toplev_link = { + .name = "link", + .kind = IPSTATS_STAT_DESC_KIND_LEAF, + .pack = &ipstats_stat_desc_pack_link, + .show = &ipstats_stat_desc_show_link, +}; + +static const struct ipstats_stat_desc *ipstats_stat_desc_toplev_subs[] = { + &ipstats_stat_desc_toplev_link, + &ipstats_stat_desc_offload_group, +}; + +static const struct ipstats_stat_desc ipstats_stat_desc_toplev_group = { + .name = "top-level", + .kind = IPSTATS_STAT_DESC_KIND_GROUP, + .subs = ipstats_stat_desc_toplev_subs, + .nsubs = ARRAY_SIZE(ipstats_stat_desc_toplev_subs), +}; + +static void ipstats_show_group(const struct ipstats_sel *sel) +{ + int i; + + for (i = 0; i < IPSTATS_LEVELS_COUNT; i++) { + if (sel->sel[i] == NULL) + break; + print_string(PRINT_JSON, ipstats_levels[i], NULL, sel->sel[i]); + print_string(PRINT_FP, NULL, " %s ", ipstats_levels[i]); + print_string(PRINT_FP, NULL, "%s", sel->sel[i]); + } +} + +static int +ipstats_process_ifsm(struct nlmsghdr *answer, + struct ipstats_stat_enabled *enabled) +{ + struct ipstats_stat_show_attrs show_attrs = {}; + const char *dev; + int err = 0; + int i; + + show_attrs.ifsm = NLMSG_DATA(answer); + show_attrs.len = (answer->nlmsg_len - + NLMSG_LENGTH(sizeof(*show_attrs.ifsm))); + if (show_attrs.len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", show_attrs.len); + return -EINVAL; + } + + err = ipstats_stat_show_attrs_alloc_tb(&show_attrs, 0); + if (err != 0) { + fprintf(stderr, "Error parsing netlink answer: %s\n", + strerror(err)); + return err; + } + + dev = ll_index_to_name(show_attrs.ifsm->ifindex); + + for (i = 0; i < enabled->nenabled; i++) { + const struct ipstats_stat_desc *desc = enabled->enabled[i].desc; + + open_json_object(NULL); + print_int(PRINT_ANY, "ifindex", "%d:", + show_attrs.ifsm->ifindex); + print_color_string(PRINT_ANY, COLOR_IFNAME, + "ifname", " %s:", dev); + ipstats_show_group(&enabled->enabled[i].sel); + err = desc->show(&show_attrs, desc); + if (err != 0) + goto out; + close_json_object(); + print_nl(); + } + +out: + ipstats_stat_show_attrs_free(&show_attrs); + return err; +} + +static bool +ipstats_req_should_filter_at(struct ipstats_stat_dump_filters *filters, int at) +{ + return filters->mask[at] != 0 && + filters->mask[at] != (1 << ipstats_stat_ifla_max[at]) - 1; +} + +static int +ipstats_req_add_filters(struct ipstats_req *req, void *data) +{ + struct ipstats_stat_dump_filters dump_filters = {}; + struct ipstats_stat_enabled *enabled = data; + bool get_filters = false; + int i; + + for (i = 0; i < enabled->nenabled; i++) + enabled->enabled[i].desc->pack(&dump_filters, + enabled->enabled[i].desc); + + for (i = 1; i < ARRAY_SIZE(dump_filters.mask); i++) { + if (ipstats_req_should_filter_at(&dump_filters, i)) { + get_filters = true; + break; + } + } + + req->ifsm.filter_mask = dump_filters.mask[0]; + if (get_filters) { + struct rtattr *nest; + + nest = addattr_nest(&req->nlh, sizeof(*req), + IFLA_STATS_GET_FILTERS | NLA_F_NESTED); + + for (i = 1; i < ARRAY_SIZE(dump_filters.mask); i++) { + if (ipstats_req_should_filter_at(&dump_filters, i)) + addattr32(&req->nlh, sizeof(*req), i, + dump_filters.mask[i]); + } + + addattr_nest_end(&req->nlh, nest); + } + + return 0; +} + +static int +ipstats_show_one(int ifindex, struct ipstats_stat_enabled *enabled) +{ + struct ipstats_req req = { + .nlh.nlmsg_flags = NLM_F_REQUEST, + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg)), + .nlh.nlmsg_type = RTM_GETSTATS, + .ifsm.family = PF_UNSPEC, + .ifsm.ifindex = ifindex, + }; + struct nlmsghdr *answer; + int err = 0; + + ipstats_req_add_filters(&req, enabled); + if (rtnl_talk(&rth, &req.nlh, &answer) < 0) + return -2; + err = ipstats_process_ifsm(answer, enabled); + free(answer); + + return err; +} + +static int ipstats_dump_one(struct nlmsghdr *n, void *arg) +{ + struct ipstats_stat_enabled *enabled = arg; + int rc; + + rc = ipstats_process_ifsm(n, enabled); + if (rc) + return rc; + + print_nl(); + return 0; +} + +static int ipstats_dump(struct ipstats_stat_enabled *enabled) +{ + int rc = 0; + + if (rtnl_statsdump_req_filter(&rth, PF_UNSPEC, 0, + ipstats_req_add_filters, + enabled) < 0) { + perror("Cannot send dump request"); + return -2; + } + + if (rtnl_dump_filter(&rth, ipstats_dump_one, enabled) < 0) { + fprintf(stderr, "Dump terminated\n"); + rc = -2; + } + + fflush(stdout); + return rc; +} + +static int +ipstats_show_do(int ifindex, struct ipstats_stat_enabled *enabled) +{ + int rc; + + new_json_obj(json); + if (ifindex) + rc = ipstats_show_one(ifindex, enabled); + else + rc = ipstats_dump(enabled); + delete_json_obj(); + + return rc; +} + +static int ipstats_add_enabled(struct ipstats_stat_enabled_one ens[], + size_t nens, + struct ipstats_stat_enabled *enabled) +{ + struct ipstats_stat_enabled_one *new_en; + + new_en = realloc(enabled->enabled, + sizeof(*new_en) * (enabled->nenabled + nens)); + if (new_en == NULL) + return -ENOMEM; + + enabled->enabled = new_en; + while (nens-- > 0) + enabled->enabled[enabled->nenabled++] = *ens++; + return 0; +} + +static void ipstats_select_push(struct ipstats_sel *sel, const char *name) +{ + int i; + + for (i = 0; i < IPSTATS_LEVELS_COUNT; i++) + if (sel->sel[i] == NULL) { + sel->sel[i] = name; + return; + } + + assert(false); +} + +static int +ipstats_enable_recursively(const struct ipstats_stat_desc *desc, + struct ipstats_stat_enabled *enabled, + const struct ipstats_sel *sel) +{ + bool found = false; + size_t i; + int err; + + if (desc->kind == IPSTATS_STAT_DESC_KIND_LEAF) { + struct ipstats_stat_enabled_one en[] = {{ + .desc = desc, + .sel = *sel, + }}; + + return ipstats_add_enabled(en, ARRAY_SIZE(en), enabled); + } + + for (i = 0; i < desc->nsubs; i++) { + struct ipstats_sel subsel = *sel; + + ipstats_select_push(&subsel, desc->subs[i]->name); + err = ipstats_enable_recursively(desc->subs[i], enabled, + &subsel); + if (err == -ENOENT) + continue; + if (err != 0) + return err; + found = true; + } + + return found ? 0 : -ENOENT; +} + +static int ipstats_comp_enabled(const void *a, const void *b) +{ + const struct ipstats_stat_enabled_one *en_a = a; + const struct ipstats_stat_enabled_one *en_b = b; + + if (en_a->desc < en_b->desc) + return -1; + if (en_a->desc > en_b->desc) + return 1; + + return 0; +} + +static void ipstats_enabled_free(struct ipstats_stat_enabled *enabled) +{ + free(enabled->enabled); +} + +static const struct ipstats_stat_desc * +ipstats_stat_desc_find(const struct ipstats_stat_desc *desc, + const char *name) +{ + size_t i; + + assert(desc->kind == IPSTATS_STAT_DESC_KIND_GROUP); + for (i = 0; i < desc->nsubs; i++) { + const struct ipstats_stat_desc *sub = desc->subs[i]; + + if (strcmp(sub->name, name) == 0) + return sub; + } + + return NULL; +} + +static const struct ipstats_stat_desc * +ipstats_enable_find_stat_desc(struct ipstats_sel *sel) +{ + const struct ipstats_stat_desc *toplev = &ipstats_stat_desc_toplev_group; + const struct ipstats_stat_desc *desc = toplev; + int i; + + for (i = 0; i < IPSTATS_LEVELS_COUNT; i++) { + const struct ipstats_stat_desc *next_desc; + + if (sel->sel[i] == NULL) + break; + if (desc->kind == IPSTATS_STAT_DESC_KIND_LEAF) { + fprintf(stderr, "Error: %s %s requested inside leaf %s %s\n", + ipstats_levels[i], sel->sel[i], + ipstats_levels[i - 1], desc->name); + return NULL; + } + + next_desc = ipstats_stat_desc_find(desc, sel->sel[i]); + if (next_desc == NULL) { + fprintf(stderr, "Error: no %s named %s found inside %s\n", + ipstats_levels[i], sel->sel[i], desc->name); + return NULL; + } + + desc = next_desc; + } + + return desc; +} + +static int ipstats_enable(struct ipstats_sel *sel, + struct ipstats_stat_enabled *enabled) +{ + struct ipstats_stat_enabled new_enabled = {}; + const struct ipstats_stat_desc *desc; + size_t i, j; + int err = 0; + + desc = ipstats_enable_find_stat_desc(sel); + if (desc == NULL) + return -EINVAL; + + err = ipstats_enable_recursively(desc, &new_enabled, sel); + if (err != 0) + return err; + + err = ipstats_add_enabled(new_enabled.enabled, new_enabled.nenabled, + enabled); + if (err != 0) + goto out; + + qsort(enabled->enabled, enabled->nenabled, sizeof(*enabled->enabled), + ipstats_comp_enabled); + + for (i = 1, j = 1; i < enabled->nenabled; i++) { + if (enabled->enabled[i].desc != enabled->enabled[j - 1].desc) + enabled->enabled[j++] = enabled->enabled[i]; + } + enabled->nenabled = j; + +out: + ipstats_enabled_free(&new_enabled); + return err; +} + +static int ipstats_enable_check(struct ipstats_sel *sel, + struct ipstats_stat_enabled *enabled) +{ + int err; + int i; + + err = ipstats_enable(sel, enabled); + if (err == -ENOENT) { + fprintf(stderr, "The request for"); + for (i = 0; i < IPSTATS_LEVELS_COUNT; i++) + if (sel->sel[i] != NULL) + fprintf(stderr, " %s %s", + ipstats_levels[i], sel->sel[i]); + else + break; + fprintf(stderr, " did not match any known stats.\n"); + } + + return err; +} + +static int do_help(void) +{ + const struct ipstats_stat_desc *toplev = &ipstats_stat_desc_toplev_group; + int i; + + fprintf(stderr, + "Usage: ip stats help\n" + " ip stats show [ dev DEV ] [ group GROUP [ subgroup SUBGROUP ] ... ] ...\n" + " ip stats set dev DEV l3_stats { on | off }\n" + ); + + for (i = 0; i < toplev->nsubs; i++) { + const struct ipstats_stat_desc *desc = toplev->subs[i]; + + if (i == 0) + fprintf(stderr, "GROUP := { %s", desc->name); + else + fprintf(stderr, " | %s", desc->name); + } + if (i > 0) + fprintf(stderr, " }\n"); + + for (i = 0; i < toplev->nsubs; i++) { + const struct ipstats_stat_desc *desc = toplev->subs[i]; + bool opened = false; + size_t j; + + if (desc->kind != IPSTATS_STAT_DESC_KIND_GROUP) + continue; + + for (j = 0; j < desc->nsubs; j++) { + if (j == 0) + fprintf(stderr, "%s SUBGROUP := {", desc->name); + else + fprintf(stderr, " |"); + fprintf(stderr, " %s", desc->subs[j]->name); + opened = true; + + if (desc->subs[j]->kind != IPSTATS_STAT_DESC_KIND_GROUP) + continue; + } + if (opened) + fprintf(stderr, " }\n"); + } + + return 0; +} + +static int ipstats_select(struct ipstats_sel *old_sel, + const char *new_sel, int level, + struct ipstats_stat_enabled *enabled) +{ + int err; + int i; + + for (i = 0; i < level; i++) { + if (old_sel->sel[i] == NULL) { + fprintf(stderr, "Error: %s %s requested without selecting a %s first\n", + ipstats_levels[level], new_sel, + ipstats_levels[i]); + return -EINVAL; + } + } + + for (i = level; i < IPSTATS_LEVELS_COUNT; i++) { + if (old_sel->sel[i] != NULL) { + err = ipstats_enable_check(old_sel, enabled); + if (err) + return err; + break; + } + } + + old_sel->sel[level] = new_sel; + for (i = level + 1; i < IPSTATS_LEVELS_COUNT; i++) + old_sel->sel[i] = NULL; + + return 0; +} + +static int ipstats_show(int argc, char **argv) +{ + struct ipstats_stat_enabled enabled = {}; + struct ipstats_sel sel = {}; + const char *dev = NULL; + int ifindex; + int err; + int i; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (dev != NULL) + duparg2("dev", *argv); + if (check_ifname(*argv)) + invarg("\"dev\" not a valid ifname", *argv); + dev = *argv; + } else if (strcmp(*argv, "help") == 0) { + do_help(); + return 0; + } else { + bool found_level = false; + + for (i = 0; i < ARRAY_SIZE(ipstats_levels); i++) { + if (strcmp(*argv, ipstats_levels[i]) == 0) { + NEXT_ARG(); + err = ipstats_select(&sel, *argv, i, + &enabled); + if (err) + goto err; + + found_level = true; + } + } + + if (!found_level) { + fprintf(stderr, "What is \"%s\"?\n", *argv); + do_help(); + err = -EINVAL; + goto err; + } + } + + NEXT_ARG_FWD(); + } + + /* Push whatever was given. */ + err = ipstats_enable_check(&sel, &enabled); + if (err) + goto err; + + if (dev) { + ifindex = ll_name_to_index(dev); + if (!ifindex) { + err = nodev(dev); + goto err; + } + } else { + ifindex = 0; + } + + + err = ipstats_show_do(ifindex, &enabled); + +err: + ipstats_enabled_free(&enabled); + return err; +} + +static int ipstats_set_do(int ifindex, int at, bool enable) +{ + struct ipstats_req req = { + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg)), + .nlh.nlmsg_flags = NLM_F_REQUEST, + .nlh.nlmsg_type = RTM_SETSTATS, + .ifsm.family = PF_UNSPEC, + .ifsm.ifindex = ifindex, + }; + + addattr8(&req.nlh, sizeof(req), at, enable); + + if (rtnl_talk(&rth, &req.nlh, NULL) < 0) + return -2; + return 0; +} + +static int ipstats_set(int argc, char **argv) +{ + const char *dev = NULL; + bool enable = false; + int ifindex; + int at = 0; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (dev) + duparg2("dev", *argv); + if (check_ifname(*argv)) + invarg("\"dev\" not a valid ifname", *argv); + dev = *argv; + } else if (strcmp(*argv, "l3_stats") == 0) { + int err; + + NEXT_ARG(); + if (at) { + fprintf(stderr, "A statistics suite to toggle was already given.\n"); + return -EINVAL; + } + at = IFLA_STATS_SET_OFFLOAD_XSTATS_L3_STATS; + enable = parse_on_off("l3_stats", *argv, &err); + if (err) + return err; + } else if (strcmp(*argv, "help") == 0) { + do_help(); + return 0; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + do_help(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } + + if (!dev) { + fprintf(stderr, "Not enough information: \"dev\" argument is required.\n"); + exit(-1); + } + + if (!at) { + fprintf(stderr, "Not enough information: stat type to toggle is required.\n"); + exit(-1); + } + + ifindex = ll_name_to_index(dev); + if (!ifindex) + return nodev(dev); + + return ipstats_set_do(ifindex, at, enable); +} + +int do_ipstats(int argc, char **argv) +{ + int rc; + + if (argc == 0) { + rc = ipstats_show(0, NULL); + } else if (strcmp(*argv, "help") == 0) { + do_help(); + rc = 0; + } else if (strcmp(*argv, "show") == 0) { + /* Invoking "stats show" implies one -s. Passing -d adds one + * more -s. + */ + show_stats += show_details + 1; + rc = ipstats_show(argc-1, argv+1); + } else if (strcmp(*argv, "set") == 0) { + rc = ipstats_set(argc-1, argv+1); + } else { + fprintf(stderr, "Command \"%s\" is unknown, try \"ip stats help\".\n", + *argv); + rc = -1; + } + + return rc; +} + +int ipstats_print(struct nlmsghdr *n, void *arg) +{ + struct ipstats_stat_enabled_one one = { + .desc = &ipstats_stat_desc_offload_hw_s_info, + }; + struct ipstats_stat_enabled enabled = { + .enabled = &one, + .nenabled = 1, + }; + FILE *fp = arg; + int rc; + + rc = ipstats_process_ifsm(n, &enabled); + if (rc) + return rc; + + fflush(fp); + return 0; +} diff --git a/lib/libnetlink.c b/lib/libnetlink.c index 6d1b11876..4d33e4dd1 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -619,12 +619,13 @@ int rtnl_fdb_linkdump_req_filter_fn(struct rtnl_handle *rth, return send(rth->fd, &req, sizeof(req), 0); } -int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask) +int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, + __u32 filt_mask, + int (*filter_fn)(struct ipstats_req *req, + void *data), + void *filter_data) { - struct { - struct nlmsghdr nlh; - struct if_stats_msg ifsm; - } req; + struct ipstats_req req; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg)); @@ -635,6 +636,14 @@ int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask) req.ifsm.family = fam; req.ifsm.filter_mask = filt_mask; + if (filter_fn) { + int err; + + err = filter_fn(&req, filter_data); + if (err) + return err; + } + return send(rth->fd, &req, sizeof(req), 0); } diff --git a/man/man8/ip-monitor.8 b/man/man8/ip-monitor.8 index f886d31b8..ec033c691 100644 --- a/man/man8/ip-monitor.8 +++ b/man/man8/ip-monitor.8 @@ -55,7 +55,7 @@ command is the first in the command line and then the object list follows: is the list of object types that we want to monitor. It may contain .BR link ", " address ", " route ", " mroute ", " prefix ", " -.BR neigh ", " netconf ", " rule ", " nsid " and " nexthop "." +.BR neigh ", " netconf ", " rule ", " stats ", " nsid " and " nexthop "." If no .B file argument is given, diff --git a/man/man8/ip-stats.8 b/man/man8/ip-stats.8 new file mode 100644 index 000000000..7eaaf122b --- /dev/null +++ b/man/man8/ip-stats.8 @@ -0,0 +1,160 @@ +.TH IP\-STATS 8 "16 Mar 2022" "iproute2" "Linux" +.SH NAME +ip-stats \- manage and show interface statistics +.SH SYNOPSIS +.sp +.ad l +.in +8 +.ti -8 +.B ip +.B stats +.RI " { " COMMAND " | " +.BR help " }" +.sp + +.ti -8 +.BR "ip stats show" +.RB "[ " dev +.IR DEV " ] " +.RB "[ " group +.IR GROUP " [ " +.BI subgroup " SUBGROUP" +.R " ] ... ] ..." + +.ti -8 +.BR "ip stats set" +.BI dev " DEV" +.BR l3_stats " { " +.BR on " | " off " }" + +.SH DESCRIPTION + +.TP +.B ip stats set +is used for toggling whether a certain HW statistics suite is collected on +a given netdevice. The following statistics suites are supported: + +.in 21 + +.ti 14 +.B l3_stats +L3 stats reflect traffic that takes place in a HW device on an object that +corresponds to the given software netdevice. + +.TP +.B ip stats show +is used for showing stats on a given netdevice, or dumping statistics +across all netdevices. By default, all stats are requested. It is possible +to filter which stats are requested by using the +.B group +and +.B subgroup +keywords. + +It is possible to specify several groups, or several subgroups for one +group. When no subgroups are given for a group, all the subgroups are +requested. + +The following groups are recognized: +.in 21 + +.ti 14 +.B group link +- Link statistics. The same suite that "ip -s link show" shows. + +.ti 14 +.B group offload +- A group that contains a number of HW-oriented statistics. See below for +individual subgroups within this group. + +.TQ +.BR "group offload " subgroups: +.in 21 + +.ti 14 +.B subgroup cpu_hit +- The +.B cpu_hit +statistics suite is useful on hardware netdevices. The +.B link +statistics on these devices reflect both the hardware- and +software-datapath traffic. The +.B cpu_hit +statistics then only reflect software-datapath traffic. + +.ti 14 +.B subgroup hw_stats_info +- This suite does not include traffic statistics, but rather communicates +the state of other statistics. Through this subgroup, it is possible to +discover whether a given statistic was enabled, and when it was, whether +any device driver actually configured its device to collect these +statistics. For example, +.B l3_stats +was enabled in the following case, but no driver has installed it: + +# ip stats show dev swp1 group offload subgroup hw_stats_info +.br +56: swp1: group offload subgroup hw_stats_info +.br + l3_stats on used off + +After an L3 address is added to the netdevice, the counter will be +installed: + +# ip addr add dev swp1 192.0.2.1/28 +.br +# ip stats show dev swp1 group offload subgroup hw_stats_info +.br +56: swp1: group offload subgroup hw_stats_info +.br + l3_stats on used on + +.ti 14 +.B subgroup l3_stats +- These statistics reflect L3 traffic that takes place in HW on an object +that corresponds to the netdevice. Note that this suite is disabled by +default and needs to be first enabled through +.B ip stats set\fR. + +For example: + +# ip stats show dev swp2.200 group offload subgroup l3_stats +.br +112: swp2.200: group offload subgroup l3_stats on used on +.br + RX: bytes packets errors dropped mcast +.br + 8900 72 2 0 3 +.br + TX: bytes packets errors dropped +.br + 7176 58 0 0 + +Note how the l3_stats_info for the selected group is also part of the dump. + +.SH EXAMPLES +.PP +# ip stats set dev swp1 l3_stats on +.RS +Enables collection of L3 HW statistics on swp1. +.RE + +.PP +# ip stats show group offload +.RS +Shows all offload statistics on all netdevices. +.RE + +.PP +# ip stats show dev swp1 group link +.RS +Shows link statistics on the given netdevice. +.RE + +.SH SEE ALSO +.br +.BR ip (8), +.BR ip-link (8), + +.SH AUTHOR +Manpage by Petr Machata. diff --git a/man/man8/ip.8 b/man/man8/ip.8 index 2a4848b78..f6adbc772 100644 --- a/man/man8/ip.8 +++ b/man/man8/ip.8 @@ -22,7 +22,7 @@ ip \- show / manipulate routing, network devices, interfaces and tunnels .BR link " | " address " | " addrlabel " | " route " | " rule " | " neigh " | "\ ntable " | " tunnel " | " tuntap " | " maddress " | " mroute " | " mrule " | "\ monitor " | " xfrm " | " netns " | " l2tp " | " tcp_metrics " | " token " | "\ - macsec " | " vrf " | " mptcp " | " ioam " }" + macsec " | " vrf " | " mptcp " | " ioam " | " stats " }" .sp .ti -8 @@ -303,6 +303,10 @@ readability. - rule in routing policy database. .TP +.B stats +- manage and show interface statistics. + +.TP .B tcp_metrics/tcpmetrics - manage TCP Metrics @@ -419,6 +423,7 @@ was written by Alexey N. Kuznetsov and added in Linux 2.2. .BR ip-ntable (8), .BR ip-route (8), .BR ip-rule (8), +.BR ip-stats (8) .BR ip-tcp_metrics (8), .BR ip-token (8), .BR ip-tunnel (8), diff --git a/misc/ifstat.c b/misc/ifstat.c index d4a33429d..291f288b3 100644 --- a/misc/ifstat.c +++ b/misc/ifstat.c @@ -202,7 +202,7 @@ static void load_info(void) ll_init_map(&rth); filter_mask = IFLA_STATS_FILTER_BIT(filter_type); if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, - filter_mask) < 0) { + filter_mask, NULL, NULL) < 0) { perror("Cannot send dump request"); exit(1); } |