1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <errno.h> 7 #include <net/if.h> 8 9 #include <ynl.h> 10 #include "netdev-user.h" 11 12 #include "main.h" 13 14 struct pp_stat { 15 unsigned int ifc; 16 17 struct { 18 unsigned int cnt; 19 size_t refs, bytes; 20 } live[2]; 21 22 size_t alloc_slow, alloc_fast, recycle_ring, recycle_cache; 23 }; 24 25 struct pp_stats_array { 26 unsigned int i, max; 27 struct pp_stat *s; 28 }; 29 30 static struct pp_stat *find_ifc(struct pp_stats_array *a, unsigned int ifindex) 31 { 32 unsigned int i; 33 34 for (i = 0; i < a->i; i++) { 35 if (a->s[i].ifc == ifindex) 36 return &a->s[i]; 37 } 38 39 a->i++; 40 if (a->i == a->max) { 41 a->max *= 2; 42 a->s = reallocarray(a->s, a->max, sizeof(*a->s)); 43 } 44 a->s[i].ifc = ifindex; 45 return &a->s[i]; 46 } 47 48 static void count_pool(struct pp_stat *s, unsigned int l, 49 struct netdev_page_pool_get_rsp *pp) 50 { 51 s->live[l].cnt++; 52 if (pp->_present.inflight) 53 s->live[l].refs += pp->inflight; 54 if (pp->_present.inflight_mem) 55 s->live[l].bytes += pp->inflight_mem; 56 } 57 58 /* We don't know how many pages are sitting in cache and ring 59 * so we will under-count the recycling rate a bit. 60 */ 61 static void print_json_recycling_stats(struct pp_stat *s) 62 { 63 double recycle; 64 65 if (s->alloc_fast + s->alloc_slow) { 66 recycle = (double)(s->recycle_ring + s->recycle_cache) / 67 (s->alloc_fast + s->alloc_slow) * 100; 68 jsonw_float_field(json_wtr, "recycling_pct", recycle); 69 } 70 71 jsonw_name(json_wtr, "alloc"); 72 jsonw_start_object(json_wtr); 73 jsonw_uint_field(json_wtr, "slow", s->alloc_slow); 74 jsonw_uint_field(json_wtr, "fast", s->alloc_fast); 75 jsonw_end_object(json_wtr); 76 77 jsonw_name(json_wtr, "recycle"); 78 jsonw_start_object(json_wtr); 79 jsonw_uint_field(json_wtr, "ring", s->recycle_ring); 80 jsonw_uint_field(json_wtr, "cache", s->recycle_cache); 81 jsonw_end_object(json_wtr); 82 } 83 84 static void print_plain_recycling_stats(struct pp_stat *s) 85 { 86 double recycle; 87 88 if (s->alloc_fast + s->alloc_slow) { 89 recycle = (double)(s->recycle_ring + s->recycle_cache) / 90 (s->alloc_fast + s->alloc_slow) * 100; 91 printf("recycling: %.1lf%% (alloc: %zu:%zu recycle: %zu:%zu)", 92 recycle, s->alloc_slow, s->alloc_fast, 93 s->recycle_ring, s->recycle_cache); 94 } 95 } 96 97 static void print_json_stats(struct pp_stats_array *a) 98 { 99 jsonw_start_array(json_wtr); 100 101 for (unsigned int i = 0; i < a->i; i++) { 102 char ifname[IF_NAMESIZE]; 103 struct pp_stat *s = &a->s[i]; 104 const char *name; 105 106 jsonw_start_object(json_wtr); 107 108 if (!s->ifc) { 109 jsonw_string_field(json_wtr, "ifname", "<orphan>"); 110 jsonw_uint_field(json_wtr, "ifindex", 0); 111 } else { 112 name = if_indextoname(s->ifc, ifname); 113 if (name) 114 jsonw_string_field(json_wtr, "ifname", name); 115 jsonw_uint_field(json_wtr, "ifindex", s->ifc); 116 } 117 118 jsonw_uint_field(json_wtr, "page_pools", s->live[1].cnt); 119 jsonw_uint_field(json_wtr, "zombies", s->live[0].cnt); 120 121 jsonw_name(json_wtr, "live"); 122 jsonw_start_object(json_wtr); 123 jsonw_uint_field(json_wtr, "refs", s->live[1].refs); 124 jsonw_uint_field(json_wtr, "bytes", s->live[1].bytes); 125 jsonw_end_object(json_wtr); 126 127 jsonw_name(json_wtr, "zombie"); 128 jsonw_start_object(json_wtr); 129 jsonw_uint_field(json_wtr, "refs", s->live[0].refs); 130 jsonw_uint_field(json_wtr, "bytes", s->live[0].bytes); 131 jsonw_end_object(json_wtr); 132 133 if (s->alloc_fast || s->alloc_slow) 134 print_json_recycling_stats(s); 135 136 jsonw_end_object(json_wtr); 137 } 138 139 jsonw_end_array(json_wtr); 140 } 141 142 static void print_plain_stats(struct pp_stats_array *a) 143 { 144 for (unsigned int i = 0; i < a->i; i++) { 145 char ifname[IF_NAMESIZE]; 146 struct pp_stat *s = &a->s[i]; 147 const char *name; 148 149 if (!s->ifc) { 150 printf("<orphan>\t"); 151 } else { 152 name = if_indextoname(s->ifc, ifname); 153 if (name) 154 printf("%8s", name); 155 printf("[%u]\t", s->ifc); 156 } 157 158 printf("page pools: %u (zombies: %u)\n", 159 s->live[1].cnt, s->live[0].cnt); 160 printf("\t\trefs: %zu bytes: %zu (refs: %zu bytes: %zu)\n", 161 s->live[1].refs, s->live[1].bytes, 162 s->live[0].refs, s->live[0].bytes); 163 164 if (s->alloc_fast || s->alloc_slow) { 165 printf("\t\t"); 166 print_plain_recycling_stats(s); 167 printf("\n"); 168 } 169 } 170 } 171 172 static bool 173 find_pool_stat_in_list(struct netdev_page_pool_stats_get_list *pp_stats, 174 __u64 pool_id, struct pp_stat *pstat) 175 { 176 ynl_dump_foreach(pp_stats, pp) { 177 if (!pp->_present.info || !pp->info._present.id) 178 continue; 179 if (pp->info.id != pool_id) 180 continue; 181 182 memset(pstat, 0, sizeof(*pstat)); 183 if (pp->_present.alloc_fast) 184 pstat->alloc_fast = pp->alloc_fast; 185 if (pp->_present.alloc_refill) 186 pstat->alloc_fast += pp->alloc_refill; 187 if (pp->_present.alloc_slow) 188 pstat->alloc_slow = pp->alloc_slow; 189 if (pp->_present.recycle_ring) 190 pstat->recycle_ring = pp->recycle_ring; 191 if (pp->_present.recycle_cached) 192 pstat->recycle_cache = pp->recycle_cached; 193 return true; 194 } 195 return false; 196 } 197 198 static void 199 print_json_pool_list(struct netdev_page_pool_get_list *pools, 200 struct netdev_page_pool_stats_get_list *pp_stats, 201 bool zombies_only) 202 { 203 jsonw_start_array(json_wtr); 204 205 ynl_dump_foreach(pools, pp) { 206 char ifname[IF_NAMESIZE]; 207 struct pp_stat pstat; 208 const char *name; 209 210 if (zombies_only && !pp->_present.detach_time) 211 continue; 212 213 jsonw_start_object(json_wtr); 214 215 jsonw_uint_field(json_wtr, "id", pp->id); 216 217 if (pp->_present.ifindex) { 218 name = if_indextoname(pp->ifindex, ifname); 219 if (name) 220 jsonw_string_field(json_wtr, "ifname", name); 221 jsonw_uint_field(json_wtr, "ifindex", pp->ifindex); 222 } 223 224 if (pp->_present.napi_id) 225 jsonw_uint_field(json_wtr, "napi_id", pp->napi_id); 226 227 if (pp->_present.inflight) 228 jsonw_uint_field(json_wtr, "refs", pp->inflight); 229 230 if (pp->_present.inflight_mem) 231 jsonw_uint_field(json_wtr, "bytes", pp->inflight_mem); 232 233 if (pp->_present.detach_time) 234 jsonw_uint_field(json_wtr, "detach_time", pp->detach_time); 235 236 if (pp->_present.dmabuf) 237 jsonw_uint_field(json_wtr, "dmabuf", pp->dmabuf); 238 239 if (find_pool_stat_in_list(pp_stats, pp->id, &pstat) && 240 (pstat.alloc_fast || pstat.alloc_slow)) 241 print_json_recycling_stats(&pstat); 242 243 jsonw_end_object(json_wtr); 244 } 245 246 jsonw_end_array(json_wtr); 247 } 248 249 static void 250 print_plain_pool_list(struct netdev_page_pool_get_list *pools, 251 struct netdev_page_pool_stats_get_list *pp_stats, 252 bool zombies_only) 253 { 254 ynl_dump_foreach(pools, pp) { 255 char ifname[IF_NAMESIZE]; 256 struct pp_stat pstat; 257 const char *name; 258 259 if (zombies_only && !pp->_present.detach_time) 260 continue; 261 262 printf("pool id: %llu", pp->id); 263 264 if (pp->_present.ifindex) { 265 name = if_indextoname(pp->ifindex, ifname); 266 if (name) 267 printf(" dev: %s", name); 268 printf("[%u]", pp->ifindex); 269 } 270 271 if (pp->_present.napi_id) 272 printf(" napi: %llu", pp->napi_id); 273 274 printf("\n"); 275 276 if (pp->_present.inflight || pp->_present.inflight_mem) { 277 printf(" inflight:"); 278 if (pp->_present.inflight) 279 printf(" %llu pages", pp->inflight); 280 if (pp->_present.inflight_mem) 281 printf(" %llu bytes", pp->inflight_mem); 282 printf("\n"); 283 } 284 285 if (pp->_present.detach_time) 286 printf(" detached: %llu\n", pp->detach_time); 287 288 if (pp->_present.dmabuf) 289 printf(" dmabuf: %u\n", pp->dmabuf); 290 291 if (find_pool_stat_in_list(pp_stats, pp->id, &pstat) && 292 (pstat.alloc_fast || pstat.alloc_slow)) { 293 printf(" "); 294 print_plain_recycling_stats(&pstat); 295 printf("\n"); 296 } 297 } 298 } 299 300 static void aggregate_device_stats(struct pp_stats_array *a, 301 struct netdev_page_pool_get_list *pools, 302 struct netdev_page_pool_stats_get_list *pp_stats) 303 { 304 ynl_dump_foreach(pools, pp) { 305 struct pp_stat *s = find_ifc(a, pp->ifindex); 306 307 count_pool(s, 1, pp); 308 if (pp->_present.detach_time) 309 count_pool(s, 0, pp); 310 } 311 312 ynl_dump_foreach(pp_stats, pp) { 313 struct pp_stat *s = find_ifc(a, pp->info.ifindex); 314 315 if (pp->_present.alloc_fast) 316 s->alloc_fast += pp->alloc_fast; 317 if (pp->_present.alloc_refill) 318 s->alloc_fast += pp->alloc_refill; 319 if (pp->_present.alloc_slow) 320 s->alloc_slow += pp->alloc_slow; 321 if (pp->_present.recycle_ring) 322 s->recycle_ring += pp->recycle_ring; 323 if (pp->_present.recycle_cached) 324 s->recycle_cache += pp->recycle_cached; 325 } 326 } 327 328 static int do_stats(int argc, char **argv) 329 { 330 struct netdev_page_pool_stats_get_req_dump pp_stat_req = {}; 331 struct netdev_page_pool_stats_get_list *pp_stats; 332 struct netdev_page_pool_get_req_dump pp_req = {}; 333 struct netdev_page_pool_get_list *pools; 334 enum { 335 GROUP_BY_DEVICE, 336 GROUP_BY_POOL, 337 } group_by = GROUP_BY_DEVICE; 338 bool zombies_only = false; 339 struct pp_stats_array a = {}; 340 struct ynl_error yerr; 341 struct ynl_sock *ys; 342 int ret = 0; 343 344 /* Parse options */ 345 while (argc > 0) { 346 if (is_prefix(*argv, "group-by")) { 347 NEXT_ARG(); 348 349 if (!REQ_ARGS(1)) 350 return -1; 351 352 if (is_prefix(*argv, "device")) { 353 group_by = GROUP_BY_DEVICE; 354 } else if (is_prefix(*argv, "pp") || 355 is_prefix(*argv, "page-pool") || 356 is_prefix(*argv, "none")) { 357 group_by = GROUP_BY_POOL; 358 } else { 359 p_err("invalid group-by value '%s'", *argv); 360 return -1; 361 } 362 NEXT_ARG(); 363 } else if (is_prefix(*argv, "zombies")) { 364 zombies_only = true; 365 group_by = GROUP_BY_POOL; 366 NEXT_ARG(); 367 } else { 368 p_err("unknown option '%s'", *argv); 369 return -1; 370 } 371 } 372 373 ys = ynl_sock_create(&ynl_netdev_family, &yerr); 374 if (!ys) { 375 p_err("YNL: %s", yerr.msg); 376 return -1; 377 } 378 379 pools = netdev_page_pool_get_dump(ys, &pp_req); 380 if (!pools) { 381 p_err("failed to get page pools: %s", ys->err.msg); 382 ret = -1; 383 goto exit_close; 384 } 385 386 pp_stats = netdev_page_pool_stats_get_dump(ys, &pp_stat_req); 387 if (!pp_stats) { 388 p_err("failed to get page pool stats: %s", ys->err.msg); 389 ret = -1; 390 goto exit_free_pp_list; 391 } 392 393 /* If grouping by pool, print individual pools */ 394 if (group_by == GROUP_BY_POOL) { 395 if (json_output) 396 print_json_pool_list(pools, pp_stats, zombies_only); 397 else 398 print_plain_pool_list(pools, pp_stats, zombies_only); 399 } else { 400 /* Aggregated stats mode (group-by device) */ 401 a.max = 64; 402 a.s = calloc(a.max, sizeof(*a.s)); 403 if (!a.s) { 404 p_err("failed to allocate stats array"); 405 ret = -1; 406 goto exit_free_stats_list; 407 } 408 409 aggregate_device_stats(&a, pools, pp_stats); 410 411 if (json_output) 412 print_json_stats(&a); 413 else 414 print_plain_stats(&a); 415 416 free(a.s); 417 } 418 419 exit_free_stats_list: 420 netdev_page_pool_stats_get_list_free(pp_stats); 421 exit_free_pp_list: 422 netdev_page_pool_get_list_free(pools); 423 exit_close: 424 ynl_sock_destroy(ys); 425 return ret; 426 } 427 428 static int do_help(int argc __attribute__((unused)), 429 char **argv __attribute__((unused))) 430 { 431 if (json_output) { 432 jsonw_null(json_wtr); 433 return 0; 434 } 435 436 fprintf(stderr, 437 "Usage: %s page-pool { COMMAND | help }\n" 438 " %s page-pool stats [ OPTIONS ]\n" 439 "\n" 440 " OPTIONS := { group-by { device | page-pool | none } | zombies }\n" 441 "\n" 442 " stats - Display page pool statistics\n" 443 " stats group-by device - Group statistics by network device (default)\n" 444 " stats group-by page-pool | pp | none\n" 445 " - Show individual page pool details (no grouping)\n" 446 " stats zombies - Show only zombie page pools (detached but with\n" 447 " pages in flight). Implies group-by page-pool.\n" 448 "", 449 bin_name, bin_name); 450 451 return 0; 452 } 453 454 static const struct cmd page_pool_cmds[] = { 455 { "help", do_help }, 456 { "stats", do_stats }, 457 { 0 } 458 }; 459 460 int do_page_pool(int argc, char **argv) 461 { 462 return cmd_select(page_pool_cmds, argc, argv, do_help); 463 } 464