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
find_ifc(struct pp_stats_array * a,unsigned int ifindex)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
count_pool(struct pp_stat * s,unsigned int l,struct netdev_page_pool_get_rsp * pp)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 */
print_json_recycling_stats(struct pp_stat * s)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
print_plain_recycling_stats(struct pp_stat * s)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
print_json_stats(struct pp_stats_array * a)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
print_plain_stats(struct pp_stats_array * a)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
find_pool_stat_in_list(struct netdev_page_pool_stats_get_list * pp_stats,__u64 pool_id,struct pp_stat * pstat)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
print_json_pool_list(struct netdev_page_pool_get_list * pools,struct netdev_page_pool_stats_get_list * pp_stats,bool zombies_only)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
print_plain_pool_list(struct netdev_page_pool_get_list * pools,struct netdev_page_pool_stats_get_list * pp_stats,bool zombies_only)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
aggregate_device_stats(struct pp_stats_array * a,struct netdev_page_pool_get_list * pools,struct netdev_page_pool_stats_get_list * pp_stats)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
do_stats(int argc,char ** argv)328 static int do_stats(int argc, char **argv)
329 {
330 struct netdev_page_pool_stats_get_list *pp_stats;
331 struct netdev_page_pool_get_list *pools;
332 enum {
333 GROUP_BY_DEVICE,
334 GROUP_BY_POOL,
335 } group_by = GROUP_BY_DEVICE;
336 bool zombies_only = false;
337 struct pp_stats_array a = {};
338 struct ynl_error yerr;
339 struct ynl_sock *ys;
340 int ret = 0;
341
342 /* Parse options */
343 while (argc > 0) {
344 if (is_prefix(*argv, "group-by")) {
345 NEXT_ARG();
346
347 if (!REQ_ARGS(1))
348 return -1;
349
350 if (is_prefix(*argv, "device")) {
351 group_by = GROUP_BY_DEVICE;
352 } else if (is_prefix(*argv, "pp") ||
353 is_prefix(*argv, "page-pool") ||
354 is_prefix(*argv, "none")) {
355 group_by = GROUP_BY_POOL;
356 } else {
357 p_err("invalid group-by value '%s'", *argv);
358 return -1;
359 }
360 NEXT_ARG();
361 } else if (is_prefix(*argv, "zombies")) {
362 zombies_only = true;
363 group_by = GROUP_BY_POOL;
364 NEXT_ARG();
365 } else {
366 p_err("unknown option '%s'", *argv);
367 return -1;
368 }
369 }
370
371 ys = ynl_sock_create(&ynl_netdev_family, &yerr);
372 if (!ys) {
373 p_err("YNL: %s", yerr.msg);
374 return -1;
375 }
376
377 pools = netdev_page_pool_get_dump(ys);
378 if (!pools) {
379 p_err("failed to get page pools: %s", ys->err.msg);
380 ret = -1;
381 goto exit_close;
382 }
383
384 pp_stats = netdev_page_pool_stats_get_dump(ys);
385 if (!pp_stats) {
386 p_err("failed to get page pool stats: %s", ys->err.msg);
387 ret = -1;
388 goto exit_free_pp_list;
389 }
390
391 /* If grouping by pool, print individual pools */
392 if (group_by == GROUP_BY_POOL) {
393 if (json_output)
394 print_json_pool_list(pools, pp_stats, zombies_only);
395 else
396 print_plain_pool_list(pools, pp_stats, zombies_only);
397 } else {
398 /* Aggregated stats mode (group-by device) */
399 a.max = 64;
400 a.s = calloc(a.max, sizeof(*a.s));
401 if (!a.s) {
402 p_err("failed to allocate stats array");
403 ret = -1;
404 goto exit_free_stats_list;
405 }
406
407 aggregate_device_stats(&a, pools, pp_stats);
408
409 if (json_output)
410 print_json_stats(&a);
411 else
412 print_plain_stats(&a);
413
414 free(a.s);
415 }
416
417 exit_free_stats_list:
418 netdev_page_pool_stats_get_list_free(pp_stats);
419 exit_free_pp_list:
420 netdev_page_pool_get_list_free(pools);
421 exit_close:
422 ynl_sock_destroy(ys);
423 return ret;
424 }
425
do_help(int argc,char ** argv)426 static int do_help(int argc __attribute__((unused)),
427 char **argv __attribute__((unused)))
428 {
429 if (json_output) {
430 jsonw_null(json_wtr);
431 return 0;
432 }
433
434 fprintf(stderr,
435 "Usage: %s page-pool { COMMAND | help }\n"
436 " %s page-pool stats [ OPTIONS ]\n"
437 "\n"
438 " OPTIONS := { group-by { device | page-pool | none } | zombies }\n"
439 "\n"
440 " stats - Display page pool statistics\n"
441 " stats group-by device - Group statistics by network device (default)\n"
442 " stats group-by page-pool | pp | none\n"
443 " - Show individual page pool details (no grouping)\n"
444 " stats zombies - Show only zombie page pools (detached but with\n"
445 " pages in flight). Implies group-by page-pool.\n"
446 "",
447 bin_name, bin_name);
448
449 return 0;
450 }
451
452 static const struct cmd page_pool_cmds[] = {
453 { "help", do_help },
454 { "stats", do_stats },
455 { 0 }
456 };
457
do_page_pool(int argc,char ** argv)458 int do_page_pool(int argc, char **argv)
459 {
460 return cmd_select(page_pool_cmds, argc, argv, do_help);
461 }
462