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 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 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 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 * 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 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 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 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 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 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 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 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 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 671 int do_qstats(int argc, char **argv) 672 { 673 return cmd_select(qstats_cmds, argc, argv, do_help); 674 } 675