xref: /linux/tools/net/ynl/ynltool/qstats.c (revision 6dfafbd0299a60bfb5d5e277fdf100037c7ded07)
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