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 int do_show(int argc, char **argv) 241 { 242 struct netdev_qstats_get_list *qstats; 243 struct netdev_qstats_get_req *req; 244 struct ynl_error yerr; 245 struct ynl_sock *ys; 246 int ret = 0; 247 248 /* Parse options */ 249 while (argc > 0) { 250 if (is_prefix(*argv, "scope") || is_prefix(*argv, "group-by")) { 251 NEXT_ARG(); 252 253 if (!REQ_ARGS(1)) 254 return -1; 255 256 if (is_prefix(*argv, "queue")) { 257 scope = NETDEV_QSTATS_SCOPE_QUEUE; 258 } else if (is_prefix(*argv, "device")) { 259 scope = 0; 260 } else { 261 p_err("invalid scope value '%s'", *argv); 262 return -1; 263 } 264 NEXT_ARG(); 265 } else { 266 p_err("unknown option '%s'", *argv); 267 return -1; 268 } 269 } 270 271 ys = ynl_sock_create(&ynl_netdev_family, &yerr); 272 if (!ys) { 273 p_err("YNL: %s", yerr.msg); 274 return -1; 275 } 276 277 req = netdev_qstats_get_req_alloc(); 278 if (!req) { 279 p_err("failed to allocate qstats request"); 280 ret = -1; 281 goto exit_close; 282 } 283 284 if (scope) 285 netdev_qstats_get_req_set_scope(req, scope); 286 287 qstats = netdev_qstats_get_dump(ys, req); 288 netdev_qstats_get_req_free(req); 289 if (!qstats) { 290 p_err("failed to get queue stats: %s", ys->err.msg); 291 ret = -1; 292 goto exit_close; 293 } 294 295 /* Print the stats as returned by the kernel */ 296 if (json_output) 297 print_json_qstats(qstats); 298 else 299 print_plain_qstats(qstats); 300 301 netdev_qstats_get_list_free(qstats); 302 exit_close: 303 ynl_sock_destroy(ys); 304 return ret; 305 } 306 307 static void compute_stats(__u64 *values, unsigned int count, 308 double *mean, double *stddev, __u64 *min, __u64 *max) 309 { 310 double sum = 0.0, variance = 0.0; 311 unsigned int i; 312 313 *min = ~0ULL; 314 *max = 0; 315 316 if (count == 0) { 317 *mean = 0; 318 *stddev = 0; 319 *min = 0; 320 return; 321 } 322 323 for (i = 0; i < count; i++) { 324 sum += values[i]; 325 if (values[i] < *min) 326 *min = values[i]; 327 if (values[i] > *max) 328 *max = values[i]; 329 } 330 331 *mean = sum / count; 332 333 if (count > 1) { 334 for (i = 0; i < count; i++) { 335 double diff = values[i] - *mean; 336 337 variance += diff * diff; 338 } 339 *stddev = sqrt(variance / (count - 1)); 340 } else { 341 *stddev = 0; 342 } 343 } 344 345 static void print_balance_stats(const char *name, enum netdev_queue_type type, 346 __u64 *values, unsigned int count) 347 { 348 double mean, stddev, cv, ns; 349 __u64 min, max; 350 351 if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) || 352 (name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX)) 353 return; 354 355 compute_stats(values, count, &mean, &stddev, &min, &max); 356 357 cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0; 358 ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0; 359 360 printf(" %-12s: cv=%.1f%% ns=%.1f%% stddev=%.0f\n", 361 name, cv, ns, stddev); 362 printf(" %-12s min=%llu max=%llu mean=%.0f\n", 363 "", min, max, mean); 364 } 365 366 static void 367 print_balance_stats_json(const char *name, enum netdev_queue_type type, 368 __u64 *values, unsigned int count) 369 { 370 double mean, stddev, cv, ns; 371 __u64 min, max; 372 373 if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) || 374 (name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX)) 375 return; 376 377 compute_stats(values, count, &mean, &stddev, &min, &max); 378 379 cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0; 380 ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0; 381 382 jsonw_name(json_wtr, name); 383 jsonw_start_object(json_wtr); 384 jsonw_uint_field(json_wtr, "queue-count", count); 385 jsonw_uint_field(json_wtr, "min", min); 386 jsonw_uint_field(json_wtr, "max", max); 387 jsonw_float_field(json_wtr, "mean", mean); 388 jsonw_float_field(json_wtr, "stddev", stddev); 389 jsonw_float_field(json_wtr, "coefficient-of-variation", cv); 390 jsonw_float_field(json_wtr, "normalized-spread", ns); 391 jsonw_end_object(json_wtr); 392 } 393 394 static int cmp_ifindex_type(const void *a, const void *b) 395 { 396 const struct netdev_qstats_get_rsp *qa = a; 397 const struct netdev_qstats_get_rsp *qb = b; 398 399 if (qa->ifindex != qb->ifindex) 400 return qa->ifindex - qb->ifindex; 401 if (qa->queue_type != qb->queue_type) 402 return qa->queue_type - qb->queue_type; 403 return qa->queue_id - qb->queue_id; 404 } 405 406 static int do_balance(int argc, char **argv __attribute__((unused))) 407 { 408 struct netdev_qstats_get_list *qstats; 409 struct netdev_qstats_get_req *req; 410 struct netdev_qstats_get_rsp **sorted; 411 struct ynl_error yerr; 412 struct ynl_sock *ys; 413 unsigned int count = 0; 414 unsigned int i, j; 415 int ret = 0; 416 417 if (argc > 0) { 418 p_err("balance command takes no arguments"); 419 return -1; 420 } 421 422 ys = ynl_sock_create(&ynl_netdev_family, &yerr); 423 if (!ys) { 424 p_err("YNL: %s", yerr.msg); 425 return -1; 426 } 427 428 req = netdev_qstats_get_req_alloc(); 429 if (!req) { 430 p_err("failed to allocate qstats request"); 431 ret = -1; 432 goto exit_close; 433 } 434 435 /* Always use queue scope for balance analysis */ 436 netdev_qstats_get_req_set_scope(req, NETDEV_QSTATS_SCOPE_QUEUE); 437 438 qstats = netdev_qstats_get_dump(ys, req); 439 netdev_qstats_get_req_free(req); 440 if (!qstats) { 441 p_err("failed to get queue stats: %s", ys->err.msg); 442 ret = -1; 443 goto exit_close; 444 } 445 446 /* Count and sort queues */ 447 ynl_dump_foreach(qstats, qs) 448 count++; 449 450 if (count == 0) { 451 if (json_output) 452 jsonw_start_array(json_wtr); 453 else 454 printf("No queue statistics available\n"); 455 goto exit_free_qstats; 456 } 457 458 sorted = calloc(count, sizeof(*sorted)); 459 if (!sorted) { 460 p_err("failed to allocate sorted array"); 461 ret = -1; 462 goto exit_free_qstats; 463 } 464 465 i = 0; 466 ynl_dump_foreach(qstats, qs) 467 sorted[i++] = qs; 468 469 qsort(sorted, count, sizeof(*sorted), cmp_ifindex_type); 470 471 if (json_output) 472 jsonw_start_array(json_wtr); 473 474 /* Process each device/queue-type combination */ 475 i = 0; 476 while (i < count) { 477 __u64 *rx_packets, *rx_bytes, *tx_packets, *tx_bytes; 478 enum netdev_queue_type type = sorted[i]->queue_type; 479 unsigned int ifindex = sorted[i]->ifindex; 480 unsigned int queue_count = 0; 481 char ifname[IF_NAMESIZE]; 482 const char *name; 483 484 /* Count queues for this device/type */ 485 for (j = i; j < count && sorted[j]->ifindex == ifindex && 486 sorted[j]->queue_type == type; j++) 487 queue_count++; 488 489 /* Skip if no packets/bytes (inactive queues) */ 490 if (!sorted[i]->_present.rx_packets && 491 !sorted[i]->_present.rx_bytes && 492 !sorted[i]->_present.tx_packets && 493 !sorted[i]->_present.tx_bytes) 494 goto next_ifc; 495 496 /* Allocate arrays for statistics */ 497 rx_packets = calloc(queue_count, sizeof(*rx_packets)); 498 rx_bytes = calloc(queue_count, sizeof(*rx_bytes)); 499 tx_packets = calloc(queue_count, sizeof(*tx_packets)); 500 tx_bytes = calloc(queue_count, sizeof(*tx_bytes)); 501 502 if (!rx_packets || !rx_bytes || !tx_packets || !tx_bytes) { 503 p_err("failed to allocate statistics arrays"); 504 free(rx_packets); 505 free(rx_bytes); 506 free(tx_packets); 507 free(tx_bytes); 508 ret = -1; 509 goto exit_free_sorted; 510 } 511 512 /* Collect statistics */ 513 for (j = 0; j < queue_count; j++) { 514 rx_packets[j] = sorted[i + j]->_present.rx_packets ? 515 sorted[i + j]->rx_packets : 0; 516 rx_bytes[j] = sorted[i + j]->_present.rx_bytes ? 517 sorted[i + j]->rx_bytes : 0; 518 tx_packets[j] = sorted[i + j]->_present.tx_packets ? 519 sorted[i + j]->tx_packets : 0; 520 tx_bytes[j] = sorted[i + j]->_present.tx_bytes ? 521 sorted[i + j]->tx_bytes : 0; 522 } 523 524 name = if_indextoname(ifindex, ifname); 525 526 if (json_output) { 527 jsonw_start_object(json_wtr); 528 if (name) 529 jsonw_string_field(json_wtr, "ifname", name); 530 jsonw_uint_field(json_wtr, "ifindex", ifindex); 531 jsonw_string_field(json_wtr, "queue-type", 532 netdev_queue_type_str(type)); 533 534 print_balance_stats_json("rx-packets", type, 535 rx_packets, queue_count); 536 print_balance_stats_json("rx-bytes", type, 537 rx_bytes, queue_count); 538 print_balance_stats_json("tx-packets", type, 539 tx_packets, queue_count); 540 print_balance_stats_json("tx-bytes", type, 541 tx_bytes, queue_count); 542 543 jsonw_end_object(json_wtr); 544 } else { 545 if (name) 546 printf("%s", name); 547 else 548 printf("ifindex:%u", ifindex); 549 printf(" %s %d queues:\n", 550 netdev_queue_type_str(type), queue_count); 551 552 print_balance_stats("rx-packets", type, 553 rx_packets, queue_count); 554 print_balance_stats("rx-bytes", type, 555 rx_bytes, queue_count); 556 print_balance_stats("tx-packets", type, 557 tx_packets, queue_count); 558 print_balance_stats("tx-bytes", type, 559 tx_bytes, queue_count); 560 printf("\n"); 561 } 562 563 free(rx_packets); 564 free(rx_bytes); 565 free(tx_packets); 566 free(tx_bytes); 567 568 next_ifc: 569 i += queue_count; 570 } 571 572 if (json_output) 573 jsonw_end_array(json_wtr); 574 575 exit_free_sorted: 576 free(sorted); 577 exit_free_qstats: 578 netdev_qstats_get_list_free(qstats); 579 exit_close: 580 ynl_sock_destroy(ys); 581 return ret; 582 } 583 584 static int do_help(int argc __attribute__((unused)), 585 char **argv __attribute__((unused))) 586 { 587 if (json_output) { 588 jsonw_null(json_wtr); 589 return 0; 590 } 591 592 fprintf(stderr, 593 "Usage: %s qstats { COMMAND | help }\n" 594 " %s qstats [ show ] [ OPTIONS ]\n" 595 " %s qstats balance\n" 596 "\n" 597 " OPTIONS := { scope queue | group-by { device | queue } }\n" 598 "\n" 599 " show - Display queue statistics (default)\n" 600 " Statistics are aggregated for the entire device.\n" 601 " show scope queue - Display per-queue statistics\n" 602 " show group-by device - Display device-aggregated statistics (default)\n" 603 " show group-by queue - Display per-queue statistics\n" 604 " balance - Analyze traffic distribution balance.\n" 605 "", 606 bin_name, bin_name, bin_name); 607 608 return 0; 609 } 610 611 static const struct cmd qstats_cmds[] = { 612 { "show", do_show }, 613 { "balance", do_balance }, 614 { "help", do_help }, 615 { 0 } 616 }; 617 618 int do_qstats(int argc, char **argv) 619 { 620 return cmd_select(qstats_cmds, argc, argv, do_help); 621 } 622