aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Machata <petrm@nvidia.com>2022-04-22 10:30:54 +0200
committerDavid Ahern <dsahern@kernel.org>2022-04-27 20:12:42 -0600
commitdf0b2c6d0098bfcc8be1c74238d43a0e38681090 (patch)
tree13ed744732023ab5da1147f11f78c9c541b27c54
parent82f6444f83c76e01870ca9f7779a43cfd81d38d9 (diff)
downloadiproute2-df0b2c6d0098bfcc8be1c74238d43a0e38681090.tar.gz
ipstats: Add a shell of "show" command
Add the scaffolding necessary for adding individual stats suites to show. Expose some ipstats artifacts in ip_common.h. These will be used to support "xstats" in a follow-up patchset. Signed-off-by: Petr Machata <petrm@nvidia.com> Reviewed-by: Ido Schimmel <idosch@nvidia.com> Signed-off-by: David Ahern <dsahern@kernel.org>
-rw-r--r--ip/ip_common.h26
-rw-r--r--ip/ipstats.c614
2 files changed, 638 insertions, 2 deletions
diff --git a/ip/ip_common.h b/ip/ip_common.h
index 53866d7ac..8b0a64262 100644
--- a/ip/ip_common.h
+++ b/ip/ip_common.h
@@ -158,6 +158,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
diff --git a/ip/ipstats.c b/ip/ipstats.c
index 1f5b3f774..79f7b1ffa 100644
--- a/ip/ipstats.c
+++ b/ip/ipstats.c
@@ -1,19 +1,624 @@
// 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];
+};
+
+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 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]);
+}
+
+static const struct ipstats_stat_desc *ipstats_stat_desc_toplev_subs[] = {
+};
+
+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 = {
@@ -92,11 +697,16 @@ int do_ipstats(int argc, char **argv)
int rc;
if (argc == 0) {
- do_help();
- rc = -1;
+ 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 {