Skip to content

Commit 9eef97a

Browse files
kuba-mooPaolo Abeni
authored andcommitted
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 <[email protected]> Link: https://patch.msgid.link/[email protected] Acked-by: Stanislav Fomichev <[email protected]> Signed-off-by: Paolo Abeni <[email protected]>
1 parent 3f0a638 commit 9eef97a

File tree

2 files changed

+293
-2
lines changed

2 files changed

+293
-2
lines changed

tools/net/ynl/ynltool/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Q = @
3131

3232
$(YNLTOOL): ../libynl.a $(OBJS)
3333
$(Q)echo -e "\tLINK $@"
34-
$(Q)$(CC) $(CFLAGS) -o $@ $(OBJS) ../libynl.a -lmnl
34+
$(Q)$(CC) $(CFLAGS) -o $@ $(OBJS) ../libynl.a -lmnl -lm
3535

3636
%.o: %.c ../libynl.a
3737
$(Q)echo -e "\tCC $@"

tools/net/ynl/ynltool/qstats.c

Lines changed: 292 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <string.h>
66
#include <errno.h>
77
#include <net/if.h>
8+
#include <math.h>
89

910
#include <ynl.h>
1011
#include "netdev-user.h"
@@ -13,6 +14,16 @@
1314

1415
static enum netdev_qstats_scope scope; /* default - device */
1516

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+
1627
static void print_json_qstats(struct netdev_qstats_get_list *qstats)
1728
{
1829
jsonw_start_array(json_wtr);
@@ -293,6 +304,283 @@ static int do_show(int argc, char **argv)
293304
return ret;
294305
}
295306

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+
296584
static int do_help(int argc __attribute__((unused)),
297585
char **argv __attribute__((unused)))
298586
{
@@ -304,6 +592,7 @@ static int do_help(int argc __attribute__((unused)),
304592
fprintf(stderr,
305593
"Usage: %s qstats { COMMAND | help }\n"
306594
" %s qstats [ show ] [ OPTIONS ]\n"
595+
" %s qstats balance\n"
307596
"\n"
308597
" OPTIONS := { scope queue | group-by { device | queue } }\n"
309598
"\n"
@@ -312,14 +601,16 @@ static int do_help(int argc __attribute__((unused)),
312601
" show scope queue - Display per-queue statistics\n"
313602
" show group-by device - Display device-aggregated statistics (default)\n"
314603
" show group-by queue - Display per-queue statistics\n"
604+
" balance - Analyze traffic distribution balance.\n"
315605
"",
316-
bin_name, bin_name);
606+
bin_name, bin_name, bin_name);
317607

318608
return 0;
319609
}
320610

321611
static const struct cmd qstats_cmds[] = {
322612
{ "show", do_show },
613+
{ "balance", do_balance },
323614
{ "help", do_help },
324615
{ 0 }
325616
};

0 commit comments

Comments
 (0)