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"
1314
1415static 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+
1627static 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+
296584static 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
321611static const struct cmd qstats_cmds [] = {
322612 { "show" , do_show },
613+ { "balance" , do_balance },
323614 { "help" , do_help },
324615 { 0 }
325616};
0 commit comments