1 /* SPDX-License-Identifier: GPL-2.0
2 * Copyright(c) 2017 Jesper Dangaard Brouer, Red Hat, Inc.
4 static const char *__doc__
=
5 "XDP monitor tool, based on tracepoints\n"
8 static const char *__doc_err_only__
=
9 " NOTICE: Only tracking XDP redirect errors\n"
10 " Enable TX success stats via '--stats'\n"
11 " (which comes with a per packet processing overhead)\n"
24 #include <sys/resource.h>
31 #include <bpf/libbpf.h>
42 static const char *const map_type_strings
[] = {
43 [REDIRECT_ERR_CNT
] = "redirect_err_cnt",
44 [EXCEPTION_CNT
] = "exception_cnt",
45 [CPUMAP_ENQUEUE_CNT
] = "cpumap_enqueue_cnt",
46 [CPUMAP_KTHREAD_CNT
] = "cpumap_kthread_cnt",
47 [DEVMAP_XMIT_CNT
] = "devmap_xmit_cnt",
55 static int verbose
= 1;
56 static bool debug
= false;
57 struct bpf_map
*map_data
[NUM_MAP
] = {};
58 struct bpf_link
*tp_links
[NUM_TP
] = {};
59 struct bpf_object
*obj
;
61 static const struct option long_options
[] = {
62 {"help", no_argument
, NULL
, 'h' },
63 {"debug", no_argument
, NULL
, 'D' },
64 {"stats", no_argument
, NULL
, 'S' },
65 {"sec", required_argument
, NULL
, 's' },
69 static void int_exit(int sig
)
71 /* Detach tracepoints */
73 bpf_link__destroy(tp_links
[--tp_cnt
]);
75 bpf_object__close(obj
);
79 /* C standard specifies two constants, EXIT_SUCCESS(0) and EXIT_FAILURE(1) */
80 #define EXIT_FAIL_MEM 5
82 static void usage(char *argv
[])
85 printf("\nDOCUMENTATION:\n%s\n", __doc__
);
87 printf(" Usage: %s (options-see-below)\n",
89 printf(" Listing options:\n");
90 for (i
= 0; long_options
[i
].name
!= 0; i
++) {
91 printf(" --%-15s", long_options
[i
].name
);
92 if (long_options
[i
].flag
!= NULL
)
93 printf(" flag (internal value:%d)",
94 *long_options
[i
].flag
);
96 printf("short-option: -%c",
103 #define NANOSEC_PER_SEC 1000000000 /* 10^9 */
104 static __u64
gettime(void)
109 res
= clock_gettime(CLOCK_MONOTONIC
, &t
);
111 fprintf(stderr
, "Error with gettimeofday! (%i)\n", res
);
114 return (__u64
) t
.tv_sec
* NANOSEC_PER_SEC
+ t
.tv_nsec
;
121 #define REDIR_RES_MAX 2
122 static const char *redir_names
[REDIR_RES_MAX
] = {
123 [REDIR_SUCCESS
] = "Success",
124 [REDIR_ERROR
] = "Error",
126 static const char *err2str(int err
)
128 if (err
< REDIR_RES_MAX
)
129 return redir_names
[err
];
132 /* enum xdp_action */
133 #define XDP_UNKNOWN XDP_REDIRECT + 1
134 #define XDP_ACTION_MAX (XDP_UNKNOWN + 1)
135 static const char *xdp_action_names
[XDP_ACTION_MAX
] = {
136 [XDP_ABORTED
] = "XDP_ABORTED",
137 [XDP_DROP
] = "XDP_DROP",
138 [XDP_PASS
] = "XDP_PASS",
140 [XDP_REDIRECT
] = "XDP_REDIRECT",
141 [XDP_UNKNOWN
] = "XDP_UNKNOWN",
143 static const char *action2str(int action
)
145 if (action
< XDP_ACTION_MAX
)
146 return xdp_action_names
[action
];
150 /* Common stats data record shared with _kern.c */
159 /* Userspace structs for collection of stats from maps */
162 struct datarec total
;
169 /* record for _kern side __u64 values */
175 struct stats_record
{
176 struct record_u64 xdp_redirect
[REDIR_RES_MAX
];
177 struct record_u64 xdp_exception
[XDP_ACTION_MAX
];
178 struct record xdp_cpumap_kthread
;
179 struct record xdp_cpumap_enqueue
[MAX_CPUS
];
180 struct record xdp_devmap_xmit
;
183 static bool map_collect_record(int fd
, __u32 key
, struct record
*rec
)
185 /* For percpu maps, userspace gets a value per possible CPU */
186 unsigned int nr_cpus
= bpf_num_possible_cpus();
187 struct datarec values
[nr_cpus
];
188 __u64 sum_processed
= 0;
189 __u64 sum_dropped
= 0;
194 if ((bpf_map_lookup_elem(fd
, &key
, values
)) != 0) {
196 "ERR: bpf_map_lookup_elem failed key:0x%X\n", key
);
199 /* Get time as close as possible to reading map contents */
200 rec
->timestamp
= gettime();
202 /* Record and sum values from each CPU */
203 for (i
= 0; i
< nr_cpus
; i
++) {
204 rec
->cpu
[i
].processed
= values
[i
].processed
;
205 sum_processed
+= values
[i
].processed
;
206 rec
->cpu
[i
].dropped
= values
[i
].dropped
;
207 sum_dropped
+= values
[i
].dropped
;
208 rec
->cpu
[i
].info
= values
[i
].info
;
209 sum_info
+= values
[i
].info
;
210 rec
->cpu
[i
].err
= values
[i
].err
;
211 sum_err
+= values
[i
].err
;
213 rec
->total
.processed
= sum_processed
;
214 rec
->total
.dropped
= sum_dropped
;
215 rec
->total
.info
= sum_info
;
216 rec
->total
.err
= sum_err
;
220 static bool map_collect_record_u64(int fd
, __u32 key
, struct record_u64
*rec
)
222 /* For percpu maps, userspace gets a value per possible CPU */
223 unsigned int nr_cpus
= bpf_num_possible_cpus();
224 struct u64rec values
[nr_cpus
];
228 if ((bpf_map_lookup_elem(fd
, &key
, values
)) != 0) {
230 "ERR: bpf_map_lookup_elem failed key:0x%X\n", key
);
233 /* Get time as close as possible to reading map contents */
234 rec
->timestamp
= gettime();
236 /* Record and sum values from each CPU */
237 for (i
= 0; i
< nr_cpus
; i
++) {
238 rec
->cpu
[i
].processed
= values
[i
].processed
;
239 sum_total
+= values
[i
].processed
;
241 rec
->total
.processed
= sum_total
;
245 static double calc_period(struct record
*r
, struct record
*p
)
250 period
= r
->timestamp
- p
->timestamp
;
252 period_
= ((double) period
/ NANOSEC_PER_SEC
);
257 static double calc_period_u64(struct record_u64
*r
, struct record_u64
*p
)
262 period
= r
->timestamp
- p
->timestamp
;
264 period_
= ((double) period
/ NANOSEC_PER_SEC
);
269 static double calc_pps(struct datarec
*r
, struct datarec
*p
, double period
)
275 packets
= r
->processed
- p
->processed
;
276 pps
= packets
/ period
;
281 static double calc_pps_u64(struct u64rec
*r
, struct u64rec
*p
, double period
)
287 packets
= r
->processed
- p
->processed
;
288 pps
= packets
/ period
;
293 static double calc_drop(struct datarec
*r
, struct datarec
*p
, double period
)
299 packets
= r
->dropped
- p
->dropped
;
300 pps
= packets
/ period
;
305 static double calc_info(struct datarec
*r
, struct datarec
*p
, double period
)
311 packets
= r
->info
- p
->info
;
312 pps
= packets
/ period
;
317 static double calc_err(struct datarec
*r
, struct datarec
*p
, double period
)
323 packets
= r
->err
- p
->err
;
324 pps
= packets
/ period
;
329 static void stats_print(struct stats_record
*stats_rec
,
330 struct stats_record
*stats_prev
,
333 unsigned int nr_cpus
= bpf_num_possible_cpus();
334 int rec_i
= 0, i
, to_cpu
;
335 double t
= 0, pps
= 0;
338 printf("%-15s %-7s %-12s %-12s %-9s\n",
339 "XDP-event", "CPU:to", "pps", "drop-pps", "extra-info");
341 /* tracepoint: xdp:xdp_redirect_* */
345 for (; rec_i
< REDIR_RES_MAX
; rec_i
++) {
346 struct record_u64
*rec
, *prev
;
347 char *fmt1
= "%-15s %-7d %'-12.0f %'-12.0f %s\n";
348 char *fmt2
= "%-15s %-7s %'-12.0f %'-12.0f %s\n";
350 rec
= &stats_rec
->xdp_redirect
[rec_i
];
351 prev
= &stats_prev
->xdp_redirect
[rec_i
];
352 t
= calc_period_u64(rec
, prev
);
354 for (i
= 0; i
< nr_cpus
; i
++) {
355 struct u64rec
*r
= &rec
->cpu
[i
];
356 struct u64rec
*p
= &prev
->cpu
[i
];
358 pps
= calc_pps_u64(r
, p
, t
);
360 printf(fmt1
, "XDP_REDIRECT", i
,
361 rec_i
? 0.0: pps
, rec_i
? pps
: 0.0,
364 pps
= calc_pps_u64(&rec
->total
, &prev
->total
, t
);
365 printf(fmt2
, "XDP_REDIRECT", "total",
366 rec_i
? 0.0: pps
, rec_i
? pps
: 0.0, err2str(rec_i
));
369 /* tracepoint: xdp:xdp_exception */
370 for (rec_i
= 0; rec_i
< XDP_ACTION_MAX
; rec_i
++) {
371 struct record_u64
*rec
, *prev
;
372 char *fmt1
= "%-15s %-7d %'-12.0f %'-12.0f %s\n";
373 char *fmt2
= "%-15s %-7s %'-12.0f %'-12.0f %s\n";
375 rec
= &stats_rec
->xdp_exception
[rec_i
];
376 prev
= &stats_prev
->xdp_exception
[rec_i
];
377 t
= calc_period_u64(rec
, prev
);
379 for (i
= 0; i
< nr_cpus
; i
++) {
380 struct u64rec
*r
= &rec
->cpu
[i
];
381 struct u64rec
*p
= &prev
->cpu
[i
];
383 pps
= calc_pps_u64(r
, p
, t
);
385 printf(fmt1
, "Exception", i
,
386 0.0, pps
, action2str(rec_i
));
388 pps
= calc_pps_u64(&rec
->total
, &prev
->total
, t
);
390 printf(fmt2
, "Exception", "total",
391 0.0, pps
, action2str(rec_i
));
394 /* cpumap enqueue stats */
395 for (to_cpu
= 0; to_cpu
< MAX_CPUS
; to_cpu
++) {
396 char *fmt1
= "%-15s %3d:%-3d %'-12.0f %'-12.0f %'-10.2f %s\n";
397 char *fmt2
= "%-15s %3s:%-3d %'-12.0f %'-12.0f %'-10.2f %s\n";
398 struct record
*rec
, *prev
;
402 rec
= &stats_rec
->xdp_cpumap_enqueue
[to_cpu
];
403 prev
= &stats_prev
->xdp_cpumap_enqueue
[to_cpu
];
404 t
= calc_period(rec
, prev
);
405 for (i
= 0; i
< nr_cpus
; i
++) {
406 struct datarec
*r
= &rec
->cpu
[i
];
407 struct datarec
*p
= &prev
->cpu
[i
];
409 pps
= calc_pps(r
, p
, t
);
410 drop
= calc_drop(r
, p
, t
);
411 info
= calc_info(r
, p
, t
);
413 info_str
= "bulk-average";
414 info
= pps
/ info
; /* calc average bulk size */
417 printf(fmt1
, "cpumap-enqueue",
418 i
, to_cpu
, pps
, drop
, info
, info_str
);
420 pps
= calc_pps(&rec
->total
, &prev
->total
, t
);
422 drop
= calc_drop(&rec
->total
, &prev
->total
, t
);
423 info
= calc_info(&rec
->total
, &prev
->total
, t
);
425 info_str
= "bulk-average";
426 info
= pps
/ info
; /* calc average bulk size */
428 printf(fmt2
, "cpumap-enqueue",
429 "sum", to_cpu
, pps
, drop
, info
, info_str
);
433 /* cpumap kthread stats */
435 char *fmt1
= "%-15s %-7d %'-12.0f %'-12.0f %'-10.0f %s\n";
436 char *fmt2
= "%-15s %-7s %'-12.0f %'-12.0f %'-10.0f %s\n";
437 struct record
*rec
, *prev
;
441 rec
= &stats_rec
->xdp_cpumap_kthread
;
442 prev
= &stats_prev
->xdp_cpumap_kthread
;
443 t
= calc_period(rec
, prev
);
444 for (i
= 0; i
< nr_cpus
; i
++) {
445 struct datarec
*r
= &rec
->cpu
[i
];
446 struct datarec
*p
= &prev
->cpu
[i
];
448 pps
= calc_pps(r
, p
, t
);
449 drop
= calc_drop(r
, p
, t
);
450 info
= calc_info(r
, p
, t
);
453 if (pps
> 0 || drop
> 0)
454 printf(fmt1
, "cpumap-kthread",
455 i
, pps
, drop
, info
, i_str
);
457 pps
= calc_pps(&rec
->total
, &prev
->total
, t
);
458 drop
= calc_drop(&rec
->total
, &prev
->total
, t
);
459 info
= calc_info(&rec
->total
, &prev
->total
, t
);
462 printf(fmt2
, "cpumap-kthread", "total", pps
, drop
, info
, i_str
);
465 /* devmap ndo_xdp_xmit stats */
467 char *fmt1
= "%-15s %-7d %'-12.0f %'-12.0f %'-10.2f %s %s\n";
468 char *fmt2
= "%-15s %-7s %'-12.0f %'-12.0f %'-10.2f %s %s\n";
469 struct record
*rec
, *prev
;
470 double drop
, info
, err
;
474 rec
= &stats_rec
->xdp_devmap_xmit
;
475 prev
= &stats_prev
->xdp_devmap_xmit
;
476 t
= calc_period(rec
, prev
);
477 for (i
= 0; i
< nr_cpus
; i
++) {
478 struct datarec
*r
= &rec
->cpu
[i
];
479 struct datarec
*p
= &prev
->cpu
[i
];
481 pps
= calc_pps(r
, p
, t
);
482 drop
= calc_drop(r
, p
, t
);
483 info
= calc_info(r
, p
, t
);
484 err
= calc_err(r
, p
, t
);
486 i_str
= "bulk-average";
487 info
= (pps
+drop
) / info
; /* calc avg bulk */
491 if (pps
> 0 || drop
> 0)
492 printf(fmt1
, "devmap-xmit",
493 i
, pps
, drop
, info
, i_str
, err_str
);
495 pps
= calc_pps(&rec
->total
, &prev
->total
, t
);
496 drop
= calc_drop(&rec
->total
, &prev
->total
, t
);
497 info
= calc_info(&rec
->total
, &prev
->total
, t
);
498 err
= calc_err(&rec
->total
, &prev
->total
, t
);
500 i_str
= "bulk-average";
501 info
= (pps
+drop
) / info
; /* calc avg bulk */
505 printf(fmt2
, "devmap-xmit", "total", pps
, drop
,
506 info
, i_str
, err_str
);
512 static bool stats_collect(struct stats_record
*rec
)
517 /* TODO: Detect if someone unloaded the perf event_fd's, as
518 * this can happen by someone running perf-record -e
521 fd
= bpf_map__fd(map_data
[REDIRECT_ERR_CNT
]);
522 for (i
= 0; i
< REDIR_RES_MAX
; i
++)
523 map_collect_record_u64(fd
, i
, &rec
->xdp_redirect
[i
]);
525 fd
= bpf_map__fd(map_data
[EXCEPTION_CNT
]);
526 for (i
= 0; i
< XDP_ACTION_MAX
; i
++) {
527 map_collect_record_u64(fd
, i
, &rec
->xdp_exception
[i
]);
530 fd
= bpf_map__fd(map_data
[CPUMAP_ENQUEUE_CNT
]);
531 for (i
= 0; i
< MAX_CPUS
; i
++)
532 map_collect_record(fd
, i
, &rec
->xdp_cpumap_enqueue
[i
]);
534 fd
= bpf_map__fd(map_data
[CPUMAP_KTHREAD_CNT
]);
535 map_collect_record(fd
, 0, &rec
->xdp_cpumap_kthread
);
537 fd
= bpf_map__fd(map_data
[DEVMAP_XMIT_CNT
]);
538 map_collect_record(fd
, 0, &rec
->xdp_devmap_xmit
);
543 static void *alloc_rec_per_cpu(int record_size
)
545 unsigned int nr_cpus
= bpf_num_possible_cpus();
548 array
= calloc(nr_cpus
, record_size
);
550 fprintf(stderr
, "Mem alloc error (nr_cpus:%u)\n", nr_cpus
);
556 static struct stats_record
*alloc_stats_record(void)
558 struct stats_record
*rec
;
562 /* Alloc main stats_record structure */
563 rec
= calloc(1, sizeof(*rec
));
565 fprintf(stderr
, "Mem alloc error\n");
569 /* Alloc stats stored per CPU for each record */
570 rec_sz
= sizeof(struct u64rec
);
571 for (i
= 0; i
< REDIR_RES_MAX
; i
++)
572 rec
->xdp_redirect
[i
].cpu
= alloc_rec_per_cpu(rec_sz
);
574 for (i
= 0; i
< XDP_ACTION_MAX
; i
++)
575 rec
->xdp_exception
[i
].cpu
= alloc_rec_per_cpu(rec_sz
);
577 rec_sz
= sizeof(struct datarec
);
578 rec
->xdp_cpumap_kthread
.cpu
= alloc_rec_per_cpu(rec_sz
);
579 rec
->xdp_devmap_xmit
.cpu
= alloc_rec_per_cpu(rec_sz
);
581 for (i
= 0; i
< MAX_CPUS
; i
++)
582 rec
->xdp_cpumap_enqueue
[i
].cpu
= alloc_rec_per_cpu(rec_sz
);
587 static void free_stats_record(struct stats_record
*r
)
591 for (i
= 0; i
< REDIR_RES_MAX
; i
++)
592 free(r
->xdp_redirect
[i
].cpu
);
594 for (i
= 0; i
< XDP_ACTION_MAX
; i
++)
595 free(r
->xdp_exception
[i
].cpu
);
597 free(r
->xdp_cpumap_kthread
.cpu
);
598 free(r
->xdp_devmap_xmit
.cpu
);
600 for (i
= 0; i
< MAX_CPUS
; i
++)
601 free(r
->xdp_cpumap_enqueue
[i
].cpu
);
606 /* Pointer swap trick */
607 static inline void swap(struct stats_record
**a
, struct stats_record
**b
)
609 struct stats_record
*tmp
;
616 static void stats_poll(int interval
, bool err_only
)
618 struct stats_record
*rec
, *prev
;
620 rec
= alloc_stats_record();
621 prev
= alloc_stats_record();
625 printf("\n%s\n", __doc_err_only__
);
627 /* Trick to pretty printf with thousands separators use %' */
628 setlocale(LC_NUMERIC
, "en_US");
632 printf("\n%s", __doc__
);
634 /* TODO Need more advanced stats on error types */
636 printf(" - Stats map0: %s\n", bpf_map__name(map_data
[0]));
637 printf(" - Stats map1: %s\n", bpf_map__name(map_data
[1]));
645 stats_print(rec
, prev
, err_only
);
650 free_stats_record(rec
);
651 free_stats_record(prev
);
654 static void print_bpf_prog_info(void)
656 struct bpf_program
*prog
;
661 printf("Loaded BPF prog have %d bpf program(s)\n", tp_cnt
);
662 bpf_object__for_each_program(prog
, obj
) {
663 printf(" - prog_fd[%d] = fd(%d)\n", i
, bpf_program__fd(prog
));
669 printf("Loaded BPF prog have %d map(s)\n", map_cnt
);
670 bpf_object__for_each_map(map
, obj
) {
671 const char *name
= bpf_map__name(map
);
672 int fd
= bpf_map__fd(map
);
674 printf(" - map_data[%d] = fd(%d) name:%s\n", i
, fd
, name
);
679 printf("Searching for (max:%d) event file descriptor(s)\n", tp_cnt
);
680 for (i
= 0; i
< tp_cnt
; i
++) {
681 int fd
= bpf_link__fd(tp_links
[i
]);
684 printf(" - event_fd[%d] = fd(%d)\n", i
, fd
);
688 int main(int argc
, char **argv
)
690 struct bpf_program
*prog
;
691 int longindex
= 0, opt
;
692 int ret
= EXIT_FAILURE
;
696 /* Default settings: */
697 bool errors_only
= true;
700 /* Parse commands line args */
701 while ((opt
= getopt_long(argc
, argv
, "hDSs:",
702 long_options
, &longindex
)) != -1) {
711 interval
= atoi(optarg
);
720 snprintf(filename
, sizeof(filename
), "%s_kern.o", argv
[0]);
722 /* Remove tracepoint program when program is interrupted or killed */
723 signal(SIGINT
, int_exit
);
724 signal(SIGTERM
, int_exit
);
726 obj
= bpf_object__open_file(filename
, NULL
);
727 if (libbpf_get_error(obj
)) {
728 printf("ERROR: opening BPF object file failed\n");
733 /* load BPF program */
734 if (bpf_object__load(obj
)) {
735 printf("ERROR: loading BPF object file failed\n");
739 for (type
= 0; type
< NUM_MAP
; type
++) {
741 bpf_object__find_map_by_name(obj
, map_type_strings
[type
]);
743 if (libbpf_get_error(map_data
[type
])) {
744 printf("ERROR: finding a map in obj file failed\n");
750 bpf_object__for_each_program(prog
, obj
) {
751 tp_links
[tp_cnt
] = bpf_program__attach(prog
);
752 if (libbpf_get_error(tp_links
[tp_cnt
])) {
753 printf("ERROR: bpf_program__attach failed\n");
754 tp_links
[tp_cnt
] = NULL
;
761 print_bpf_prog_info();
764 /* Unload/stop tracepoint event by closing bpf_link's */
766 /* The bpf_link[i] depend on the order of
767 * the functions was defined in _kern.c
769 bpf_link__destroy(tp_links
[2]); /* tracepoint/xdp/xdp_redirect */
772 bpf_link__destroy(tp_links
[3]); /* tracepoint/xdp/xdp_redirect_map */
776 stats_poll(interval
, errors_only
);
781 /* Detach tracepoints */
783 bpf_link__destroy(tp_links
[--tp_cnt
]);
785 bpf_object__close(obj
);