xref: /linux/tools/net/ynl/ynltool/qstats.c (revision ca220141fa8ebae09765a242076b2b77338106b0)
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 struct netdev_qstats_get_list *
241 qstats_dump(enum netdev_qstats_scope scope)
242 {
243 	struct netdev_qstats_get_list *qstats;
244 	struct netdev_qstats_get_req *req;
245 	struct ynl_error yerr;
246 	struct ynl_sock *ys;
247 
248 	ys = ynl_sock_create(&ynl_netdev_family, &yerr);
249 	if (!ys) {
250 		p_err("YNL: %s", yerr.msg);
251 		return NULL;
252 	}
253 
254 	req = netdev_qstats_get_req_alloc();
255 	if (!req) {
256 		p_err("failed to allocate qstats request");
257 		goto err_close;
258 	}
259 
260 	if (scope)
261 		netdev_qstats_get_req_set_scope(req, scope);
262 
263 	qstats = netdev_qstats_get_dump(ys, req);
264 	netdev_qstats_get_req_free(req);
265 	if (!qstats) {
266 		p_err("failed to get queue stats: %s", ys->err.msg);
267 		goto err_close;
268 	}
269 
270 	ynl_sock_destroy(ys);
271 	return qstats;
272 
273 err_close:
274 	ynl_sock_destroy(ys);
275 	return NULL;
276 }
277 
278 static int do_show(int argc, char **argv)
279 {
280 	struct netdev_qstats_get_list *qstats;
281 
282 	/* Parse options */
283 	while (argc > 0) {
284 		if (is_prefix(*argv, "scope") || is_prefix(*argv, "group-by")) {
285 			NEXT_ARG();
286 
287 			if (!REQ_ARGS(1))
288 				return -1;
289 
290 			if (is_prefix(*argv, "queue")) {
291 				scope = NETDEV_QSTATS_SCOPE_QUEUE;
292 			} else if (is_prefix(*argv, "device")) {
293 				scope = 0;
294 			} else {
295 				p_err("invalid scope value '%s'", *argv);
296 				return -1;
297 			}
298 			NEXT_ARG();
299 		} else {
300 			p_err("unknown option '%s'", *argv);
301 			return -1;
302 		}
303 	}
304 
305 	qstats = qstats_dump(scope);
306 	if (!qstats)
307 		return -1;
308 
309 	/* Print the stats as returned by the kernel */
310 	if (json_output)
311 		print_json_qstats(qstats);
312 	else
313 		print_plain_qstats(qstats);
314 
315 	netdev_qstats_get_list_free(qstats);
316 	return 0;
317 }
318 
319 static void compute_stats(__u64 *values, unsigned int count,
320 			  double *mean, double *stddev, __u64 *min, __u64 *max)
321 {
322 	double sum = 0.0, variance = 0.0;
323 	unsigned int i;
324 
325 	*min = ~0ULL;
326 	*max = 0;
327 
328 	if (count == 0) {
329 		*mean = 0;
330 		*stddev = 0;
331 		*min = 0;
332 		return;
333 	}
334 
335 	for (i = 0; i < count; i++) {
336 		sum += values[i];
337 		if (values[i] < *min)
338 			*min = values[i];
339 		if (values[i] > *max)
340 			*max = values[i];
341 	}
342 
343 	*mean = sum / count;
344 
345 	if (count > 1) {
346 		for (i = 0; i < count; i++) {
347 			double diff = values[i] - *mean;
348 
349 			variance += diff * diff;
350 		}
351 		*stddev = sqrt(variance / (count - 1));
352 	} else {
353 		*stddev = 0;
354 	}
355 }
356 
357 static void print_balance_stats(const char *name, enum netdev_queue_type type,
358 				__u64 *values, unsigned int count)
359 {
360 	double mean, stddev, cv, ns;
361 	__u64 min, max;
362 
363 	if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) ||
364 	    (name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX))
365 		return;
366 
367 	compute_stats(values, count, &mean, &stddev, &min, &max);
368 
369 	cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0;
370 	ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0;
371 
372 	printf("  %-12s: cv=%.1f%% ns=%.1f%% stddev=%.0f\n",
373 	       name, cv, ns, stddev);
374 	printf("  %-12s  min=%llu max=%llu mean=%.0f\n",
375 	       "", min, max, mean);
376 }
377 
378 static void
379 print_balance_stats_json(const char *name, enum netdev_queue_type type,
380 			 __u64 *values, unsigned int count)
381 {
382 	double mean, stddev, cv, ns;
383 	__u64 min, max;
384 
385 	if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) ||
386 	    (name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX))
387 		return;
388 
389 	compute_stats(values, count, &mean, &stddev, &min, &max);
390 
391 	cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0;
392 	ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0;
393 
394 	jsonw_name(json_wtr, name);
395 	jsonw_start_object(json_wtr);
396 	jsonw_uint_field(json_wtr, "queue-count", count);
397 	jsonw_uint_field(json_wtr, "min", min);
398 	jsonw_uint_field(json_wtr, "max", max);
399 	jsonw_float_field(json_wtr, "mean", mean);
400 	jsonw_float_field(json_wtr, "stddev", stddev);
401 	jsonw_float_field(json_wtr, "coefficient-of-variation", cv);
402 	jsonw_float_field(json_wtr, "normalized-spread", ns);
403 	jsonw_end_object(json_wtr);
404 }
405 
406 static int cmp_ifindex_type(const void *a, const void *b)
407 {
408 	const struct netdev_qstats_get_rsp *qa = a;
409 	const struct netdev_qstats_get_rsp *qb = b;
410 
411 	if (qa->ifindex != qb->ifindex)
412 		return qa->ifindex - qb->ifindex;
413 	if (qa->queue_type != qb->queue_type)
414 		return qa->queue_type - qb->queue_type;
415 	return qa->queue_id - qb->queue_id;
416 }
417 
418 static int do_balance(int argc, char **argv __attribute__((unused)))
419 {
420 	struct netdev_qstats_get_list *qstats;
421 	struct netdev_qstats_get_rsp **sorted;
422 	unsigned int count = 0;
423 	unsigned int i, j;
424 	int ret = 0;
425 
426 	if (argc > 0) {
427 		p_err("balance command takes no arguments");
428 		return -1;
429 	}
430 
431 	qstats = qstats_dump(NETDEV_QSTATS_SCOPE_QUEUE);
432 	if (!qstats)
433 		return -1;
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 	return ret;
569 }
570 
571 static int do_hw_gro(int argc, char **argv __attribute__((unused)))
572 {
573 	struct netdev_qstats_get_list *qstats;
574 
575 	if (argc > 0) {
576 		p_err("hw-gro command takes no arguments");
577 		return -1;
578 	}
579 
580 	qstats = qstats_dump(0);
581 	if (!qstats)
582 		return -1;
583 
584 	if (json_output)
585 		jsonw_start_array(json_wtr);
586 
587 	ynl_dump_foreach(qstats, qs) {
588 		char ifname[IF_NAMESIZE];
589 		const char *name;
590 		double savings;
591 
592 		if (!qs->_present.rx_packets ||
593 		    !qs->_present.rx_hw_gro_packets ||
594 		    !qs->_present.rx_hw_gro_wire_packets)
595 			continue;
596 
597 		if (!qs->rx_packets)
598 			continue;
599 
600 		/* How many skbs did we avoid allocating thanks to HW GRO */
601 		savings = (double)(qs->rx_hw_gro_wire_packets -
602 				   qs->rx_hw_gro_packets) /
603 			qs->rx_packets * 100.0;
604 
605 		name = if_indextoname(qs->ifindex, ifname);
606 
607 		if (json_output) {
608 			jsonw_start_object(json_wtr);
609 			jsonw_uint_field(json_wtr, "ifindex", qs->ifindex);
610 			if (name)
611 				jsonw_string_field(json_wtr, "ifname", name);
612 			jsonw_float_field(json_wtr, "savings", savings);
613 			jsonw_end_object(json_wtr);
614 		} else {
615 			if (name)
616 				printf("%s", name);
617 			else
618 				printf("ifindex:%u", qs->ifindex);
619 			printf(": %.1f%% savings\n", savings);
620 		}
621 	}
622 
623 	if (json_output)
624 		jsonw_end_array(json_wtr);
625 
626 	netdev_qstats_get_list_free(qstats);
627 	return 0;
628 }
629 
630 static int do_help(int argc __attribute__((unused)),
631 		   char **argv __attribute__((unused)))
632 {
633 	if (json_output) {
634 		jsonw_null(json_wtr);
635 		return 0;
636 	}
637 
638 	fprintf(stderr,
639 		"Usage: %1$s qstats { COMMAND | help }\n"
640 		"       %1$s qstats [ show ] [ OPTIONS ]\n"
641 		"       %1$s qstats balance\n"
642 		"       %1$s qstats hw-gro\n"
643 		"\n"
644 		"       OPTIONS := { scope queue | group-by { device | queue } }\n"
645 		"\n"
646 		"       show                  - Display queue statistics (default)\n"
647 		"                               Statistics are aggregated for the entire device.\n"
648 		"       show scope queue      - Display per-queue statistics\n"
649 		"       show group-by device  - Display device-aggregated statistics (default)\n"
650 		"       show group-by queue   - Display per-queue statistics\n"
651 		"\n"
652 		"  Analysis:\n"
653 		"       balance               - Traffic distribution between queues.\n"
654 		"       hw-gro                - HW GRO effectiveness analysis\n"
655 		"                               - savings - delta between packets received\n"
656 		"                                 on the wire and packets seen by the kernel.\n"
657 		"",
658 		bin_name);
659 
660 	return 0;
661 }
662 
663 static const struct cmd qstats_cmds[] = {
664 	{ "show",	do_show },
665 	{ "balance",	do_balance },
666 	{ "hw-gro",	do_hw_gro },
667 	{ "help",	do_help },
668 	{ 0 }
669 };
670 
671 int do_qstats(int argc, char **argv)
672 {
673 	return cmd_select(qstats_cmds, argc, argv, do_help);
674 }
675