Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,2 @@
ynltool
*.d
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0-only
include ../Makefile.deps
INSTALL ?= install
prefix ?= /usr
CC := gcc
CFLAGS := -Wall -Wextra -Werror -O2
ifeq ("$(DEBUG)","1")
CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan
endif
CFLAGS += -I../lib -I../generated -I../../../include/uapi/
SRC_VERSION := \
$(shell make --no-print-directory -sC ../../../.. kernelversion 2>/dev/null || \
echo "unknown")
CFLAGS += -DSRC_VERSION='"$(SRC_VERSION)"'
SRCS := $(wildcard *.c)
OBJS := $(patsubst %.c,$(OUTPUT)%.o,$(SRCS))
YNLTOOL := $(OUTPUT)ynltool
include $(wildcard *.d)
all: $(YNLTOOL)
Q = @
$(YNLTOOL): ../libynl.a $(OBJS)
$(Q)echo -e "\tLINK $@"
$(Q)$(CC) $(CFLAGS) -o $@ $(OBJS) ../libynl.a -lm
%.o: %.c ../libynl.a
$(Q)echo -e "\tCC $@"
$(Q)$(COMPILE.c) -MMD -c -o $@ $<
../libynl.a:
$(Q)$(MAKE) -C ../
clean:
rm -f *.o *.d *~
distclean: clean
rm -f $(YNLTOOL)
bindir ?= /usr/bin
install: $(YNLTOOL)
$(INSTALL) -m 0755 $(YNLTOOL) $(DESTDIR)$(bindir)/$(YNLTOOL)
.PHONY: all clean distclean
.DEFAULT_GOAL=all
@@ -0,0 +1,288 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
/*
* Simple streaming JSON writer
*
* This takes care of the annoying bits of JSON syntax like the commas
* after elements
*
* Authors: Stephen Hemminger <stephen@networkplumber.org>
*/
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <assert.h>
#include <malloc.h>
#include <inttypes.h>
#include <stdint.h>
#include "json_writer.h"
struct json_writer {
FILE *out;
unsigned depth;
bool pretty;
char sep;
};
static void jsonw_indent(json_writer_t *self)
{
unsigned i;
for (i = 0; i < self->depth; ++i)
fputs(" ", self->out);
}
static void jsonw_eol(json_writer_t *self)
{
if (!self->pretty)
return;
putc('\n', self->out);
jsonw_indent(self);
}
static void jsonw_eor(json_writer_t *self)
{
if (self->sep != '\0')
putc(self->sep, self->out);
self->sep = ',';
}
static void jsonw_puts(json_writer_t *self, const char *str)
{
putc('"', self->out);
for (; *str; ++str)
switch (*str) {
case '\t':
fputs("\\t", self->out);
break;
case '\n':
fputs("\\n", self->out);
break;
case '\r':
fputs("\\r", self->out);
break;
case '\f':
fputs("\\f", self->out);
break;
case '\b':
fputs("\\b", self->out);
break;
case '\\':
fputs("\\\\", self->out);
break;
case '"':
fputs("\\\"", self->out);
break;
default:
putc(*str, self->out);
}
putc('"', self->out);
}
json_writer_t *jsonw_new(FILE *f)
{
json_writer_t *self = malloc(sizeof(*self));
if (self) {
self->out = f;
self->depth = 0;
self->pretty = false;
self->sep = '\0';
}
return self;
}
void jsonw_destroy(json_writer_t **self_p)
{
json_writer_t *self = *self_p;
assert(self->depth == 0);
fputs("\n", self->out);
fflush(self->out);
free(self);
*self_p = NULL;
}
void jsonw_pretty(json_writer_t *self, bool on)
{
self->pretty = on;
}
void jsonw_reset(json_writer_t *self)
{
assert(self->depth == 0);
self->sep = '\0';
}
static void jsonw_begin(json_writer_t *self, int c)
{
jsonw_eor(self);
putc(c, self->out);
++self->depth;
self->sep = '\0';
}
static void jsonw_end(json_writer_t *self, int c)
{
assert(self->depth > 0);
--self->depth;
if (self->sep != '\0')
jsonw_eol(self);
putc(c, self->out);
self->sep = ',';
}
void jsonw_name(json_writer_t *self, const char *name)
{
jsonw_eor(self);
jsonw_eol(self);
self->sep = '\0';
jsonw_puts(self, name);
putc(':', self->out);
if (self->pretty)
putc(' ', self->out);
}
void jsonw_vprintf_enquote(json_writer_t *self, const char *fmt, va_list ap)
{
jsonw_eor(self);
putc('"', self->out);
vfprintf(self->out, fmt, ap);
putc('"', self->out);
}
void jsonw_printf(json_writer_t *self, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
jsonw_eor(self);
vfprintf(self->out, fmt, ap);
va_end(ap);
}
void jsonw_start_object(json_writer_t *self)
{
jsonw_begin(self, '{');
}
void jsonw_end_object(json_writer_t *self)
{
jsonw_end(self, '}');
}
void jsonw_start_array(json_writer_t *self)
{
jsonw_begin(self, '[');
}
void jsonw_end_array(json_writer_t *self)
{
jsonw_end(self, ']');
}
void jsonw_string(json_writer_t *self, const char *value)
{
jsonw_eor(self);
jsonw_puts(self, value);
}
void jsonw_bool(json_writer_t *self, bool val)
{
jsonw_printf(self, "%s", val ? "true" : "false");
}
void jsonw_null(json_writer_t *self)
{
jsonw_printf(self, "null");
}
void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num)
{
jsonw_printf(self, fmt, num);
}
void jsonw_float(json_writer_t *self, double num)
{
jsonw_printf(self, "%g", num);
}
void jsonw_hu(json_writer_t *self, unsigned short num)
{
jsonw_printf(self, "%hu", num);
}
void jsonw_uint(json_writer_t *self, uint64_t num)
{
jsonw_printf(self, "%"PRIu64, num);
}
void jsonw_lluint(json_writer_t *self, unsigned long long int num)
{
jsonw_printf(self, "%llu", num);
}
void jsonw_int(json_writer_t *self, int64_t num)
{
jsonw_printf(self, "%"PRId64, num);
}
void jsonw_string_field(json_writer_t *self, const char *prop, const char *val)
{
jsonw_name(self, prop);
jsonw_string(self, val);
}
void jsonw_bool_field(json_writer_t *self, const char *prop, bool val)
{
jsonw_name(self, prop);
jsonw_bool(self, val);
}
void jsonw_float_field(json_writer_t *self, const char *prop, double val)
{
jsonw_name(self, prop);
jsonw_float(self, val);
}
void jsonw_float_field_fmt(json_writer_t *self,
const char *prop,
const char *fmt,
double val)
{
jsonw_name(self, prop);
jsonw_float_fmt(self, fmt, val);
}
void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num)
{
jsonw_name(self, prop);
jsonw_uint(self, num);
}
void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num)
{
jsonw_name(self, prop);
jsonw_hu(self, num);
}
void jsonw_lluint_field(json_writer_t *self,
const char *prop,
unsigned long long int num)
{
jsonw_name(self, prop);
jsonw_lluint(self, num);
}
void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num)
{
jsonw_name(self, prop);
jsonw_int(self, num);
}
void jsonw_null_field(json_writer_t *self, const char *prop)
{
jsonw_name(self, prop);
jsonw_null(self);
}
@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/*
* Simple streaming JSON writer
*
* This takes care of the annoying bits of JSON syntax like the commas
* after elements
*
* Authors: Stephen Hemminger <stephen@networkplumber.org>
*/
#ifndef _JSON_WRITER_H_
#define _JSON_WRITER_H_
#include <stdbool.h>
#include <stdint.h>
#include <stdarg.h>
#include <stdio.h>
/* Opaque class structure */
typedef struct json_writer json_writer_t;
/* Create a new JSON stream */
json_writer_t *jsonw_new(FILE *f);
/* End output to JSON stream */
void jsonw_destroy(json_writer_t **self_p);
/* Cause output to have pretty whitespace */
void jsonw_pretty(json_writer_t *self, bool on);
/* Reset separator to create new JSON */
void jsonw_reset(json_writer_t *self);
/* Add property name */
void jsonw_name(json_writer_t *self, const char *name);
/* Add value */
void __attribute__((format(printf, 2, 0))) jsonw_vprintf_enquote(json_writer_t *self,
const char *fmt,
va_list ap);
void __attribute__((format(printf, 2, 3))) jsonw_printf(json_writer_t *self,
const char *fmt, ...);
void jsonw_string(json_writer_t *self, const char *value);
void jsonw_bool(json_writer_t *self, bool value);
void jsonw_float(json_writer_t *self, double number);
void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num);
void jsonw_uint(json_writer_t *self, uint64_t number);
void jsonw_hu(json_writer_t *self, unsigned short number);
void jsonw_int(json_writer_t *self, int64_t number);
void jsonw_null(json_writer_t *self);
void jsonw_lluint(json_writer_t *self, unsigned long long int num);
/* Useful Combinations of name and value */
void jsonw_string_field(json_writer_t *self, const char *prop, const char *val);
void jsonw_bool_field(json_writer_t *self, const char *prop, bool value);
void jsonw_float_field(json_writer_t *self, const char *prop, double num);
void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num);
void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num);
void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num);
void jsonw_null_field(json_writer_t *self, const char *prop);
void jsonw_lluint_field(json_writer_t *self, const char *prop,
unsigned long long int num);
void jsonw_float_field_fmt(json_writer_t *self, const char *prop,
const char *fmt, double val);
/* Collections */
void jsonw_start_object(json_writer_t *self);
void jsonw_end_object(json_writer_t *self);
void jsonw_start_array(json_writer_t *self);
void jsonw_end_array(json_writer_t *self);
/* Override default exception handling */
typedef void (jsonw_err_handler_fn)(const char *);
#endif /* _JSON_WRITER_H_ */
+242
View File
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright (C) 2017-2018 Netronome Systems, Inc. */
/* Copyright Meta Platforms, Inc. and affiliates */
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "main.h"
const char *bin_name;
static int last_argc;
static char **last_argv;
static int (*last_do_help)(int argc, char **argv);
json_writer_t *json_wtr;
bool pretty_output;
bool json_output;
static void __attribute__((noreturn)) clean_and_exit(int i)
{
if (json_output)
jsonw_destroy(&json_wtr);
exit(i);
}
void usage(void)
{
last_do_help(last_argc - 1, last_argv + 1);
clean_and_exit(-1);
}
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 [OPTIONS] OBJECT { COMMAND | help }\n"
" %s version\n"
"\n"
" OBJECT := { page-pool | qstats }\n"
" " HELP_SPEC_OPTIONS "\n"
"",
bin_name, bin_name);
return 0;
}
static int do_version(int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
if (json_output) {
jsonw_start_object(json_wtr);
jsonw_name(json_wtr, "version");
jsonw_printf(json_wtr, SRC_VERSION);
jsonw_end_object(json_wtr);
} else {
printf("%s " SRC_VERSION "\n", bin_name);
}
return 0;
}
static const struct cmd commands[] = {
{ "help", do_help },
{ "page-pool", do_page_pool },
{ "qstats", do_qstats },
{ "version", do_version },
{ 0 }
};
int cmd_select(const struct cmd *cmds, int argc, char **argv,
int (*help)(int argc, char **argv))
{
unsigned int i;
last_argc = argc;
last_argv = argv;
last_do_help = help;
if (argc < 1 && cmds[0].func)
return cmds[0].func(argc, argv);
for (i = 0; cmds[i].cmd; i++) {
if (is_prefix(*argv, cmds[i].cmd)) {
if (!cmds[i].func) {
p_err("command '%s' is not available", cmds[i].cmd);
return -1;
}
return cmds[i].func(argc - 1, argv + 1);
}
}
help(argc - 1, argv + 1);
return -1;
}
bool is_prefix(const char *pfx, const char *str)
{
if (!pfx)
return false;
if (strlen(str) < strlen(pfx))
return false;
return !memcmp(str, pfx, strlen(pfx));
}
/* Last argument MUST be NULL pointer */
int detect_common_prefix(const char *arg, ...)
{
unsigned int count = 0;
const char *ref;
char msg[256];
va_list ap;
snprintf(msg, sizeof(msg), "ambiguous prefix: '%s' could be '", arg);
va_start(ap, arg);
while ((ref = va_arg(ap, const char *))) {
if (!is_prefix(arg, ref))
continue;
count++;
if (count > 1)
strncat(msg, "' or '", sizeof(msg) - strlen(msg) - 1);
strncat(msg, ref, sizeof(msg) - strlen(msg) - 1);
}
va_end(ap);
strncat(msg, "'", sizeof(msg) - strlen(msg) - 1);
if (count >= 2) {
p_err("%s", msg);
return -1;
}
return 0;
}
void p_err(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (json_output) {
jsonw_start_object(json_wtr);
jsonw_name(json_wtr, "error");
jsonw_vprintf_enquote(json_wtr, fmt, ap);
jsonw_end_object(json_wtr);
} else {
fprintf(stderr, "Error: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
}
va_end(ap);
}
void p_info(const char *fmt, ...)
{
va_list ap;
if (json_output)
return;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
int main(int argc, char **argv)
{
static const struct option options[] = {
{ "json", no_argument, NULL, 'j' },
{ "help", no_argument, NULL, 'h' },
{ "pretty", no_argument, NULL, 'p' },
{ "version", no_argument, NULL, 'V' },
{ 0 }
};
bool version_requested = false;
int opt, ret;
setlinebuf(stdout);
last_do_help = do_help;
pretty_output = false;
json_output = false;
bin_name = "ynltool";
opterr = 0;
while ((opt = getopt_long(argc, argv, "Vhjp",
options, NULL)) >= 0) {
switch (opt) {
case 'V':
version_requested = true;
break;
case 'h':
return do_help(argc, argv);
case 'p':
pretty_output = true;
/* fall through */
case 'j':
if (!json_output) {
json_wtr = jsonw_new(stdout);
if (!json_wtr) {
p_err("failed to create JSON writer");
return -1;
}
json_output = true;
}
jsonw_pretty(json_wtr, pretty_output);
break;
default:
p_err("unrecognized option '%s'", argv[optind - 1]);
if (json_output)
clean_and_exit(-1);
else
usage();
}
}
argc -= optind;
argv += optind;
if (argc < 0)
usage();
if (version_requested)
ret = do_version(argc, argv);
else
ret = cmd_select(commands, argc, argv, do_help);
if (json_output)
jsonw_destroy(&json_wtr);
return ret;
}
+66
View File
@@ -0,0 +1,66 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/* Copyright (C) 2017-2018 Netronome Systems, Inc. */
/* Copyright Meta Platforms, Inc. and affiliates */
#ifndef __YNLTOOL_H
#define __YNLTOOL_H
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "json_writer.h"
#define NEXT_ARG() ({ argc--; argv++; if (argc < 0) usage(); })
#define NEXT_ARGP() ({ (*argc)--; (*argv)++; if (*argc < 0) usage(); })
#define BAD_ARG() ({ p_err("what is '%s'?", *argv); -1; })
#define GET_ARG() ({ argc--; *argv++; })
#define REQ_ARGS(cnt) \
({ \
int _cnt = (cnt); \
bool _res; \
\
if (argc < _cnt) { \
p_err("'%s' needs at least %d arguments, %d found", \
argv[-1], _cnt, argc); \
_res = false; \
} else { \
_res = true; \
} \
_res; \
})
#define HELP_SPEC_OPTIONS \
"OPTIONS := { {-j|--json} [{-p|--pretty}] }"
extern const char *bin_name;
extern json_writer_t *json_wtr;
extern bool json_output;
extern bool pretty_output;
void __attribute__((format(printf, 1, 2))) p_err(const char *fmt, ...);
void __attribute__((format(printf, 1, 2))) p_info(const char *fmt, ...);
bool is_prefix(const char *pfx, const char *str);
int detect_common_prefix(const char *arg, ...);
void usage(void) __attribute__((noreturn));
struct cmd {
const char *cmd;
int (*func)(int argc, char **argv);
};
int cmd_select(const struct cmd *cmds, int argc, char **argv,
int (*help)(int argc, char **argv));
/* subcommands */
int do_page_pool(int argc, char **argv);
int do_qstats(int argc, char **argv);
#endif /* __YNLTOOL_H */
@@ -0,0 +1,463 @@
// 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"
struct pp_stat {
unsigned int ifc;
struct {
unsigned int cnt;
size_t refs, bytes;
} live[2];
size_t alloc_slow, alloc_fast, recycle_ring, recycle_cache;
};
struct pp_stats_array {
unsigned int i, max;
struct pp_stat *s;
};
static struct pp_stat *find_ifc(struct pp_stats_array *a, unsigned int ifindex)
{
unsigned int i;
for (i = 0; i < a->i; i++) {
if (a->s[i].ifc == ifindex)
return &a->s[i];
}
a->i++;
if (a->i == a->max) {
a->max *= 2;
a->s = reallocarray(a->s, a->max, sizeof(*a->s));
}
a->s[i].ifc = ifindex;
return &a->s[i];
}
static void count_pool(struct pp_stat *s, unsigned int l,
struct netdev_page_pool_get_rsp *pp)
{
s->live[l].cnt++;
if (pp->_present.inflight)
s->live[l].refs += pp->inflight;
if (pp->_present.inflight_mem)
s->live[l].bytes += pp->inflight_mem;
}
/* We don't know how many pages are sitting in cache and ring
* so we will under-count the recycling rate a bit.
*/
static void print_json_recycling_stats(struct pp_stat *s)
{
double recycle;
if (s->alloc_fast + s->alloc_slow) {
recycle = (double)(s->recycle_ring + s->recycle_cache) /
(s->alloc_fast + s->alloc_slow) * 100;
jsonw_float_field(json_wtr, "recycling_pct", recycle);
}
jsonw_name(json_wtr, "alloc");
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "slow", s->alloc_slow);
jsonw_uint_field(json_wtr, "fast", s->alloc_fast);
jsonw_end_object(json_wtr);
jsonw_name(json_wtr, "recycle");
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "ring", s->recycle_ring);
jsonw_uint_field(json_wtr, "cache", s->recycle_cache);
jsonw_end_object(json_wtr);
}
static void print_plain_recycling_stats(struct pp_stat *s)
{
double recycle;
if (s->alloc_fast + s->alloc_slow) {
recycle = (double)(s->recycle_ring + s->recycle_cache) /
(s->alloc_fast + s->alloc_slow) * 100;
printf("recycling: %.1lf%% (alloc: %zu:%zu recycle: %zu:%zu)",
recycle, s->alloc_slow, s->alloc_fast,
s->recycle_ring, s->recycle_cache);
}
}
static void print_json_stats(struct pp_stats_array *a)
{
jsonw_start_array(json_wtr);
for (unsigned int i = 0; i < a->i; i++) {
char ifname[IF_NAMESIZE];
struct pp_stat *s = &a->s[i];
const char *name;
jsonw_start_object(json_wtr);
if (!s->ifc) {
jsonw_string_field(json_wtr, "ifname", "<orphan>");
jsonw_uint_field(json_wtr, "ifindex", 0);
} else {
name = if_indextoname(s->ifc, ifname);
if (name)
jsonw_string_field(json_wtr, "ifname", name);
jsonw_uint_field(json_wtr, "ifindex", s->ifc);
}
jsonw_uint_field(json_wtr, "page_pools", s->live[1].cnt);
jsonw_uint_field(json_wtr, "zombies", s->live[0].cnt);
jsonw_name(json_wtr, "live");
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "refs", s->live[1].refs);
jsonw_uint_field(json_wtr, "bytes", s->live[1].bytes);
jsonw_end_object(json_wtr);
jsonw_name(json_wtr, "zombie");
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "refs", s->live[0].refs);
jsonw_uint_field(json_wtr, "bytes", s->live[0].bytes);
jsonw_end_object(json_wtr);
if (s->alloc_fast || s->alloc_slow)
print_json_recycling_stats(s);
jsonw_end_object(json_wtr);
}
jsonw_end_array(json_wtr);
}
static void print_plain_stats(struct pp_stats_array *a)
{
for (unsigned int i = 0; i < a->i; i++) {
char ifname[IF_NAMESIZE];
struct pp_stat *s = &a->s[i];
const char *name;
if (!s->ifc) {
printf("<orphan>\t");
} else {
name = if_indextoname(s->ifc, ifname);
if (name)
printf("%8s", name);
printf("[%u]\t", s->ifc);
}
printf("page pools: %u (zombies: %u)\n",
s->live[1].cnt, s->live[0].cnt);
printf("\t\trefs: %zu bytes: %zu (refs: %zu bytes: %zu)\n",
s->live[1].refs, s->live[1].bytes,
s->live[0].refs, s->live[0].bytes);
if (s->alloc_fast || s->alloc_slow) {
printf("\t\t");
print_plain_recycling_stats(s);
printf("\n");
}
}
}
static bool
find_pool_stat_in_list(struct netdev_page_pool_stats_get_list *pp_stats,
__u64 pool_id, struct pp_stat *pstat)
{
ynl_dump_foreach(pp_stats, pp) {
if (!pp->_present.info || !pp->info._present.id)
continue;
if (pp->info.id != pool_id)
continue;
memset(pstat, 0, sizeof(*pstat));
if (pp->_present.alloc_fast)
pstat->alloc_fast = pp->alloc_fast;
if (pp->_present.alloc_refill)
pstat->alloc_fast += pp->alloc_refill;
if (pp->_present.alloc_slow)
pstat->alloc_slow = pp->alloc_slow;
if (pp->_present.recycle_ring)
pstat->recycle_ring = pp->recycle_ring;
if (pp->_present.recycle_cached)
pstat->recycle_cache = pp->recycle_cached;
return true;
}
return false;
}
static void
print_json_pool_list(struct netdev_page_pool_get_list *pools,
struct netdev_page_pool_stats_get_list *pp_stats,
bool zombies_only)
{
jsonw_start_array(json_wtr);
ynl_dump_foreach(pools, pp) {
char ifname[IF_NAMESIZE];
struct pp_stat pstat;
const char *name;
if (zombies_only && !pp->_present.detach_time)
continue;
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "id", pp->id);
if (pp->_present.ifindex) {
name = if_indextoname(pp->ifindex, ifname);
if (name)
jsonw_string_field(json_wtr, "ifname", name);
jsonw_uint_field(json_wtr, "ifindex", pp->ifindex);
}
if (pp->_present.napi_id)
jsonw_uint_field(json_wtr, "napi_id", pp->napi_id);
if (pp->_present.inflight)
jsonw_uint_field(json_wtr, "refs", pp->inflight);
if (pp->_present.inflight_mem)
jsonw_uint_field(json_wtr, "bytes", pp->inflight_mem);
if (pp->_present.detach_time)
jsonw_uint_field(json_wtr, "detach_time", pp->detach_time);
if (pp->_present.dmabuf)
jsonw_uint_field(json_wtr, "dmabuf", pp->dmabuf);
if (find_pool_stat_in_list(pp_stats, pp->id, &pstat) &&
(pstat.alloc_fast || pstat.alloc_slow))
print_json_recycling_stats(&pstat);
jsonw_end_object(json_wtr);
}
jsonw_end_array(json_wtr);
}
static void
print_plain_pool_list(struct netdev_page_pool_get_list *pools,
struct netdev_page_pool_stats_get_list *pp_stats,
bool zombies_only)
{
ynl_dump_foreach(pools, pp) {
char ifname[IF_NAMESIZE];
struct pp_stat pstat;
const char *name;
if (zombies_only && !pp->_present.detach_time)
continue;
printf("pool id: %llu", pp->id);
if (pp->_present.ifindex) {
name = if_indextoname(pp->ifindex, ifname);
if (name)
printf(" dev: %s", name);
printf("[%u]", pp->ifindex);
}
if (pp->_present.napi_id)
printf(" napi: %llu", pp->napi_id);
printf("\n");
if (pp->_present.inflight || pp->_present.inflight_mem) {
printf(" inflight:");
if (pp->_present.inflight)
printf(" %llu pages", pp->inflight);
if (pp->_present.inflight_mem)
printf(" %llu bytes", pp->inflight_mem);
printf("\n");
}
if (pp->_present.detach_time)
printf(" detached: %llu\n", pp->detach_time);
if (pp->_present.dmabuf)
printf(" dmabuf: %u\n", pp->dmabuf);
if (find_pool_stat_in_list(pp_stats, pp->id, &pstat) &&
(pstat.alloc_fast || pstat.alloc_slow)) {
printf(" ");
print_plain_recycling_stats(&pstat);
printf("\n");
}
}
}
static void aggregate_device_stats(struct pp_stats_array *a,
struct netdev_page_pool_get_list *pools,
struct netdev_page_pool_stats_get_list *pp_stats)
{
ynl_dump_foreach(pools, pp) {
struct pp_stat *s = find_ifc(a, pp->ifindex);
count_pool(s, 1, pp);
if (pp->_present.detach_time)
count_pool(s, 0, pp);
}
ynl_dump_foreach(pp_stats, pp) {
struct pp_stat *s = find_ifc(a, pp->info.ifindex);
if (pp->_present.alloc_fast)
s->alloc_fast += pp->alloc_fast;
if (pp->_present.alloc_refill)
s->alloc_fast += pp->alloc_refill;
if (pp->_present.alloc_slow)
s->alloc_slow += pp->alloc_slow;
if (pp->_present.recycle_ring)
s->recycle_ring += pp->recycle_ring;
if (pp->_present.recycle_cached)
s->recycle_cache += pp->recycle_cached;
}
}
static int do_stats(int argc, char **argv)
{
struct netdev_page_pool_stats_get_req_dump pp_stat_req = {};
struct netdev_page_pool_stats_get_list *pp_stats;
struct netdev_page_pool_get_req_dump pp_req = {};
struct netdev_page_pool_get_list *pools;
enum {
GROUP_BY_DEVICE,
GROUP_BY_POOL,
} group_by = GROUP_BY_DEVICE;
bool zombies_only = false;
struct pp_stats_array a = {};
struct ynl_error yerr;
struct ynl_sock *ys;
int ret = 0;
/* Parse options */
while (argc > 0) {
if (is_prefix(*argv, "group-by")) {
NEXT_ARG();
if (!REQ_ARGS(1))
return -1;
if (is_prefix(*argv, "device")) {
group_by = GROUP_BY_DEVICE;
} else if (is_prefix(*argv, "pp") ||
is_prefix(*argv, "page-pool") ||
is_prefix(*argv, "none")) {
group_by = GROUP_BY_POOL;
} else {
p_err("invalid group-by value '%s'", *argv);
return -1;
}
NEXT_ARG();
} else if (is_prefix(*argv, "zombies")) {
zombies_only = true;
group_by = GROUP_BY_POOL;
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;
}
pools = netdev_page_pool_get_dump(ys, &pp_req);
if (!pools) {
p_err("failed to get page pools: %s", ys->err.msg);
ret = -1;
goto exit_close;
}
pp_stats = netdev_page_pool_stats_get_dump(ys, &pp_stat_req);
if (!pp_stats) {
p_err("failed to get page pool stats: %s", ys->err.msg);
ret = -1;
goto exit_free_pp_list;
}
/* If grouping by pool, print individual pools */
if (group_by == GROUP_BY_POOL) {
if (json_output)
print_json_pool_list(pools, pp_stats, zombies_only);
else
print_plain_pool_list(pools, pp_stats, zombies_only);
} else {
/* Aggregated stats mode (group-by device) */
a.max = 64;
a.s = calloc(a.max, sizeof(*a.s));
if (!a.s) {
p_err("failed to allocate stats array");
ret = -1;
goto exit_free_stats_list;
}
aggregate_device_stats(&a, pools, pp_stats);
if (json_output)
print_json_stats(&a);
else
print_plain_stats(&a);
free(a.s);
}
exit_free_stats_list:
netdev_page_pool_stats_get_list_free(pp_stats);
exit_free_pp_list:
netdev_page_pool_get_list_free(pools);
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 page-pool { COMMAND | help }\n"
" %s page-pool stats [ OPTIONS ]\n"
"\n"
" OPTIONS := { group-by { device | page-pool | none } | zombies }\n"
"\n"
" stats - Display page pool statistics\n"
" stats group-by device - Group statistics by network device (default)\n"
" stats group-by page-pool | pp | none\n"
" - Show individual page pool details (no grouping)\n"
" stats zombies - Show only zombie page pools (detached but with\n"
" pages in flight). Implies group-by page-pool.\n"
"",
bin_name, bin_name);
return 0;
}
static const struct cmd page_pool_cmds[] = {
{ "help", do_help },
{ "stats", do_stats },
{ 0 }
};
int do_page_pool(int argc, char **argv)
{
return cmd_select(page_pool_cmds, argc, argv, do_help);
}
+674
View File
@@ -0,0 +1,674 @@
// 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 <math.h>
#include <ynl.h>
#include "netdev-user.h"
#include "main.h"
static enum netdev_qstats_scope scope; /* default - device */
struct queue_balance {
unsigned int ifindex;
enum netdev_queue_type type;
unsigned int queue_count;
__u64 *rx_packets;
__u64 *rx_bytes;
__u64 *tx_packets;
__u64 *tx_bytes;
};
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 struct netdev_qstats_get_list *
qstats_dump(enum netdev_qstats_scope scope)
{
struct netdev_qstats_get_list *qstats;
struct netdev_qstats_get_req *req;
struct ynl_error yerr;
struct ynl_sock *ys;
ys = ynl_sock_create(&ynl_netdev_family, &yerr);
if (!ys) {
p_err("YNL: %s", yerr.msg);
return NULL;
}
req = netdev_qstats_get_req_alloc();
if (!req) {
p_err("failed to allocate qstats request");
goto err_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);
goto err_close;
}
ynl_sock_destroy(ys);
return qstats;
err_close:
ynl_sock_destroy(ys);
return NULL;
}
static int do_show(int argc, char **argv)
{
struct netdev_qstats_get_list *qstats;
/* 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;
}
}
qstats = qstats_dump(scope);
if (!qstats)
return -1;
/* 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);
return 0;
}
static void compute_stats(__u64 *values, unsigned int count,
double *mean, double *stddev, __u64 *min, __u64 *max)
{
double sum = 0.0, variance = 0.0;
unsigned int i;
*min = ~0ULL;
*max = 0;
if (count == 0) {
*mean = 0;
*stddev = 0;
*min = 0;
return;
}
for (i = 0; i < count; i++) {
sum += values[i];
if (values[i] < *min)
*min = values[i];
if (values[i] > *max)
*max = values[i];
}
*mean = sum / count;
if (count > 1) {
for (i = 0; i < count; i++) {
double diff = values[i] - *mean;
variance += diff * diff;
}
*stddev = sqrt(variance / (count - 1));
} else {
*stddev = 0;
}
}
static void print_balance_stats(const char *name, enum netdev_queue_type type,
__u64 *values, unsigned int count)
{
double mean, stddev, cv, ns;
__u64 min, max;
if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) ||
(name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX))
return;
compute_stats(values, count, &mean, &stddev, &min, &max);
cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0;
ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0;
printf(" %-12s: cv=%.1f%% ns=%.1f%% stddev=%.0f\n",
name, cv, ns, stddev);
printf(" %-12s min=%llu max=%llu mean=%.0f\n",
"", min, max, mean);
}
static void
print_balance_stats_json(const char *name, enum netdev_queue_type type,
__u64 *values, unsigned int count)
{
double mean, stddev, cv, ns;
__u64 min, max;
if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) ||
(name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX))
return;
compute_stats(values, count, &mean, &stddev, &min, &max);
cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0;
ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0;
jsonw_name(json_wtr, name);
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "queue-count", count);
jsonw_uint_field(json_wtr, "min", min);
jsonw_uint_field(json_wtr, "max", max);
jsonw_float_field(json_wtr, "mean", mean);
jsonw_float_field(json_wtr, "stddev", stddev);
jsonw_float_field(json_wtr, "coefficient-of-variation", cv);
jsonw_float_field(json_wtr, "normalized-spread", ns);
jsonw_end_object(json_wtr);
}
static int cmp_ifindex_type(const void *a, const void *b)
{
const struct netdev_qstats_get_rsp *qa = a;
const struct netdev_qstats_get_rsp *qb = b;
if (qa->ifindex != qb->ifindex)
return qa->ifindex - qb->ifindex;
if (qa->queue_type != qb->queue_type)
return qa->queue_type - qb->queue_type;
return qa->queue_id - qb->queue_id;
}
static int do_balance(int argc, char **argv __attribute__((unused)))
{
struct netdev_qstats_get_list *qstats;
struct netdev_qstats_get_rsp **sorted;
unsigned int count = 0;
unsigned int i, j;
int ret = 0;
if (argc > 0) {
p_err("balance command takes no arguments");
return -1;
}
qstats = qstats_dump(NETDEV_QSTATS_SCOPE_QUEUE);
if (!qstats)
return -1;
/* Count and sort queues */
ynl_dump_foreach(qstats, qs)
count++;
if (count == 0) {
if (json_output)
jsonw_start_array(json_wtr);
else
printf("No queue statistics available\n");
goto exit_free_qstats;
}
sorted = calloc(count, sizeof(*sorted));
if (!sorted) {
p_err("failed to allocate sorted array");
ret = -1;
goto exit_free_qstats;
}
i = 0;
ynl_dump_foreach(qstats, qs)
sorted[i++] = qs;
qsort(sorted, count, sizeof(*sorted), cmp_ifindex_type);
if (json_output)
jsonw_start_array(json_wtr);
/* Process each device/queue-type combination */
i = 0;
while (i < count) {
__u64 *rx_packets, *rx_bytes, *tx_packets, *tx_bytes;
enum netdev_queue_type type = sorted[i]->queue_type;
unsigned int ifindex = sorted[i]->ifindex;
unsigned int queue_count = 0;
char ifname[IF_NAMESIZE];
const char *name;
/* Count queues for this device/type */
for (j = i; j < count && sorted[j]->ifindex == ifindex &&
sorted[j]->queue_type == type; j++)
queue_count++;
/* Skip if no packets/bytes (inactive queues) */
if (!sorted[i]->_present.rx_packets &&
!sorted[i]->_present.rx_bytes &&
!sorted[i]->_present.tx_packets &&
!sorted[i]->_present.tx_bytes)
goto next_ifc;
/* Allocate arrays for statistics */
rx_packets = calloc(queue_count, sizeof(*rx_packets));
rx_bytes = calloc(queue_count, sizeof(*rx_bytes));
tx_packets = calloc(queue_count, sizeof(*tx_packets));
tx_bytes = calloc(queue_count, sizeof(*tx_bytes));
if (!rx_packets || !rx_bytes || !tx_packets || !tx_bytes) {
p_err("failed to allocate statistics arrays");
free(rx_packets);
free(rx_bytes);
free(tx_packets);
free(tx_bytes);
ret = -1;
goto exit_free_sorted;
}
/* Collect statistics */
for (j = 0; j < queue_count; j++) {
rx_packets[j] = sorted[i + j]->_present.rx_packets ?
sorted[i + j]->rx_packets : 0;
rx_bytes[j] = sorted[i + j]->_present.rx_bytes ?
sorted[i + j]->rx_bytes : 0;
tx_packets[j] = sorted[i + j]->_present.tx_packets ?
sorted[i + j]->tx_packets : 0;
tx_bytes[j] = sorted[i + j]->_present.tx_bytes ?
sorted[i + j]->tx_bytes : 0;
}
name = if_indextoname(ifindex, ifname);
if (json_output) {
jsonw_start_object(json_wtr);
if (name)
jsonw_string_field(json_wtr, "ifname", name);
jsonw_uint_field(json_wtr, "ifindex", ifindex);
jsonw_string_field(json_wtr, "queue-type",
netdev_queue_type_str(type));
print_balance_stats_json("rx-packets", type,
rx_packets, queue_count);
print_balance_stats_json("rx-bytes", type,
rx_bytes, queue_count);
print_balance_stats_json("tx-packets", type,
tx_packets, queue_count);
print_balance_stats_json("tx-bytes", type,
tx_bytes, queue_count);
jsonw_end_object(json_wtr);
} else {
if (name)
printf("%s", name);
else
printf("ifindex:%u", ifindex);
printf(" %s %d queues:\n",
netdev_queue_type_str(type), queue_count);
print_balance_stats("rx-packets", type,
rx_packets, queue_count);
print_balance_stats("rx-bytes", type,
rx_bytes, queue_count);
print_balance_stats("tx-packets", type,
tx_packets, queue_count);
print_balance_stats("tx-bytes", type,
tx_bytes, queue_count);
printf("\n");
}
free(rx_packets);
free(rx_bytes);
free(tx_packets);
free(tx_bytes);
next_ifc:
i += queue_count;
}
if (json_output)
jsonw_end_array(json_wtr);
exit_free_sorted:
free(sorted);
exit_free_qstats:
netdev_qstats_get_list_free(qstats);
return ret;
}
static int do_hw_gro(int argc, char **argv __attribute__((unused)))
{
struct netdev_qstats_get_list *qstats;
if (argc > 0) {
p_err("hw-gro command takes no arguments");
return -1;
}
qstats = qstats_dump(0);
if (!qstats)
return -1;
if (json_output)
jsonw_start_array(json_wtr);
ynl_dump_foreach(qstats, qs) {
char ifname[IF_NAMESIZE];
const char *name;
double savings;
if (!qs->_present.rx_packets ||
!qs->_present.rx_hw_gro_packets ||
!qs->_present.rx_hw_gro_wire_packets)
continue;
if (!qs->rx_packets)
continue;
/* How many skbs did we avoid allocating thanks to HW GRO */
savings = (double)(qs->rx_hw_gro_wire_packets -
qs->rx_hw_gro_packets) /
qs->rx_packets * 100.0;
name = if_indextoname(qs->ifindex, ifname);
if (json_output) {
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "ifindex", qs->ifindex);
if (name)
jsonw_string_field(json_wtr, "ifname", name);
jsonw_float_field(json_wtr, "savings", savings);
jsonw_end_object(json_wtr);
} else {
if (name)
printf("%s", name);
else
printf("ifindex:%u", qs->ifindex);
printf(": %.1f%% savings\n", savings);
}
}
if (json_output)
jsonw_end_array(json_wtr);
netdev_qstats_get_list_free(qstats);
return 0;
}
static int do_help(int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
if (json_output) {
jsonw_null(json_wtr);
return 0;
}
fprintf(stderr,
"Usage: %1$s qstats { COMMAND | help }\n"
" %1$s qstats [ show ] [ OPTIONS ]\n"
" %1$s qstats balance\n"
" %1$s qstats hw-gro\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"
"\n"
" Analysis:\n"
" balance - Traffic distribution between queues.\n"
" hw-gro - HW GRO effectiveness analysis\n"
" - savings - delta between packets received\n"
" on the wire and packets seen by the kernel.\n"
"",
bin_name);
return 0;
}
static const struct cmd qstats_cmds[] = {
{ "show", do_show },
{ "balance", do_balance },
{ "hw-gro", do_hw_gro },
{ "help", do_help },
{ 0 }
};
int do_qstats(int argc, char **argv)
{
return cmd_select(qstats_cmds, argc, argv, do_help);
}