summaryrefslogtreecommitdiffstats
path: root/netlink/stats.c
diff options
context:
space:
mode:
Diffstat (limited to 'netlink/stats.c')
-rw-r--r--netlink/stats.c333
1 files changed, 333 insertions, 0 deletions
diff --git a/netlink/stats.c b/netlink/stats.c
new file mode 100644
index 0000000..8620d8d
--- /dev/null
+++ b/netlink/stats.c
@@ -0,0 +1,333 @@
+/*
+ * stats.c - netlink implementation of stats
+ *
+ * Implementation of "ethtool -S <dev> [--groups <types>] etc."
+ */
+
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+#include "strset.h"
+
+static int parse_rmon_hist_one(const char *grp_name, const struct nlattr *hist,
+ const char *dir)
+{
+ const struct nlattr *tb[ETHTOOL_A_STATS_GRP_HIST_VAL + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ unsigned long long val;
+ unsigned int low, hi;
+ int ret;
+
+ ret = mnl_attr_parse_nested(hist, attr_cb, &tb_info);
+ if (ret < 0) {
+ fprintf(stderr, "invalid kernel response - malformed histogram entry\n");
+ return 1;
+ }
+
+ if (!tb[ETHTOOL_A_STATS_GRP_HIST_BKT_LOW] ||
+ !tb[ETHTOOL_A_STATS_GRP_HIST_BKT_HI] ||
+ !tb[ETHTOOL_A_STATS_GRP_HIST_VAL]) {
+ fprintf(stderr, "invalid kernel response - histogram entry missing attributes\n");
+ return 1;
+ }
+
+ low = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_HIST_BKT_LOW]);
+ hi = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_HIST_BKT_HI]);
+ val = mnl_attr_get_u64(tb[ETHTOOL_A_STATS_GRP_HIST_VAL]);
+
+ if (!is_json_context()) {
+ fprintf(stdout, "%s-%s-etherStatsPkts", dir, grp_name);
+
+ if (low && hi) {
+ fprintf(stdout, "%uto%uOctets: %llu\n", low, hi, val);
+ } else if (hi) {
+ fprintf(stdout, "%uOctets: %llu\n", hi, val);
+ } else if (low) {
+ fprintf(stdout, "%utoMaxOctets: %llu\n", low, val);
+ } else {
+ fprintf(stderr, "invalid kernel response - bad histogram entry bounds\n");
+ return 1;
+ }
+ } else {
+ open_json_object(NULL);
+ print_uint(PRINT_JSON, "low", NULL, low);
+ print_uint(PRINT_JSON, "high", NULL, hi);
+ print_u64(PRINT_JSON, "val", NULL, val);
+ close_json_object();
+ }
+
+ return 0;
+}
+
+static int parse_rmon_hist(const struct nlattr *grp, const char *grp_name,
+ const char *name, const char *dir, unsigned int type)
+{
+ const struct nlattr *attr;
+
+ open_json_array(name, "");
+
+ mnl_attr_for_each_nested(attr, grp) {
+ if (mnl_attr_get_type(attr) == type &&
+ parse_rmon_hist_one(grp_name, attr, dir))
+ goto err_close_rmon;
+ }
+ close_json_array("");
+
+ return 0;
+
+err_close_rmon:
+ close_json_array("");
+ return 1;
+}
+
+static int parse_grp(struct nl_context *nlctx, const struct nlattr *grp,
+ const struct stringset *std_str)
+{
+ const struct nlattr *tb[ETHTOOL_A_STATS_GRP_SS_ID + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ bool hist_rx = false, hist_tx = false;
+ const struct stringset *stat_str;
+ const struct nlattr *attr, *stat;
+ const char *std_name, *name;
+ unsigned int ss_id, id, s;
+ unsigned long long val;
+ int ret;
+
+ ret = mnl_attr_parse_nested(grp, attr_cb, &tb_info);
+ if (ret < 0)
+ return 1;
+
+ if (!tb[ETHTOOL_A_STATS_GRP_ID])
+ return 1;
+ if (!tb[ETHTOOL_A_STATS_GRP_SS_ID])
+ return 0;
+
+ id = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_ID]);
+ ss_id = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_SS_ID]);
+
+ stat_str = global_stringset(ss_id, nlctx->ethnl2_socket);
+
+ std_name = get_string(std_str, id);
+ open_json_object(std_name);
+
+ mnl_attr_for_each_nested(attr, grp) {
+ switch (mnl_attr_get_type(attr)) {
+ case ETHTOOL_A_STATS_GRP_STAT:
+ break;
+ case ETHTOOL_A_STATS_GRP_HIST_RX:
+ hist_rx = true;
+ continue;
+ case ETHTOOL_A_STATS_GRP_HIST_TX:
+ hist_tx = true;
+ continue;
+ default:
+ continue;
+ }
+
+ stat = mnl_attr_get_payload(attr);
+ ret = mnl_attr_validate(stat, MNL_TYPE_U64);
+ if (ret) {
+ fprintf(stderr, "invalid kernel response - bad statistic entry\n");
+ goto err_close_grp;
+ }
+ s = mnl_attr_get_type(stat);
+ name = get_string(stat_str, s);
+ if (!name || !name[0])
+ continue;
+
+ if (!is_json_context())
+ fprintf(stdout, "%s-%s: ", std_name, name);
+
+ val = mnl_attr_get_u64(stat);
+ print_u64(PRINT_ANY, name, "%llu\n", val);
+ }
+
+ if (hist_rx)
+ parse_rmon_hist(grp, std_name, "rx-pktsNtoM", "rx",
+ ETHTOOL_A_STATS_GRP_HIST_RX);
+ if (hist_tx)
+ parse_rmon_hist(grp, std_name, "tx-pktsNtoM", "tx",
+ ETHTOOL_A_STATS_GRP_HIST_TX);
+
+ close_json_object();
+
+ return 0;
+
+err_close_grp:
+ close_json_object();
+ return 1;
+}
+
+static int stats_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_STATS_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ const struct stringset *std_str;
+ const struct nlattr *attr;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_STATS_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return err_ret;
+ std_str = global_stringset(ETH_SS_STATS_STD, nlctx->ethnl2_socket);
+
+ if (silent)
+ print_nl();
+
+ open_json_object(NULL);
+
+ print_string(PRINT_ANY, "ifname", "Standard stats for %s:\n",
+ nlctx->devname);
+
+ mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
+ if (mnl_attr_get_type(attr) == ETHTOOL_A_STATS_GRP) {
+ ret = parse_grp(nlctx, attr, std_str);
+ if (ret)
+ goto err_close_dev;
+ }
+ }
+
+ close_json_object();
+
+ return MNL_CB_OK;
+
+err_close_dev:
+ close_json_object();
+ return err_ret;
+}
+
+static const struct bitset_parser_data stats_parser_data = {
+ .no_mask = true,
+ .force_hex = false,
+};
+
+static int stats_parse_all_groups(struct nl_context *nlctx, uint16_t type,
+ const void *data, struct nl_msg_buff *msgbuff,
+ void *dest)
+{
+ const struct stringset *std_str;
+ struct nlattr *nest;
+ int i, ret, nbits;
+ uint32_t *bits;
+
+ if (data || dest)
+ return -EFAULT;
+
+ /* ethnl2 and strset code already does caching */
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return ret;
+ std_str = global_stringset(ETH_SS_STATS_STD, nlctx->ethnl2_socket);
+
+ nbits = get_count(std_str);
+ bits = calloc(DIV_ROUND_UP(nbits, 32), sizeof(uint32_t));
+ if (!bits)
+ return -ENOMEM;
+
+ for (i = 0; i < nbits; i++)
+ bits[i / 32] |= 1U << (i % 32);
+
+ ret = -EMSGSIZE;
+ nest = ethnla_nest_start(msgbuff, type);
+ if (!nest)
+ goto err_free;
+
+ if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true) ||
+ ethnla_put_u32(msgbuff, ETHTOOL_A_BITSET_SIZE, nbits) ||
+ ethnla_put(msgbuff, ETHTOOL_A_BITSET_VALUE,
+ DIV_ROUND_UP(nbits, 32) * sizeof(uint32_t), bits))
+ goto err_cancel;
+
+ ethnla_nest_end(msgbuff, nest);
+ free(bits);
+ return 0;
+
+err_cancel:
+ ethnla_nest_cancel(msgbuff, nest);
+err_free:
+ free(bits);
+ return ret;
+}
+
+static const struct lookup_entry_u32 stats_src_values[] = {
+ { .arg = "aggregate", .val = ETHTOOL_MAC_STATS_SRC_AGGREGATE },
+ { .arg = "emac", .val = ETHTOOL_MAC_STATS_SRC_EMAC },
+ { .arg = "pmac", .val = ETHTOOL_MAC_STATS_SRC_PMAC },
+ {}
+};
+
+static const struct param_parser stats_params[] = {
+ {
+ .arg = "--groups",
+ .type = ETHTOOL_A_STATS_GROUPS,
+ .handler = nl_parse_bitset,
+ .handler_data = &stats_parser_data,
+ .min_argc = 1,
+ .alt_group = 1,
+ },
+ {
+ .arg = "--all-groups",
+ .type = ETHTOOL_A_STATS_GROUPS,
+ .handler = stats_parse_all_groups,
+ .alt_group = 1,
+ },
+ {
+ .arg = "--src",
+ .type = ETHTOOL_A_STATS_SRC,
+ .handler = nl_parse_lookup_u32,
+ .handler_data = stats_src_values,
+ .min_argc = 1,
+ },
+ {}
+};
+
+int nl_gstats(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_STATS_GET,
+ ETHTOOL_A_STATS_HEADER, 0);
+ if (ret < 0)
+ return ret;
+
+ nlctx->cmd = "-S";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+
+ ret = nl_parser(nlctx, stats_params, NULL, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ return 1;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, stats_reply_cb);
+ delete_json_obj();
+ return ret;
+}
+
+bool nl_gstats_chk(struct cmd_context *ctx)
+{
+ return ctx->argc;
+}