1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <net/if.h>
8 #include <math.h>
9
10 #include <ynl.h>
11 #include "netdev-user.h"
12
13 #include "main.h"
14
15 static enum netdev_qstats_scope scope; /* default - device */
16
17 struct queue_balance {
18 unsigned int ifindex;
19 enum netdev_queue_type type;
20 unsigned int queue_count;
21 __u64 *rx_packets;
22 __u64 *rx_bytes;
23 __u64 *tx_packets;
24 __u64 *tx_bytes;
25 };
26
print_json_qstats(struct netdev_qstats_get_list * qstats)27 static void print_json_qstats(struct netdev_qstats_get_list *qstats)
28 {
29 jsonw_start_array(json_wtr);
30
31 ynl_dump_foreach(qstats, qs) {
32 char ifname[IF_NAMESIZE];
33 const char *name;
34
35 jsonw_start_object(json_wtr);
36
37 name = if_indextoname(qs->ifindex, ifname);
38 if (name)
39 jsonw_string_field(json_wtr, "ifname", name);
40 jsonw_uint_field(json_wtr, "ifindex", qs->ifindex);
41
42 if (qs->_present.queue_type)
43 jsonw_string_field(json_wtr, "queue-type",
44 netdev_queue_type_str(qs->queue_type));
45 if (qs->_present.queue_id)
46 jsonw_uint_field(json_wtr, "queue-id", qs->queue_id);
47
48 if (qs->_present.rx_packets || qs->_present.rx_bytes ||
49 qs->_present.rx_alloc_fail || qs->_present.rx_hw_drops ||
50 qs->_present.rx_csum_complete || qs->_present.rx_hw_gro_packets) {
51 jsonw_name(json_wtr, "rx");
52 jsonw_start_object(json_wtr);
53 if (qs->_present.rx_packets)
54 jsonw_uint_field(json_wtr, "packets", qs->rx_packets);
55 if (qs->_present.rx_bytes)
56 jsonw_uint_field(json_wtr, "bytes", qs->rx_bytes);
57 if (qs->_present.rx_alloc_fail)
58 jsonw_uint_field(json_wtr, "alloc-fail", qs->rx_alloc_fail);
59 if (qs->_present.rx_hw_drops)
60 jsonw_uint_field(json_wtr, "hw-drops", qs->rx_hw_drops);
61 if (qs->_present.rx_hw_drop_overruns)
62 jsonw_uint_field(json_wtr, "hw-drop-overruns", qs->rx_hw_drop_overruns);
63 if (qs->_present.rx_hw_drop_ratelimits)
64 jsonw_uint_field(json_wtr, "hw-drop-ratelimits", qs->rx_hw_drop_ratelimits);
65 if (qs->_present.rx_csum_complete)
66 jsonw_uint_field(json_wtr, "csum-complete", qs->rx_csum_complete);
67 if (qs->_present.rx_csum_unnecessary)
68 jsonw_uint_field(json_wtr, "csum-unnecessary", qs->rx_csum_unnecessary);
69 if (qs->_present.rx_csum_none)
70 jsonw_uint_field(json_wtr, "csum-none", qs->rx_csum_none);
71 if (qs->_present.rx_csum_bad)
72 jsonw_uint_field(json_wtr, "csum-bad", qs->rx_csum_bad);
73 if (qs->_present.rx_hw_gro_packets)
74 jsonw_uint_field(json_wtr, "hw-gro-packets", qs->rx_hw_gro_packets);
75 if (qs->_present.rx_hw_gro_bytes)
76 jsonw_uint_field(json_wtr, "hw-gro-bytes", qs->rx_hw_gro_bytes);
77 if (qs->_present.rx_hw_gro_wire_packets)
78 jsonw_uint_field(json_wtr, "hw-gro-wire-packets", qs->rx_hw_gro_wire_packets);
79 if (qs->_present.rx_hw_gro_wire_bytes)
80 jsonw_uint_field(json_wtr, "hw-gro-wire-bytes", qs->rx_hw_gro_wire_bytes);
81 jsonw_end_object(json_wtr);
82 }
83
84 if (qs->_present.tx_packets || qs->_present.tx_bytes ||
85 qs->_present.tx_hw_drops || qs->_present.tx_csum_none ||
86 qs->_present.tx_hw_gso_packets) {
87 jsonw_name(json_wtr, "tx");
88 jsonw_start_object(json_wtr);
89 if (qs->_present.tx_packets)
90 jsonw_uint_field(json_wtr, "packets", qs->tx_packets);
91 if (qs->_present.tx_bytes)
92 jsonw_uint_field(json_wtr, "bytes", qs->tx_bytes);
93 if (qs->_present.tx_hw_drops)
94 jsonw_uint_field(json_wtr, "hw-drops", qs->tx_hw_drops);
95 if (qs->_present.tx_hw_drop_errors)
96 jsonw_uint_field(json_wtr, "hw-drop-errors", qs->tx_hw_drop_errors);
97 if (qs->_present.tx_hw_drop_ratelimits)
98 jsonw_uint_field(json_wtr, "hw-drop-ratelimits", qs->tx_hw_drop_ratelimits);
99 if (qs->_present.tx_csum_none)
100 jsonw_uint_field(json_wtr, "csum-none", qs->tx_csum_none);
101 if (qs->_present.tx_needs_csum)
102 jsonw_uint_field(json_wtr, "needs-csum", qs->tx_needs_csum);
103 if (qs->_present.tx_hw_gso_packets)
104 jsonw_uint_field(json_wtr, "hw-gso-packets", qs->tx_hw_gso_packets);
105 if (qs->_present.tx_hw_gso_bytes)
106 jsonw_uint_field(json_wtr, "hw-gso-bytes", qs->tx_hw_gso_bytes);
107 if (qs->_present.tx_hw_gso_wire_packets)
108 jsonw_uint_field(json_wtr, "hw-gso-wire-packets", qs->tx_hw_gso_wire_packets);
109 if (qs->_present.tx_hw_gso_wire_bytes)
110 jsonw_uint_field(json_wtr, "hw-gso-wire-bytes", qs->tx_hw_gso_wire_bytes);
111 if (qs->_present.tx_stop)
112 jsonw_uint_field(json_wtr, "stop", qs->tx_stop);
113 if (qs->_present.tx_wake)
114 jsonw_uint_field(json_wtr, "wake", qs->tx_wake);
115 jsonw_end_object(json_wtr);
116 }
117
118 jsonw_end_object(json_wtr);
119 }
120
121 jsonw_end_array(json_wtr);
122 }
123
print_one(bool present,const char * name,unsigned long long val,int * line)124 static void print_one(bool present, const char *name, unsigned long long val,
125 int *line)
126 {
127 if (!present)
128 return;
129
130 if (!*line) {
131 printf(" ");
132 ++(*line);
133 }
134
135 /* Don't waste space on tx- and rx- prefix, its implied by queue type */
136 if (scope == NETDEV_QSTATS_SCOPE_QUEUE &&
137 (name[0] == 'r' || name[0] == 't') &&
138 name[1] == 'x' && name[2] == '-')
139 name += 3;
140
141 printf(" %15s: %15llu", name, val);
142
143 if (++(*line) == 3) {
144 printf("\n");
145 *line = 0;
146 }
147 }
148
print_plain_qstats(struct netdev_qstats_get_list * qstats)149 static void print_plain_qstats(struct netdev_qstats_get_list *qstats)
150 {
151 ynl_dump_foreach(qstats, qs) {
152 char ifname[IF_NAMESIZE];
153 const char *name;
154 int n;
155
156 name = if_indextoname(qs->ifindex, ifname);
157 if (name)
158 printf("%s", name);
159 else
160 printf("ifindex:%u", qs->ifindex);
161
162 if (qs->_present.queue_type && qs->_present.queue_id)
163 printf("\t%s-%-3u",
164 netdev_queue_type_str(qs->queue_type),
165 qs->queue_id);
166 else
167 printf("\t ");
168
169 n = 1;
170
171 /* Basic counters */
172 print_one(qs->_present.rx_packets, "rx-packets", qs->rx_packets, &n);
173 print_one(qs->_present.rx_bytes, "rx-bytes", qs->rx_bytes, &n);
174 print_one(qs->_present.tx_packets, "tx-packets", qs->tx_packets, &n);
175 print_one(qs->_present.tx_bytes, "tx-bytes", qs->tx_bytes, &n);
176
177 /* RX error/drop counters */
178 print_one(qs->_present.rx_alloc_fail, "rx-alloc-fail",
179 qs->rx_alloc_fail, &n);
180 print_one(qs->_present.rx_hw_drops, "rx-hw-drops",
181 qs->rx_hw_drops, &n);
182 print_one(qs->_present.rx_hw_drop_overruns, "rx-hw-drop-overruns",
183 qs->rx_hw_drop_overruns, &n);
184 print_one(qs->_present.rx_hw_drop_ratelimits, "rx-hw-drop-ratelimits",
185 qs->rx_hw_drop_ratelimits, &n);
186
187 /* RX checksum counters */
188 print_one(qs->_present.rx_csum_complete, "rx-csum-complete",
189 qs->rx_csum_complete, &n);
190 print_one(qs->_present.rx_csum_unnecessary, "rx-csum-unnecessary",
191 qs->rx_csum_unnecessary, &n);
192 print_one(qs->_present.rx_csum_none, "rx-csum-none",
193 qs->rx_csum_none, &n);
194 print_one(qs->_present.rx_csum_bad, "rx-csum-bad",
195 qs->rx_csum_bad, &n);
196
197 /* RX GRO counters */
198 print_one(qs->_present.rx_hw_gro_packets, "rx-hw-gro-packets",
199 qs->rx_hw_gro_packets, &n);
200 print_one(qs->_present.rx_hw_gro_bytes, "rx-hw-gro-bytes",
201 qs->rx_hw_gro_bytes, &n);
202 print_one(qs->_present.rx_hw_gro_wire_packets, "rx-hw-gro-wire-packets",
203 qs->rx_hw_gro_wire_packets, &n);
204 print_one(qs->_present.rx_hw_gro_wire_bytes, "rx-hw-gro-wire-bytes",
205 qs->rx_hw_gro_wire_bytes, &n);
206
207 /* TX error/drop counters */
208 print_one(qs->_present.tx_hw_drops, "tx-hw-drops",
209 qs->tx_hw_drops, &n);
210 print_one(qs->_present.tx_hw_drop_errors, "tx-hw-drop-errors",
211 qs->tx_hw_drop_errors, &n);
212 print_one(qs->_present.tx_hw_drop_ratelimits, "tx-hw-drop-ratelimits",
213 qs->tx_hw_drop_ratelimits, &n);
214
215 /* TX checksum counters */
216 print_one(qs->_present.tx_csum_none, "tx-csum-none",
217 qs->tx_csum_none, &n);
218 print_one(qs->_present.tx_needs_csum, "tx-needs-csum",
219 qs->tx_needs_csum, &n);
220
221 /* TX GSO counters */
222 print_one(qs->_present.tx_hw_gso_packets, "tx-hw-gso-packets",
223 qs->tx_hw_gso_packets, &n);
224 print_one(qs->_present.tx_hw_gso_bytes, "tx-hw-gso-bytes",
225 qs->tx_hw_gso_bytes, &n);
226 print_one(qs->_present.tx_hw_gso_wire_packets, "tx-hw-gso-wire-packets",
227 qs->tx_hw_gso_wire_packets, &n);
228 print_one(qs->_present.tx_hw_gso_wire_bytes, "tx-hw-gso-wire-bytes",
229 qs->tx_hw_gso_wire_bytes, &n);
230
231 /* TX queue control */
232 print_one(qs->_present.tx_stop, "tx-stop", qs->tx_stop, &n);
233 print_one(qs->_present.tx_wake, "tx-wake", qs->tx_wake, &n);
234
235 if (n)
236 printf("\n");
237 }
238 }
239
240 static struct netdev_qstats_get_list *
qstats_dump(enum netdev_qstats_scope scope)241 qstats_dump(enum netdev_qstats_scope scope)
242 {
243 struct netdev_qstats_get_list *qstats;
244 struct netdev_qstats_get_req *req;
245 struct ynl_error yerr;
246 struct ynl_sock *ys;
247
248 ys = ynl_sock_create(&ynl_netdev_family, &yerr);
249 if (!ys) {
250 p_err("YNL: %s", yerr.msg);
251 return NULL;
252 }
253
254 req = netdev_qstats_get_req_alloc();
255 if (!req) {
256 p_err("failed to allocate qstats request");
257 goto err_close;
258 }
259
260 if (scope)
261 netdev_qstats_get_req_set_scope(req, scope);
262
263 qstats = netdev_qstats_get_dump(ys, req);
264 netdev_qstats_get_req_free(req);
265 if (!qstats) {
266 p_err("failed to get queue stats: %s", ys->err.msg);
267 goto err_close;
268 }
269
270 ynl_sock_destroy(ys);
271 return qstats;
272
273 err_close:
274 ynl_sock_destroy(ys);
275 return NULL;
276 }
277
do_show(int argc,char ** argv)278 static int do_show(int argc, char **argv)
279 {
280 struct netdev_qstats_get_list *qstats;
281
282 /* Parse options */
283 while (argc > 0) {
284 if (is_prefix(*argv, "scope") || is_prefix(*argv, "group-by")) {
285 NEXT_ARG();
286
287 if (!REQ_ARGS(1))
288 return -1;
289
290 if (is_prefix(*argv, "queue")) {
291 scope = NETDEV_QSTATS_SCOPE_QUEUE;
292 } else if (is_prefix(*argv, "device")) {
293 scope = 0;
294 } else {
295 p_err("invalid scope value '%s'", *argv);
296 return -1;
297 }
298 NEXT_ARG();
299 } else {
300 p_err("unknown option '%s'", *argv);
301 return -1;
302 }
303 }
304
305 qstats = qstats_dump(scope);
306 if (!qstats)
307 return -1;
308
309 /* Print the stats as returned by the kernel */
310 if (json_output)
311 print_json_qstats(qstats);
312 else
313 print_plain_qstats(qstats);
314
315 netdev_qstats_get_list_free(qstats);
316 return 0;
317 }
318
compute_stats(__u64 * values,unsigned int count,double * mean,double * stddev,__u64 * min,__u64 * max)319 static void compute_stats(__u64 *values, unsigned int count,
320 double *mean, double *stddev, __u64 *min, __u64 *max)
321 {
322 double sum = 0.0, variance = 0.0;
323 unsigned int i;
324
325 *min = ~0ULL;
326 *max = 0;
327
328 if (count == 0) {
329 *mean = 0;
330 *stddev = 0;
331 *min = 0;
332 return;
333 }
334
335 for (i = 0; i < count; i++) {
336 sum += values[i];
337 if (values[i] < *min)
338 *min = values[i];
339 if (values[i] > *max)
340 *max = values[i];
341 }
342
343 *mean = sum / count;
344
345 if (count > 1) {
346 for (i = 0; i < count; i++) {
347 double diff = values[i] - *mean;
348
349 variance += diff * diff;
350 }
351 *stddev = sqrt(variance / (count - 1));
352 } else {
353 *stddev = 0;
354 }
355 }
356
print_balance_stats(const char * name,enum netdev_queue_type type,__u64 * values,unsigned int count)357 static void print_balance_stats(const char *name, enum netdev_queue_type type,
358 __u64 *values, unsigned int count)
359 {
360 double mean, stddev, cv, ns;
361 __u64 min, max;
362
363 if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) ||
364 (name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX))
365 return;
366
367 compute_stats(values, count, &mean, &stddev, &min, &max);
368
369 cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0;
370 ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0;
371
372 printf(" %-12s: cv=%.1f%% ns=%.1f%% stddev=%.0f\n",
373 name, cv, ns, stddev);
374 printf(" %-12s min=%llu max=%llu mean=%.0f\n",
375 "", min, max, mean);
376 }
377
378 static void
print_balance_stats_json(const char * name,enum netdev_queue_type type,__u64 * values,unsigned int count)379 print_balance_stats_json(const char *name, enum netdev_queue_type type,
380 __u64 *values, unsigned int count)
381 {
382 double mean, stddev, cv, ns;
383 __u64 min, max;
384
385 if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) ||
386 (name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX))
387 return;
388
389 compute_stats(values, count, &mean, &stddev, &min, &max);
390
391 cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0;
392 ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0;
393
394 jsonw_name(json_wtr, name);
395 jsonw_start_object(json_wtr);
396 jsonw_uint_field(json_wtr, "queue-count", count);
397 jsonw_uint_field(json_wtr, "min", min);
398 jsonw_uint_field(json_wtr, "max", max);
399 jsonw_float_field(json_wtr, "mean", mean);
400 jsonw_float_field(json_wtr, "stddev", stddev);
401 jsonw_float_field(json_wtr, "coefficient-of-variation", cv);
402 jsonw_float_field(json_wtr, "normalized-spread", ns);
403 jsonw_end_object(json_wtr);
404 }
405
cmp_ifindex_type(const void * a,const void * b)406 static int cmp_ifindex_type(const void *a, const void *b)
407 {
408 const struct netdev_qstats_get_rsp *qa = a;
409 const struct netdev_qstats_get_rsp *qb = b;
410
411 if (qa->ifindex != qb->ifindex)
412 return qa->ifindex - qb->ifindex;
413 if (qa->queue_type != qb->queue_type)
414 return qa->queue_type - qb->queue_type;
415 return qa->queue_id - qb->queue_id;
416 }
417
do_balance(int argc,char ** argv)418 static int do_balance(int argc, char **argv __attribute__((unused)))
419 {
420 struct netdev_qstats_get_list *qstats;
421 struct netdev_qstats_get_rsp **sorted;
422 unsigned int count = 0;
423 unsigned int i, j;
424 int ret = 0;
425
426 if (argc > 0) {
427 p_err("balance command takes no arguments");
428 return -1;
429 }
430
431 qstats = qstats_dump(NETDEV_QSTATS_SCOPE_QUEUE);
432 if (!qstats)
433 return -1;
434
435 /* Count and sort queues */
436 ynl_dump_foreach(qstats, qs)
437 count++;
438
439 if (count == 0) {
440 if (json_output)
441 jsonw_start_array(json_wtr);
442 else
443 printf("No queue statistics available\n");
444 goto exit_free_qstats;
445 }
446
447 sorted = calloc(count, sizeof(*sorted));
448 if (!sorted) {
449 p_err("failed to allocate sorted array");
450 ret = -1;
451 goto exit_free_qstats;
452 }
453
454 i = 0;
455 ynl_dump_foreach(qstats, qs)
456 sorted[i++] = qs;
457
458 qsort(sorted, count, sizeof(*sorted), cmp_ifindex_type);
459
460 if (json_output)
461 jsonw_start_array(json_wtr);
462
463 /* Process each device/queue-type combination */
464 i = 0;
465 while (i < count) {
466 __u64 *rx_packets, *rx_bytes, *tx_packets, *tx_bytes;
467 enum netdev_queue_type type = sorted[i]->queue_type;
468 unsigned int ifindex = sorted[i]->ifindex;
469 unsigned int queue_count = 0;
470 char ifname[IF_NAMESIZE];
471 const char *name;
472
473 /* Count queues for this device/type */
474 for (j = i; j < count && sorted[j]->ifindex == ifindex &&
475 sorted[j]->queue_type == type; j++)
476 queue_count++;
477
478 /* Skip if no packets/bytes (inactive queues) */
479 if (!sorted[i]->_present.rx_packets &&
480 !sorted[i]->_present.rx_bytes &&
481 !sorted[i]->_present.tx_packets &&
482 !sorted[i]->_present.tx_bytes)
483 goto next_ifc;
484
485 /* Allocate arrays for statistics */
486 rx_packets = calloc(queue_count, sizeof(*rx_packets));
487 rx_bytes = calloc(queue_count, sizeof(*rx_bytes));
488 tx_packets = calloc(queue_count, sizeof(*tx_packets));
489 tx_bytes = calloc(queue_count, sizeof(*tx_bytes));
490
491 if (!rx_packets || !rx_bytes || !tx_packets || !tx_bytes) {
492 p_err("failed to allocate statistics arrays");
493 free(rx_packets);
494 free(rx_bytes);
495 free(tx_packets);
496 free(tx_bytes);
497 ret = -1;
498 goto exit_free_sorted;
499 }
500
501 /* Collect statistics */
502 for (j = 0; j < queue_count; j++) {
503 rx_packets[j] = sorted[i + j]->_present.rx_packets ?
504 sorted[i + j]->rx_packets : 0;
505 rx_bytes[j] = sorted[i + j]->_present.rx_bytes ?
506 sorted[i + j]->rx_bytes : 0;
507 tx_packets[j] = sorted[i + j]->_present.tx_packets ?
508 sorted[i + j]->tx_packets : 0;
509 tx_bytes[j] = sorted[i + j]->_present.tx_bytes ?
510 sorted[i + j]->tx_bytes : 0;
511 }
512
513 name = if_indextoname(ifindex, ifname);
514
515 if (json_output) {
516 jsonw_start_object(json_wtr);
517 if (name)
518 jsonw_string_field(json_wtr, "ifname", name);
519 jsonw_uint_field(json_wtr, "ifindex", ifindex);
520 jsonw_string_field(json_wtr, "queue-type",
521 netdev_queue_type_str(type));
522
523 print_balance_stats_json("rx-packets", type,
524 rx_packets, queue_count);
525 print_balance_stats_json("rx-bytes", type,
526 rx_bytes, queue_count);
527 print_balance_stats_json("tx-packets", type,
528 tx_packets, queue_count);
529 print_balance_stats_json("tx-bytes", type,
530 tx_bytes, queue_count);
531
532 jsonw_end_object(json_wtr);
533 } else {
534 if (name)
535 printf("%s", name);
536 else
537 printf("ifindex:%u", ifindex);
538 printf(" %s %d queues:\n",
539 netdev_queue_type_str(type), queue_count);
540
541 print_balance_stats("rx-packets", type,
542 rx_packets, queue_count);
543 print_balance_stats("rx-bytes", type,
544 rx_bytes, queue_count);
545 print_balance_stats("tx-packets", type,
546 tx_packets, queue_count);
547 print_balance_stats("tx-bytes", type,
548 tx_bytes, queue_count);
549 printf("\n");
550 }
551
552 free(rx_packets);
553 free(rx_bytes);
554 free(tx_packets);
555 free(tx_bytes);
556
557 next_ifc:
558 i += queue_count;
559 }
560
561 if (json_output)
562 jsonw_end_array(json_wtr);
563
564 exit_free_sorted:
565 free(sorted);
566 exit_free_qstats:
567 netdev_qstats_get_list_free(qstats);
568 return ret;
569 }
570
do_hw_gro(int argc,char ** argv)571 static int do_hw_gro(int argc, char **argv __attribute__((unused)))
572 {
573 struct netdev_qstats_get_list *qstats;
574
575 if (argc > 0) {
576 p_err("hw-gro command takes no arguments");
577 return -1;
578 }
579
580 qstats = qstats_dump(0);
581 if (!qstats)
582 return -1;
583
584 if (json_output)
585 jsonw_start_array(json_wtr);
586
587 ynl_dump_foreach(qstats, qs) {
588 char ifname[IF_NAMESIZE];
589 const char *name;
590 double savings;
591
592 if (!qs->_present.rx_packets ||
593 !qs->_present.rx_hw_gro_packets ||
594 !qs->_present.rx_hw_gro_wire_packets)
595 continue;
596
597 if (!qs->rx_packets)
598 continue;
599
600 /* How many skbs did we avoid allocating thanks to HW GRO */
601 savings = (double)(qs->rx_hw_gro_wire_packets -
602 qs->rx_hw_gro_packets) /
603 qs->rx_packets * 100.0;
604
605 name = if_indextoname(qs->ifindex, ifname);
606
607 if (json_output) {
608 jsonw_start_object(json_wtr);
609 jsonw_uint_field(json_wtr, "ifindex", qs->ifindex);
610 if (name)
611 jsonw_string_field(json_wtr, "ifname", name);
612 jsonw_float_field(json_wtr, "savings", savings);
613 jsonw_end_object(json_wtr);
614 } else {
615 if (name)
616 printf("%s", name);
617 else
618 printf("ifindex:%u", qs->ifindex);
619 printf(": %.1f%% savings\n", savings);
620 }
621 }
622
623 if (json_output)
624 jsonw_end_array(json_wtr);
625
626 netdev_qstats_get_list_free(qstats);
627 return 0;
628 }
629
do_help(int argc,char ** argv)630 static int do_help(int argc __attribute__((unused)),
631 char **argv __attribute__((unused)))
632 {
633 if (json_output) {
634 jsonw_null(json_wtr);
635 return 0;
636 }
637
638 fprintf(stderr,
639 "Usage: %1$s qstats { COMMAND | help }\n"
640 " %1$s qstats [ show ] [ OPTIONS ]\n"
641 " %1$s qstats balance\n"
642 " %1$s qstats hw-gro\n"
643 "\n"
644 " OPTIONS := { scope queue | group-by { device | queue } }\n"
645 "\n"
646 " show - Display queue statistics (default)\n"
647 " Statistics are aggregated for the entire device.\n"
648 " show scope queue - Display per-queue statistics\n"
649 " show group-by device - Display device-aggregated statistics (default)\n"
650 " show group-by queue - Display per-queue statistics\n"
651 "\n"
652 " Analysis:\n"
653 " balance - Traffic distribution between queues.\n"
654 " hw-gro - HW GRO effectiveness analysis\n"
655 " - savings - delta between packets received\n"
656 " on the wire and packets seen by the kernel.\n"
657 "",
658 bin_name);
659
660 return 0;
661 }
662
663 static const struct cmd qstats_cmds[] = {
664 { "show", do_show },
665 { "balance", do_balance },
666 { "hw-gro", do_hw_gro },
667 { "help", do_help },
668 { 0 }
669 };
670
do_qstats(int argc,char ** argv)671 int do_qstats(int argc, char **argv)
672 {
673 return cmd_select(qstats_cmds, argc, argv, do_help);
674 }
675