| // 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("[%d]\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; |
| } |