Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tools: ynltool: add traffic distribution balance

The main if not only use case for per-queue stats today is checking
for traffic imbalance. Add simple traffic balance analysis to qstats.

$ ynltool qstat balance
eth0 rx 44 queues:
rx-packets : cv=6.9% ns=24.2% stddev=512006493
min=6278921110 max=8011570575 mean=7437054644
rx-bytes : cv=6.9% ns=24.1% stddev=759670503060
min=9326315769440 max=11884393670786 mean=11035439201354
...

$ ynltool -j qstat balance | jq
[
{
"ifname": "eth0",
"ifindex": 2,
"queue-type": "rx",
"rx-packets": {
"queue-count": 44,
"min": 6278301665,
"max": 8010780185,
"mean": 7.43635E+9,
"stddev": 5.12012E+8,
"coefficient-of-variation": 6.88525,
"normalized-spread": 24.249
},
...

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Link: https://patch.msgid.link/20251107162227.980672-5-kuba@kernel.org
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>

authored by

Jakub Kicinski and committed by
Paolo Abeni
9eef97a9 3f0a638d

+293 -2
+1 -1
tools/net/ynl/ynltool/Makefile
··· 31 31 32 32 $(YNLTOOL): ../libynl.a $(OBJS) 33 33 $(Q)echo -e "\tLINK $@" 34 - $(Q)$(CC) $(CFLAGS) -o $@ $(OBJS) ../libynl.a -lmnl 34 + $(Q)$(CC) $(CFLAGS) -o $@ $(OBJS) ../libynl.a -lmnl -lm 35 35 36 36 %.o: %.c ../libynl.a 37 37 $(Q)echo -e "\tCC $@"
+292 -1
tools/net/ynl/ynltool/qstats.c
··· 5 5 #include <string.h> 6 6 #include <errno.h> 7 7 #include <net/if.h> 8 + #include <math.h> 8 9 9 10 #include <ynl.h> 10 11 #include "netdev-user.h" ··· 13 12 #include "main.h" 14 13 15 14 static enum netdev_qstats_scope scope; /* default - device */ 15 + 16 + struct queue_balance { 17 + unsigned int ifindex; 18 + enum netdev_queue_type type; 19 + unsigned int queue_count; 20 + __u64 *rx_packets; 21 + __u64 *rx_bytes; 22 + __u64 *tx_packets; 23 + __u64 *tx_bytes; 24 + }; 16 25 17 26 static void print_json_qstats(struct netdev_qstats_get_list *qstats) 18 27 { ··· 304 293 return ret; 305 294 } 306 295 296 + static void compute_stats(__u64 *values, unsigned int count, 297 + double *mean, double *stddev, __u64 *min, __u64 *max) 298 + { 299 + double sum = 0.0, variance = 0.0; 300 + unsigned int i; 301 + 302 + *min = ~0ULL; 303 + *max = 0; 304 + 305 + if (count == 0) { 306 + *mean = 0; 307 + *stddev = 0; 308 + *min = 0; 309 + return; 310 + } 311 + 312 + for (i = 0; i < count; i++) { 313 + sum += values[i]; 314 + if (values[i] < *min) 315 + *min = values[i]; 316 + if (values[i] > *max) 317 + *max = values[i]; 318 + } 319 + 320 + *mean = sum / count; 321 + 322 + if (count > 1) { 323 + for (i = 0; i < count; i++) { 324 + double diff = values[i] - *mean; 325 + 326 + variance += diff * diff; 327 + } 328 + *stddev = sqrt(variance / (count - 1)); 329 + } else { 330 + *stddev = 0; 331 + } 332 + } 333 + 334 + static void print_balance_stats(const char *name, enum netdev_queue_type type, 335 + __u64 *values, unsigned int count) 336 + { 337 + double mean, stddev, cv, ns; 338 + __u64 min, max; 339 + 340 + if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) || 341 + (name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX)) 342 + return; 343 + 344 + compute_stats(values, count, &mean, &stddev, &min, &max); 345 + 346 + cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0; 347 + ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0; 348 + 349 + printf(" %-12s: cv=%.1f%% ns=%.1f%% stddev=%.0f\n", 350 + name, cv, ns, stddev); 351 + printf(" %-12s min=%llu max=%llu mean=%.0f\n", 352 + "", min, max, mean); 353 + } 354 + 355 + static void 356 + print_balance_stats_json(const char *name, enum netdev_queue_type type, 357 + __u64 *values, unsigned int count) 358 + { 359 + double mean, stddev, cv, ns; 360 + __u64 min, max; 361 + 362 + if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) || 363 + (name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX)) 364 + return; 365 + 366 + compute_stats(values, count, &mean, &stddev, &min, &max); 367 + 368 + cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0; 369 + ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0; 370 + 371 + jsonw_name(json_wtr, name); 372 + jsonw_start_object(json_wtr); 373 + jsonw_uint_field(json_wtr, "queue-count", count); 374 + jsonw_uint_field(json_wtr, "min", min); 375 + jsonw_uint_field(json_wtr, "max", max); 376 + jsonw_float_field(json_wtr, "mean", mean); 377 + jsonw_float_field(json_wtr, "stddev", stddev); 378 + jsonw_float_field(json_wtr, "coefficient-of-variation", cv); 379 + jsonw_float_field(json_wtr, "normalized-spread", ns); 380 + jsonw_end_object(json_wtr); 381 + } 382 + 383 + static int cmp_ifindex_type(const void *a, const void *b) 384 + { 385 + const struct netdev_qstats_get_rsp *qa = a; 386 + const struct netdev_qstats_get_rsp *qb = b; 387 + 388 + if (qa->ifindex != qb->ifindex) 389 + return qa->ifindex - qb->ifindex; 390 + if (qa->queue_type != qb->queue_type) 391 + return qa->queue_type - qb->queue_type; 392 + return qa->queue_id - qb->queue_id; 393 + } 394 + 395 + static int do_balance(int argc, char **argv __attribute__((unused))) 396 + { 397 + struct netdev_qstats_get_list *qstats; 398 + struct netdev_qstats_get_req *req; 399 + struct netdev_qstats_get_rsp **sorted; 400 + struct ynl_error yerr; 401 + struct ynl_sock *ys; 402 + unsigned int count = 0; 403 + unsigned int i, j; 404 + int ret = 0; 405 + 406 + if (argc > 0) { 407 + p_err("balance command takes no arguments"); 408 + return -1; 409 + } 410 + 411 + ys = ynl_sock_create(&ynl_netdev_family, &yerr); 412 + if (!ys) { 413 + p_err("YNL: %s", yerr.msg); 414 + return -1; 415 + } 416 + 417 + req = netdev_qstats_get_req_alloc(); 418 + if (!req) { 419 + p_err("failed to allocate qstats request"); 420 + ret = -1; 421 + goto exit_close; 422 + } 423 + 424 + /* Always use queue scope for balance analysis */ 425 + netdev_qstats_get_req_set_scope(req, NETDEV_QSTATS_SCOPE_QUEUE); 426 + 427 + qstats = netdev_qstats_get_dump(ys, req); 428 + netdev_qstats_get_req_free(req); 429 + if (!qstats) { 430 + p_err("failed to get queue stats: %s", ys->err.msg); 431 + ret = -1; 432 + goto exit_close; 433 + } 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 + exit_close: 569 + ynl_sock_destroy(ys); 570 + return ret; 571 + } 572 + 307 573 static int do_help(int argc __attribute__((unused)), 308 574 char **argv __attribute__((unused))) 309 575 { ··· 592 304 fprintf(stderr, 593 305 "Usage: %s qstats { COMMAND | help }\n" 594 306 " %s qstats [ show ] [ OPTIONS ]\n" 307 + " %s qstats balance\n" 595 308 "\n" 596 309 " OPTIONS := { scope queue | group-by { device | queue } }\n" 597 310 "\n" ··· 601 312 " show scope queue - Display per-queue statistics\n" 602 313 " show group-by device - Display device-aggregated statistics (default)\n" 603 314 " show group-by queue - Display per-queue statistics\n" 315 + " balance - Analyze traffic distribution balance.\n" 604 316 "", 605 - bin_name, bin_name); 317 + bin_name, bin_name, bin_name); 606 318 607 319 return 0; 608 320 } 609 321 610 322 static const struct cmd qstats_cmds[] = { 611 323 { "show", do_show }, 324 + { "balance", do_balance }, 612 325 { "help", do_help }, 613 326 { 0 } 614 327 };