mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
Merge branch 'tools-ynl-turn-the-page-pool-sample-into-a-real-tool'
Jakub Kicinski says: ==================== tools: ynl: turn the page-pool sample into a real tool The page-pool YNL sample is quite useful. It's helps calculate recycling rate and memory consumption. Since we still haven't figured out a way to integrate with iproute2 (not for the lack of thinking how to solve it) - create a ynltool command in ynl. Add page-pool and qstats support. Most commands can use the Python YNL CLI directly but low level stats often need aggregation or some math on top to be useful. Specifically in this patch set: - page pool stats are aggregated and recycling rate computed - per-queue stats are used to compute traffic balance across queues v1: https://lore.kernel.org/20251104232348.1954349-1-kuba@kernel.org ==================== Link: https://patch.msgid.link/20251107162227.980672-1-kuba@kernel.org Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
@@ -12,10 +12,11 @@ endif
|
||||
libdir ?= $(prefix)/$(libdir_relative)
|
||||
includedir ?= $(prefix)/include
|
||||
|
||||
SUBDIRS = lib generated samples
|
||||
SUBDIRS = lib generated samples ynltool
|
||||
|
||||
all: $(SUBDIRS) libynl.a
|
||||
|
||||
ynltool: | lib generated libynl.a
|
||||
samples: | lib generated
|
||||
libynl.a: | lib generated
|
||||
@echo -e "\tAR $@"
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <ynl.h>
|
||||
|
||||
#include <net/if.h>
|
||||
|
||||
#include "netdev-user.h"
|
||||
|
||||
struct 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 stats_array {
|
||||
unsigned int i, max;
|
||||
struct stat *s;
|
||||
};
|
||||
|
||||
static struct stat *find_ifc(struct 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(struct 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;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct netdev_page_pool_stats_get_list *pp_stats;
|
||||
struct netdev_page_pool_get_list *pools;
|
||||
struct stats_array a = {};
|
||||
struct ynl_error yerr;
|
||||
struct ynl_sock *ys;
|
||||
|
||||
ys = ynl_sock_create(&ynl_netdev_family, &yerr);
|
||||
if (!ys) {
|
||||
fprintf(stderr, "YNL: %s\n", yerr.msg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
a.max = 128;
|
||||
a.s = calloc(a.max, sizeof(*a.s));
|
||||
if (!a.s)
|
||||
goto err_close;
|
||||
|
||||
pools = netdev_page_pool_get_dump(ys);
|
||||
if (!pools)
|
||||
goto err_free;
|
||||
|
||||
ynl_dump_foreach(pools, pp) {
|
||||
struct stat *s = find_ifc(&a, pp->ifindex);
|
||||
|
||||
count(s, 1, pp);
|
||||
if (pp->_present.detach_time)
|
||||
count(s, 0, pp);
|
||||
}
|
||||
netdev_page_pool_get_list_free(pools);
|
||||
|
||||
pp_stats = netdev_page_pool_stats_get_dump(ys);
|
||||
if (!pp_stats)
|
||||
goto err_free;
|
||||
|
||||
ynl_dump_foreach(pp_stats, pp) {
|
||||
struct 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;
|
||||
}
|
||||
netdev_page_pool_stats_get_list_free(pp_stats);
|
||||
|
||||
for (unsigned int i = 0; i < a.i; i++) {
|
||||
char ifname[IF_NAMESIZE];
|
||||
struct stat *s = &a.s[i];
|
||||
const char *name;
|
||||
double recycle;
|
||||
|
||||
if (!s->ifc) {
|
||||
name = "<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);
|
||||
|
||||
/* We don't know how many pages are sitting in cache and ring
|
||||
* so we will under-count the recycling rate a bit.
|
||||
*/
|
||||
recycle = (double)(s->recycle_ring + s->recycle_cache) /
|
||||
(s->alloc_fast + s->alloc_slow) * 100;
|
||||
printf("\t\trecycling: %.1lf%% (alloc: %zu:%zu recycle: %zu:%zu)\n",
|
||||
recycle, s->alloc_slow, s->alloc_fast,
|
||||
s->recycle_ring, s->recycle_cache);
|
||||
}
|
||||
|
||||
ynl_sock_destroy(ys);
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
free(a.s);
|
||||
err_close:
|
||||
fprintf(stderr, "YNL: %s\n", ys->err.msg);
|
||||
ynl_sock_destroy(ys);
|
||||
return 2;
|
||||
}
|
||||
1
tools/net/ynl/ynltool/.gitignore
vendored
Normal file
1
tools/net/ynl/ynltool/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ynltool
|
||||
55
tools/net/ynl/ynltool/Makefile
Normal file
55
tools/net/ynl/ynltool/Makefile
Normal file
@@ -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 || \
|
||||
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 -lmnl -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
|
||||
288
tools/net/ynl/ynltool/json_writer.c
Normal file
288
tools/net/ynl/ynltool/json_writer.c
Normal file
@@ -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);
|
||||
}
|
||||
75
tools/net/ynl/ynltool/json_writer.h
Normal file
75
tools/net/ynl/ynltool/json_writer.h
Normal file
@@ -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
tools/net/ynl/ynltool/main.c
Normal file
242
tools/net/ynl/ynltool/main.c
Normal 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
tools/net/ynl/ynltool/main.h
Normal file
66
tools/net/ynl/ynltool/main.h
Normal 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 */
|
||||
461
tools/net/ynl/ynltool/page-pool.c
Normal file
461
tools/net/ynl/ynltool/page-pool.c
Normal file
@@ -0,0 +1,461 @@
|
||||
// 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_list *pp_stats;
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
621
tools/net/ynl/ynltool/qstats.c
Normal file
621
tools/net/ynl/ynltool/qstats.c
Normal file
@@ -0,0 +1,621 @@
|
||||
// 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 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 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_req *req;
|
||||
struct netdev_qstats_get_rsp **sorted;
|
||||
struct ynl_error yerr;
|
||||
struct ynl_sock *ys;
|
||||
unsigned int count = 0;
|
||||
unsigned int i, j;
|
||||
int ret = 0;
|
||||
|
||||
if (argc > 0) {
|
||||
p_err("balance command takes no arguments");
|
||||
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;
|
||||
}
|
||||
|
||||
/* Always use queue scope for balance analysis */
|
||||
netdev_qstats_get_req_set_scope(req, NETDEV_QSTATS_SCOPE_QUEUE);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
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"
|
||||
" %s qstats balance\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"
|
||||
" balance - Analyze traffic distribution balance.\n"
|
||||
"",
|
||||
bin_name, bin_name, bin_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct cmd qstats_cmds[] = {
|
||||
{ "show", do_show },
|
||||
{ "balance", do_balance },
|
||||
{ "help", do_help },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
int do_qstats(int argc, char **argv)
|
||||
{
|
||||
return cmd_select(qstats_cmds, argc, argv, do_help);
|
||||
}
|
||||
Reference in New Issue
Block a user