blob: 8620d8de15554e159b111c94fddb4bafcf71def3 [file]
/*
* 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;
}