All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jakub Kicinski <kuba@kernel.org>
To: davem@davemloft.net, donald.hunter@gmail.com
Cc: netdev@vger.kernel.org, edumazet@google.com, pabeni@redhat.com,
	andrew+netdev@lunn.ch, horms@kernel.org, sdf@fomichev.me,
	joe@dama.to, jstancek@redhat.com,
	Jakub Kicinski <kuba@kernel.org>
Subject: [PATCH net-next v2 3/4] tools: ynltool: add qstats support
Date: Fri,  7 Nov 2025 08:22:26 -0800	[thread overview]
Message-ID: <20251107162227.980672-4-kuba@kernel.org> (raw)
In-Reply-To: <20251107162227.980672-1-kuba@kernel.org>

  $ ynltool qstat
  eth0        rx-packets:       493192163        rx-bytes:   1442544543997
              tx-packets:       745999838        tx-bytes:   4574215826482
                 tx-stop:            7033         tx-wake:            7033

  $ ynltool qstat show group-by queue
  eth0  rx-0     packets:        70196880           bytes:    178633973750
  eth0  rx-1     packets:        63623419           bytes:    197274745250
  ...
  eth0  tx-1     packets:        98645810           bytes:    631247647938
                    stop:            1048            wake:            1048
  eth0  tx-2     packets:        86775824           bytes:    563930471952
                    stop:            1126            wake:            1126
  ...

  $ ynltool -j qstat  | jq
  [
   {
    "ifname": "eth0",
    "ifindex": 2,
    "rx": {
      "packets": 493396439,
      "bytes": 1443608198921
    },
    "tx": {
      "packets": 746239978,
      "bytes": 4574333772645,
      "stop": 7072,
      "wake": 7072
    }
   }
  ]

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 tools/net/ynl/ynltool/main.h   |   1 +
 tools/net/ynl/ynltool/main.c   |   3 +-
 tools/net/ynl/ynltool/qstats.c | 330 +++++++++++++++++++++++++++++++++
 3 files changed, 333 insertions(+), 1 deletion(-)
 create mode 100644 tools/net/ynl/ynltool/qstats.c

diff --git a/tools/net/ynl/ynltool/main.h b/tools/net/ynl/ynltool/main.h
index fd05d21451a2..c7039f9ac55a 100644
--- a/tools/net/ynl/ynltool/main.h
+++ b/tools/net/ynl/ynltool/main.h
@@ -61,5 +61,6 @@ int cmd_select(const struct cmd *cmds, int argc, char **argv,
 
 /* subcommands */
 int do_page_pool(int argc, char **argv);
+int do_qstats(int argc, char **argv);
 
 #endif /* __YNLTOOL_H */
diff --git a/tools/net/ynl/ynltool/main.c b/tools/net/ynl/ynltool/main.c
index f83c6f3245c8..5d0f428eed0a 100644
--- a/tools/net/ynl/ynltool/main.c
+++ b/tools/net/ynl/ynltool/main.c
@@ -47,7 +47,7 @@ static int do_help(int argc __attribute__((unused)),
 		"Usage: %s [OPTIONS] OBJECT { COMMAND | help }\n"
 		"       %s version\n"
 		"\n"
-		"       OBJECT := { page-pool }\n"
+		"       OBJECT := { page-pool | qstats }\n"
 		"       " HELP_SPEC_OPTIONS "\n"
 		"",
 		bin_name, bin_name);
@@ -72,6 +72,7 @@ static int do_version(int argc __attribute__((unused)),
 static const struct cmd commands[] = {
 	{ "help",	do_help },
 	{ "page-pool",	do_page_pool },
+	{ "qstats",	do_qstats },
 	{ "version",	do_version },
 	{ 0 }
 };
diff --git a/tools/net/ynl/ynltool/qstats.c b/tools/net/ynl/ynltool/qstats.c
new file mode 100644
index 000000000000..fcdbb6d9a852
--- /dev/null
+++ b/tools/net/ynl/ynltool/qstats.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <net/if.h>
+
+#include <ynl.h>
+#include "netdev-user.h"
+
+#include "main.h"
+
+static enum netdev_qstats_scope scope; /* default - device */
+
+static void print_json_qstats(struct netdev_qstats_get_list *qstats)
+{
+	jsonw_start_array(json_wtr);
+
+	ynl_dump_foreach(qstats, qs) {
+		char ifname[IF_NAMESIZE];
+		const char *name;
+
+		jsonw_start_object(json_wtr);
+
+		name = if_indextoname(qs->ifindex, ifname);
+		if (name)
+			jsonw_string_field(json_wtr, "ifname", name);
+		jsonw_uint_field(json_wtr, "ifindex", qs->ifindex);
+
+		if (qs->_present.queue_type)
+			jsonw_string_field(json_wtr, "queue-type",
+					   netdev_queue_type_str(qs->queue_type));
+		if (qs->_present.queue_id)
+			jsonw_uint_field(json_wtr, "queue-id", qs->queue_id);
+
+		if (qs->_present.rx_packets || qs->_present.rx_bytes ||
+		    qs->_present.rx_alloc_fail || qs->_present.rx_hw_drops ||
+		    qs->_present.rx_csum_complete || qs->_present.rx_hw_gro_packets) {
+			jsonw_name(json_wtr, "rx");
+			jsonw_start_object(json_wtr);
+			if (qs->_present.rx_packets)
+				jsonw_uint_field(json_wtr, "packets", qs->rx_packets);
+			if (qs->_present.rx_bytes)
+				jsonw_uint_field(json_wtr, "bytes", qs->rx_bytes);
+			if (qs->_present.rx_alloc_fail)
+				jsonw_uint_field(json_wtr, "alloc-fail", qs->rx_alloc_fail);
+			if (qs->_present.rx_hw_drops)
+				jsonw_uint_field(json_wtr, "hw-drops", qs->rx_hw_drops);
+			if (qs->_present.rx_hw_drop_overruns)
+				jsonw_uint_field(json_wtr, "hw-drop-overruns", qs->rx_hw_drop_overruns);
+			if (qs->_present.rx_hw_drop_ratelimits)
+				jsonw_uint_field(json_wtr, "hw-drop-ratelimits", qs->rx_hw_drop_ratelimits);
+			if (qs->_present.rx_csum_complete)
+				jsonw_uint_field(json_wtr, "csum-complete", qs->rx_csum_complete);
+			if (qs->_present.rx_csum_unnecessary)
+				jsonw_uint_field(json_wtr, "csum-unnecessary", qs->rx_csum_unnecessary);
+			if (qs->_present.rx_csum_none)
+				jsonw_uint_field(json_wtr, "csum-none", qs->rx_csum_none);
+			if (qs->_present.rx_csum_bad)
+				jsonw_uint_field(json_wtr, "csum-bad", qs->rx_csum_bad);
+			if (qs->_present.rx_hw_gro_packets)
+				jsonw_uint_field(json_wtr, "hw-gro-packets", qs->rx_hw_gro_packets);
+			if (qs->_present.rx_hw_gro_bytes)
+				jsonw_uint_field(json_wtr, "hw-gro-bytes", qs->rx_hw_gro_bytes);
+			if (qs->_present.rx_hw_gro_wire_packets)
+				jsonw_uint_field(json_wtr, "hw-gro-wire-packets", qs->rx_hw_gro_wire_packets);
+			if (qs->_present.rx_hw_gro_wire_bytes)
+				jsonw_uint_field(json_wtr, "hw-gro-wire-bytes", qs->rx_hw_gro_wire_bytes);
+			jsonw_end_object(json_wtr);
+		}
+
+		if (qs->_present.tx_packets || qs->_present.tx_bytes ||
+		    qs->_present.tx_hw_drops || qs->_present.tx_csum_none ||
+		    qs->_present.tx_hw_gso_packets) {
+			jsonw_name(json_wtr, "tx");
+			jsonw_start_object(json_wtr);
+			if (qs->_present.tx_packets)
+				jsonw_uint_field(json_wtr, "packets", qs->tx_packets);
+			if (qs->_present.tx_bytes)
+				jsonw_uint_field(json_wtr, "bytes", qs->tx_bytes);
+			if (qs->_present.tx_hw_drops)
+				jsonw_uint_field(json_wtr, "hw-drops", qs->tx_hw_drops);
+			if (qs->_present.tx_hw_drop_errors)
+				jsonw_uint_field(json_wtr, "hw-drop-errors", qs->tx_hw_drop_errors);
+			if (qs->_present.tx_hw_drop_ratelimits)
+				jsonw_uint_field(json_wtr, "hw-drop-ratelimits", qs->tx_hw_drop_ratelimits);
+			if (qs->_present.tx_csum_none)
+				jsonw_uint_field(json_wtr, "csum-none", qs->tx_csum_none);
+			if (qs->_present.tx_needs_csum)
+				jsonw_uint_field(json_wtr, "needs-csum", qs->tx_needs_csum);
+			if (qs->_present.tx_hw_gso_packets)
+				jsonw_uint_field(json_wtr, "hw-gso-packets", qs->tx_hw_gso_packets);
+			if (qs->_present.tx_hw_gso_bytes)
+				jsonw_uint_field(json_wtr, "hw-gso-bytes", qs->tx_hw_gso_bytes);
+			if (qs->_present.tx_hw_gso_wire_packets)
+				jsonw_uint_field(json_wtr, "hw-gso-wire-packets", qs->tx_hw_gso_wire_packets);
+			if (qs->_present.tx_hw_gso_wire_bytes)
+				jsonw_uint_field(json_wtr, "hw-gso-wire-bytes", qs->tx_hw_gso_wire_bytes);
+			if (qs->_present.tx_stop)
+				jsonw_uint_field(json_wtr, "stop", qs->tx_stop);
+			if (qs->_present.tx_wake)
+				jsonw_uint_field(json_wtr, "wake", qs->tx_wake);
+			jsonw_end_object(json_wtr);
+		}
+
+		jsonw_end_object(json_wtr);
+	}
+
+	jsonw_end_array(json_wtr);
+}
+
+static void print_one(bool present, const char *name, unsigned long long val,
+		      int *line)
+{
+	if (!present)
+		return;
+
+	if (!*line) {
+		printf("              ");
+		++(*line);
+	}
+
+	/* Don't waste space on tx- and rx- prefix, its implied by queue type */
+	if (scope == NETDEV_QSTATS_SCOPE_QUEUE &&
+	    (name[0] == 'r' || name[0] == 't') &&
+	    name[1] == 'x' && name[2] == '-')
+		name += 3;
+
+	printf(" %15s: %15llu", name, val);
+
+	if (++(*line) == 3) {
+		printf("\n");
+		*line = 0;
+	}
+}
+
+static void print_plain_qstats(struct netdev_qstats_get_list *qstats)
+{
+	ynl_dump_foreach(qstats, qs) {
+		char ifname[IF_NAMESIZE];
+		const char *name;
+		int n;
+
+		name = if_indextoname(qs->ifindex, ifname);
+		if (name)
+			printf("%s", name);
+		else
+			printf("ifindex:%u", qs->ifindex);
+
+		if (qs->_present.queue_type && qs->_present.queue_id)
+			printf("\t%s-%-3u",
+			       netdev_queue_type_str(qs->queue_type),
+			       qs->queue_id);
+		else
+			printf("\t      ");
+
+		n = 1;
+
+		/* Basic counters */
+		print_one(qs->_present.rx_packets, "rx-packets", qs->rx_packets, &n);
+		print_one(qs->_present.rx_bytes, "rx-bytes", qs->rx_bytes, &n);
+		print_one(qs->_present.tx_packets, "tx-packets", qs->tx_packets, &n);
+		print_one(qs->_present.tx_bytes, "tx-bytes", qs->tx_bytes, &n);
+
+		/* RX error/drop counters */
+		print_one(qs->_present.rx_alloc_fail, "rx-alloc-fail",
+			  qs->rx_alloc_fail, &n);
+		print_one(qs->_present.rx_hw_drops, "rx-hw-drops",
+			  qs->rx_hw_drops, &n);
+		print_one(qs->_present.rx_hw_drop_overruns, "rx-hw-drop-overruns",
+			  qs->rx_hw_drop_overruns, &n);
+		print_one(qs->_present.rx_hw_drop_ratelimits, "rx-hw-drop-ratelimits",
+			  qs->rx_hw_drop_ratelimits, &n);
+
+		/* RX checksum counters */
+		print_one(qs->_present.rx_csum_complete, "rx-csum-complete",
+			  qs->rx_csum_complete, &n);
+		print_one(qs->_present.rx_csum_unnecessary, "rx-csum-unnecessary",
+			  qs->rx_csum_unnecessary, &n);
+		print_one(qs->_present.rx_csum_none, "rx-csum-none",
+			  qs->rx_csum_none, &n);
+		print_one(qs->_present.rx_csum_bad, "rx-csum-bad",
+			  qs->rx_csum_bad, &n);
+
+		/* RX GRO counters */
+		print_one(qs->_present.rx_hw_gro_packets, "rx-hw-gro-packets",
+			  qs->rx_hw_gro_packets, &n);
+		print_one(qs->_present.rx_hw_gro_bytes, "rx-hw-gro-bytes",
+			  qs->rx_hw_gro_bytes, &n);
+		print_one(qs->_present.rx_hw_gro_wire_packets, "rx-hw-gro-wire-packets",
+			  qs->rx_hw_gro_wire_packets, &n);
+		print_one(qs->_present.rx_hw_gro_wire_bytes, "rx-hw-gro-wire-bytes",
+			  qs->rx_hw_gro_wire_bytes, &n);
+
+		/* TX error/drop counters */
+		print_one(qs->_present.tx_hw_drops, "tx-hw-drops",
+			  qs->tx_hw_drops, &n);
+		print_one(qs->_present.tx_hw_drop_errors, "tx-hw-drop-errors",
+			  qs->tx_hw_drop_errors, &n);
+		print_one(qs->_present.tx_hw_drop_ratelimits, "tx-hw-drop-ratelimits",
+			  qs->tx_hw_drop_ratelimits, &n);
+
+		/* TX checksum counters */
+		print_one(qs->_present.tx_csum_none, "tx-csum-none",
+			  qs->tx_csum_none, &n);
+		print_one(qs->_present.tx_needs_csum, "tx-needs-csum",
+			  qs->tx_needs_csum, &n);
+
+		/* TX GSO counters */
+		print_one(qs->_present.tx_hw_gso_packets, "tx-hw-gso-packets",
+			  qs->tx_hw_gso_packets, &n);
+		print_one(qs->_present.tx_hw_gso_bytes, "tx-hw-gso-bytes",
+			  qs->tx_hw_gso_bytes, &n);
+		print_one(qs->_present.tx_hw_gso_wire_packets, "tx-hw-gso-wire-packets",
+			  qs->tx_hw_gso_wire_packets, &n);
+		print_one(qs->_present.tx_hw_gso_wire_bytes, "tx-hw-gso-wire-bytes",
+			  qs->tx_hw_gso_wire_bytes, &n);
+
+		/* TX queue control */
+		print_one(qs->_present.tx_stop, "tx-stop", qs->tx_stop, &n);
+		print_one(qs->_present.tx_wake, "tx-wake", qs->tx_wake, &n);
+
+		if (n)
+			printf("\n");
+	}
+}
+
+static int do_show(int argc, char **argv)
+{
+	struct netdev_qstats_get_list *qstats;
+	struct netdev_qstats_get_req *req;
+	struct ynl_error yerr;
+	struct ynl_sock *ys;
+	int ret = 0;
+
+	/* Parse options */
+	while (argc > 0) {
+		if (is_prefix(*argv, "scope") || is_prefix(*argv, "group-by")) {
+			NEXT_ARG();
+
+			if (!REQ_ARGS(1))
+				return -1;
+
+			if (is_prefix(*argv, "queue")) {
+				scope = NETDEV_QSTATS_SCOPE_QUEUE;
+			} else if (is_prefix(*argv, "device")) {
+				scope = 0;
+			} else {
+				p_err("invalid scope value '%s'", *argv);
+				return -1;
+			}
+			NEXT_ARG();
+		} else {
+			p_err("unknown option '%s'", *argv);
+			return -1;
+		}
+	}
+
+	ys = ynl_sock_create(&ynl_netdev_family, &yerr);
+	if (!ys) {
+		p_err("YNL: %s", yerr.msg);
+		return -1;
+	}
+
+	req = netdev_qstats_get_req_alloc();
+	if (!req) {
+		p_err("failed to allocate qstats request");
+		ret = -1;
+		goto exit_close;
+	}
+
+	if (scope)
+		netdev_qstats_get_req_set_scope(req, scope);
+
+	qstats = netdev_qstats_get_dump(ys, req);
+	netdev_qstats_get_req_free(req);
+	if (!qstats) {
+		p_err("failed to get queue stats: %s", ys->err.msg);
+		ret = -1;
+		goto exit_close;
+	}
+
+	/* Print the stats as returned by the kernel */
+	if (json_output)
+		print_json_qstats(qstats);
+	else
+		print_plain_qstats(qstats);
+
+	netdev_qstats_get_list_free(qstats);
+exit_close:
+	ynl_sock_destroy(ys);
+	return ret;
+}
+
+static int do_help(int argc __attribute__((unused)),
+		   char **argv __attribute__((unused)))
+{
+	if (json_output) {
+		jsonw_null(json_wtr);
+		return 0;
+	}
+
+	fprintf(stderr,
+		"Usage: %s qstats { COMMAND | help }\n"
+		"       %s qstats [ show ] [ OPTIONS ]\n"
+		"\n"
+		"       OPTIONS := { scope queue | group-by { device | queue } }\n"
+		"\n"
+		"       show                  - Display queue statistics (default)\n"
+		"                               Statistics are aggregated for the entire device.\n"
+		"       show scope queue      - Display per-queue statistics\n"
+		"       show group-by device  - Display device-aggregated statistics (default)\n"
+		"       show group-by queue   - Display per-queue statistics\n"
+		"",
+		bin_name, bin_name);
+
+	return 0;
+}
+
+static const struct cmd qstats_cmds[] = {
+	{ "show",	do_show },
+	{ "help",	do_help },
+	{ 0 }
+};
+
+int do_qstats(int argc, char **argv)
+{
+	return cmd_select(qstats_cmds, argc, argv, do_help);
+}
-- 
2.51.1


  parent reply	other threads:[~2025-11-07 16:23 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-07 16:22 [PATCH net-next v2 0/4] tools: ynl: turn the page-pool sample into a real tool Jakub Kicinski
2025-11-07 16:22 ` [PATCH net-next v2 1/4] tools: ynltool: create skeleton for the C command Jakub Kicinski
2025-11-11 11:15   ` Paolo Abeni
2025-11-11 11:46     ` Paolo Abeni
2025-11-07 16:22 ` [PATCH net-next v2 2/4] tools: ynltool: add page-pool stats Jakub Kicinski
2025-11-07 16:22 ` Jakub Kicinski [this message]
2025-11-07 16:22 ` [PATCH net-next v2 4/4] tools: ynltool: add traffic distribution balance Jakub Kicinski
2025-11-07 21:46 ` [PATCH net-next v2 0/4] tools: ynl: turn the page-pool sample into a real tool Stanislav Fomichev
2025-11-11 11:50 ` patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251107162227.980672-4-kuba@kernel.org \
    --to=kuba@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=donald.hunter@gmail.com \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=joe@dama.to \
    --cc=jstancek@redhat.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sdf@fomichev.me \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.