1 /*
2 * stats.c - netlink implementation of stats
3 *
4 * Implementation of "ethtool -S <dev> [--groups <types>] etc."
5 */
6
7 #include <errno.h>
8 #include <ctype.h>
9 #include <inttypes.h>
10 #include <string.h>
11 #include <stdio.h>
12
13 #include "../internal.h"
14 #include "../common.h"
15 #include "netlink.h"
16 #include "parser.h"
17 #include "strset.h"
18
parse_rmon_hist_one(const char * grp_name,const struct nlattr * hist,const char * dir)19 static int parse_rmon_hist_one(const char *grp_name, const struct nlattr *hist,
20 const char *dir)
21 {
22 const struct nlattr *tb[ETHTOOL_A_STATS_GRP_HIST_VAL + 1] = {};
23 DECLARE_ATTR_TB_INFO(tb);
24 unsigned long long val;
25 unsigned int low, hi;
26 int ret;
27
28 ret = mnl_attr_parse_nested(hist, attr_cb, &tb_info);
29 if (ret < 0) {
30 fprintf(stderr, "invalid kernel response - malformed histogram entry\n");
31 return 1;
32 }
33
34 if (!tb[ETHTOOL_A_STATS_GRP_HIST_BKT_LOW] ||
35 !tb[ETHTOOL_A_STATS_GRP_HIST_BKT_HI] ||
36 !tb[ETHTOOL_A_STATS_GRP_HIST_VAL]) {
37 fprintf(stderr, "invalid kernel response - histogram entry missing attributes\n");
38 return 1;
39 }
40
41 low = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_HIST_BKT_LOW]);
42 hi = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_HIST_BKT_HI]);
43 val = mnl_attr_get_u64(tb[ETHTOOL_A_STATS_GRP_HIST_VAL]);
44
45 if (!is_json_context()) {
46 fprintf(stdout, "%s-%s-etherStatsPkts", dir, grp_name);
47
48 if (low && hi) {
49 fprintf(stdout, "%uto%uOctets: %llu\n", low, hi, val);
50 } else if (hi) {
51 fprintf(stdout, "%uOctets: %llu\n", hi, val);
52 } else if (low) {
53 fprintf(stdout, "%utoMaxOctets: %llu\n", low, val);
54 } else {
55 fprintf(stderr, "invalid kernel response - bad histogram entry bounds\n");
56 return 1;
57 }
58 } else {
59 open_json_object(NULL);
60 print_uint(PRINT_JSON, "low", NULL, low);
61 print_uint(PRINT_JSON, "high", NULL, hi);
62 print_u64(PRINT_JSON, "val", NULL, val);
63 close_json_object();
64 }
65
66 return 0;
67 }
68
parse_rmon_hist(const struct nlattr * grp,const char * grp_name,const char * name,const char * dir,unsigned int type)69 static int parse_rmon_hist(const struct nlattr *grp, const char *grp_name,
70 const char *name, const char *dir, unsigned int type)
71 {
72 const struct nlattr *attr;
73
74 open_json_array(name, "");
75
76 mnl_attr_for_each_nested(attr, grp) {
77 if (mnl_attr_get_type(attr) == type &&
78 parse_rmon_hist_one(grp_name, attr, dir))
79 goto err_close_rmon;
80 }
81 close_json_array("");
82
83 return 0;
84
85 err_close_rmon:
86 close_json_array("");
87 return 1;
88 }
89
parse_grp(struct nl_context * nlctx,const struct nlattr * grp,const struct stringset * std_str)90 static int parse_grp(struct nl_context *nlctx, const struct nlattr *grp,
91 const struct stringset *std_str)
92 {
93 const struct nlattr *tb[ETHTOOL_A_STATS_GRP_SS_ID + 1] = {};
94 DECLARE_ATTR_TB_INFO(tb);
95 bool hist_rx = false, hist_tx = false;
96 const struct stringset *stat_str;
97 const struct nlattr *attr, *stat;
98 const char *std_name, *name;
99 unsigned int ss_id, id, s;
100 unsigned long long val;
101 int ret;
102
103 ret = mnl_attr_parse_nested(grp, attr_cb, &tb_info);
104 if (ret < 0)
105 return 1;
106
107 if (!tb[ETHTOOL_A_STATS_GRP_ID])
108 return 1;
109 if (!tb[ETHTOOL_A_STATS_GRP_SS_ID])
110 return 0;
111
112 id = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_ID]);
113 ss_id = mnl_attr_get_u32(tb[ETHTOOL_A_STATS_GRP_SS_ID]);
114
115 stat_str = global_stringset(ss_id, nlctx->ethnl2_socket);
116
117 std_name = get_string(std_str, id);
118 open_json_object(std_name);
119
120 mnl_attr_for_each_nested(attr, grp) {
121 switch (mnl_attr_get_type(attr)) {
122 case ETHTOOL_A_STATS_GRP_STAT:
123 break;
124 case ETHTOOL_A_STATS_GRP_HIST_RX:
125 hist_rx = true;
126 continue;
127 case ETHTOOL_A_STATS_GRP_HIST_TX:
128 hist_tx = true;
129 continue;
130 default:
131 continue;
132 }
133
134 stat = mnl_attr_get_payload(attr);
135 ret = mnl_attr_validate(stat, MNL_TYPE_U64);
136 if (ret) {
137 fprintf(stderr, "invalid kernel response - bad statistic entry\n");
138 goto err_close_grp;
139 }
140 s = mnl_attr_get_type(stat);
141 name = get_string(stat_str, s);
142 if (!name || !name[0])
143 continue;
144
145 if (!is_json_context())
146 fprintf(stdout, "%s-%s: ", std_name, name);
147
148 val = mnl_attr_get_u64(stat);
149 print_u64(PRINT_ANY, name, "%llu\n", val);
150 }
151
152 if (hist_rx)
153 parse_rmon_hist(grp, std_name, "rx-pktsNtoM", "rx",
154 ETHTOOL_A_STATS_GRP_HIST_RX);
155 if (hist_tx)
156 parse_rmon_hist(grp, std_name, "tx-pktsNtoM", "tx",
157 ETHTOOL_A_STATS_GRP_HIST_TX);
158
159 close_json_object();
160
161 return 0;
162
163 err_close_grp:
164 close_json_object();
165 return 1;
166 }
167
stats_reply_cb(const struct nlmsghdr * nlhdr,void * data)168 static int stats_reply_cb(const struct nlmsghdr *nlhdr, void *data)
169 {
170 const struct nlattr *tb[ETHTOOL_A_STATS_MAX + 1] = {};
171 DECLARE_ATTR_TB_INFO(tb);
172 struct nl_context *nlctx = data;
173 const struct stringset *std_str;
174 const struct nlattr *attr;
175 bool silent;
176 int err_ret;
177 int ret;
178
179 silent = nlctx->is_dump || nlctx->is_monitor;
180 err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
181 ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
182 if (ret < 0)
183 return err_ret;
184 nlctx->devname = get_dev_name(tb[ETHTOOL_A_STATS_HEADER]);
185 if (!dev_ok(nlctx))
186 return err_ret;
187
188 ret = netlink_init_ethnl2_socket(nlctx);
189 if (ret < 0)
190 return err_ret;
191 std_str = global_stringset(ETH_SS_STATS_STD, nlctx->ethnl2_socket);
192
193 if (silent)
194 print_nl();
195
196 open_json_object(NULL);
197
198 print_string(PRINT_ANY, "ifname", "Standard stats for %s:\n",
199 nlctx->devname);
200
201 mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
202 if (mnl_attr_get_type(attr) == ETHTOOL_A_STATS_GRP) {
203 ret = parse_grp(nlctx, attr, std_str);
204 if (ret)
205 goto err_close_dev;
206 }
207 }
208
209 close_json_object();
210
211 return MNL_CB_OK;
212
213 err_close_dev:
214 close_json_object();
215 return err_ret;
216 }
217
218 static const struct bitset_parser_data stats_parser_data = {
219 .no_mask = true,
220 .force_hex = false,
221 };
222
stats_parse_all_groups(struct nl_context * nlctx,uint16_t type,const void * data,struct nl_msg_buff * msgbuff,void * dest)223 static int stats_parse_all_groups(struct nl_context *nlctx, uint16_t type,
224 const void *data, struct nl_msg_buff *msgbuff,
225 void *dest)
226 {
227 const struct stringset *std_str;
228 struct nlattr *nest;
229 int i, ret, nbits;
230 uint32_t *bits;
231
232 if (data || dest)
233 return -EFAULT;
234
235 /* ethnl2 and strset code already does caching */
236 ret = netlink_init_ethnl2_socket(nlctx);
237 if (ret < 0)
238 return ret;
239 std_str = global_stringset(ETH_SS_STATS_STD, nlctx->ethnl2_socket);
240
241 nbits = get_count(std_str);
242 bits = calloc(DIV_ROUND_UP(nbits, 32), sizeof(uint32_t));
243 if (!bits)
244 return -ENOMEM;
245
246 for (i = 0; i < nbits; i++)
247 bits[i / 32] |= 1U << (i % 32);
248
249 ret = -EMSGSIZE;
250 nest = ethnla_nest_start(msgbuff, type);
251 if (!nest)
252 goto err_free;
253
254 if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true) ||
255 ethnla_put_u32(msgbuff, ETHTOOL_A_BITSET_SIZE, nbits) ||
256 ethnla_put(msgbuff, ETHTOOL_A_BITSET_VALUE,
257 DIV_ROUND_UP(nbits, 32) * sizeof(uint32_t), bits))
258 goto err_cancel;
259
260 ethnla_nest_end(msgbuff, nest);
261 free(bits);
262 return 0;
263
264 err_cancel:
265 ethnla_nest_cancel(msgbuff, nest);
266 err_free:
267 free(bits);
268 return ret;
269 }
270
271 static const struct lookup_entry_u32 stats_src_values[] = {
272 { .arg = "aggregate", .val = ETHTOOL_MAC_STATS_SRC_AGGREGATE },
273 { .arg = "emac", .val = ETHTOOL_MAC_STATS_SRC_EMAC },
274 { .arg = "pmac", .val = ETHTOOL_MAC_STATS_SRC_PMAC },
275 {}
276 };
277
278 static const struct param_parser stats_params[] = {
279 {
280 .arg = "--groups",
281 .type = ETHTOOL_A_STATS_GROUPS,
282 .handler = nl_parse_bitset,
283 .handler_data = &stats_parser_data,
284 .min_argc = 1,
285 .alt_group = 1,
286 },
287 {
288 .arg = "--all-groups",
289 .type = ETHTOOL_A_STATS_GROUPS,
290 .handler = stats_parse_all_groups,
291 .alt_group = 1,
292 },
293 {
294 .arg = "--src",
295 .type = ETHTOOL_A_STATS_SRC,
296 .handler = nl_parse_lookup_u32,
297 .handler_data = stats_src_values,
298 .min_argc = 1,
299 },
300 {}
301 };
302
nl_gstats(struct cmd_context * ctx)303 int nl_gstats(struct cmd_context *ctx)
304 {
305 struct nl_context *nlctx = ctx->nlctx;
306 struct nl_socket *nlsk = nlctx->ethnl_socket;
307 int ret;
308
309 ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_STATS_GET,
310 ETHTOOL_A_STATS_HEADER, 0);
311 if (ret < 0)
312 return ret;
313
314 nlctx->cmd = "-S";
315 nlctx->argp = ctx->argp;
316 nlctx->argc = ctx->argc;
317 nlctx->devname = ctx->devname;
318 nlsk = nlctx->ethnl_socket;
319
320 ret = nl_parser(nlctx, stats_params, NULL, PARSER_GROUP_NONE, NULL);
321 if (ret < 0)
322 return 1;
323
324 new_json_obj(ctx->json);
325 ret = nlsock_send_get_request(nlsk, stats_reply_cb);
326 delete_json_obj();
327 return ret;
328 }
329
nl_gstats_chk(struct cmd_context * ctx)330 bool nl_gstats_chk(struct cmd_context *ctx)
331 {
332 return ctx->argc;
333 }
334