blob: e4e9ba52bff02c457410543424b0f90148a01730 [file] [log] [blame]
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +01001/* SPDX-License-Identifier: GPL-2.0
2 * Copyright (c) 2017 Jesper Dangaard Brouer, Red Hat Inc.
3 */
4static const char *__doc__ = " XDP RX-queue info extract example\n\n"
5 "Monitor how many packets per sec (pps) are received\n"
6 "per NIC RX queue index and which CPU processed the packet\n"
7 ;
8
9#include <errno.h>
10#include <signal.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <stdbool.h>
14#include <string.h>
15#include <unistd.h>
16#include <locale.h>
17#include <sys/resource.h>
18#include <getopt.h>
19#include <net/if.h>
20#include <time.h>
21
22#include <arpa/inet.h>
23#include <linux/if_link.h>
24
Jakub Kicinskibe5bca42018-05-10 10:24:43 -070025#include "bpf/bpf.h"
26#include "bpf/libbpf.h"
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +010027#include "bpf_util.h"
28
29static int ifindex = -1;
30static char ifname_buf[IF_NAMESIZE];
31static char *ifname;
32
33static __u32 xdp_flags;
34
Jakub Kicinskibe5bca42018-05-10 10:24:43 -070035static struct bpf_map *stats_global_map;
36static struct bpf_map *rx_queue_index_map;
37
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +010038/* Exit return codes */
39#define EXIT_OK 0
40#define EXIT_FAIL 1
41#define EXIT_FAIL_OPTION 2
42#define EXIT_FAIL_XDP 3
43#define EXIT_FAIL_BPF 4
44#define EXIT_FAIL_MEM 5
45
46static const struct option long_options[] = {
47 {"help", no_argument, NULL, 'h' },
48 {"dev", required_argument, NULL, 'd' },
49 {"skb-mode", no_argument, NULL, 'S' },
50 {"sec", required_argument, NULL, 's' },
51 {"no-separators", no_argument, NULL, 'z' },
52 {"action", required_argument, NULL, 'a' },
53 {0, 0, NULL, 0 }
54};
55
56static void int_exit(int sig)
57{
58 fprintf(stderr,
59 "Interrupted: Removing XDP program on ifindex:%d device:%s\n",
60 ifindex, ifname);
61 if (ifindex > -1)
Eric Leblondb259c2f2018-01-30 21:55:04 +010062 bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +010063 exit(EXIT_OK);
64}
65
66struct config {
67 __u32 action;
68 int ifindex;
69};
70#define XDP_ACTION_MAX (XDP_TX + 1)
71#define XDP_ACTION_MAX_STRLEN 11
72static const char *xdp_action_names[XDP_ACTION_MAX] = {
73 [XDP_ABORTED] = "XDP_ABORTED",
74 [XDP_DROP] = "XDP_DROP",
75 [XDP_PASS] = "XDP_PASS",
76 [XDP_TX] = "XDP_TX",
77};
78
79static const char *action2str(int action)
80{
81 if (action < XDP_ACTION_MAX)
82 return xdp_action_names[action];
83 return NULL;
84}
85
86static int parse_xdp_action(char *action_str)
87{
88 size_t maxlen;
89 __u64 action = -1;
90 int i;
91
92 for (i = 0; i < XDP_ACTION_MAX; i++) {
93 maxlen = XDP_ACTION_MAX_STRLEN;
94 if (strncmp(xdp_action_names[i], action_str, maxlen) == 0) {
95 action = i;
96 break;
97 }
98 }
99 return action;
100}
101
102static void list_xdp_actions(void)
103{
104 int i;
105
106 printf("Available XDP --action <options>\n");
107 for (i = 0; i < XDP_ACTION_MAX; i++)
108 printf("\t%s\n", xdp_action_names[i]);
109 printf("\n");
110}
111
112static void usage(char *argv[])
113{
114 int i;
115
116 printf("\nDOCUMENTATION:\n%s\n", __doc__);
117 printf(" Usage: %s (options-see-below)\n", argv[0]);
118 printf(" Listing options:\n");
119 for (i = 0; long_options[i].name != 0; i++) {
120 printf(" --%-12s", long_options[i].name);
121 if (long_options[i].flag != NULL)
122 printf(" flag (internal value:%d)",
123 *long_options[i].flag);
124 else
125 printf(" short-option: -%c",
126 long_options[i].val);
127 printf("\n");
128 }
129 printf("\n");
130 list_xdp_actions();
131}
132
133#define NANOSEC_PER_SEC 1000000000 /* 10^9 */
134static __u64 gettime(void)
135{
136 struct timespec t;
137 int res;
138
139 res = clock_gettime(CLOCK_MONOTONIC, &t);
140 if (res < 0) {
141 fprintf(stderr, "Error with gettimeofday! (%i)\n", res);
142 exit(EXIT_FAIL);
143 }
144 return (__u64) t.tv_sec * NANOSEC_PER_SEC + t.tv_nsec;
145}
146
147/* Common stats data record shared with _kern.c */
148struct datarec {
149 __u64 processed;
150 __u64 issue;
151};
152struct record {
153 __u64 timestamp;
154 struct datarec total;
155 struct datarec *cpu;
156};
157struct stats_record {
158 struct record stats;
159 struct record *rxq;
160};
161
162static struct datarec *alloc_record_per_cpu(void)
163{
164 unsigned int nr_cpus = bpf_num_possible_cpus();
165 struct datarec *array;
166 size_t size;
167
168 size = sizeof(struct datarec) * nr_cpus;
169 array = malloc(size);
170 memset(array, 0, size);
171 if (!array) {
172 fprintf(stderr, "Mem alloc error (nr_cpus:%u)\n", nr_cpus);
173 exit(EXIT_FAIL_MEM);
174 }
175 return array;
176}
177
178static struct record *alloc_record_per_rxq(void)
179{
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700180 unsigned int nr_rxqs = bpf_map__def(rx_queue_index_map)->max_entries;
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100181 struct record *array;
182 size_t size;
183
184 size = sizeof(struct record) * nr_rxqs;
185 array = malloc(size);
186 memset(array, 0, size);
187 if (!array) {
188 fprintf(stderr, "Mem alloc error (nr_rxqs:%u)\n", nr_rxqs);
189 exit(EXIT_FAIL_MEM);
190 }
191 return array;
192}
193
194static struct stats_record *alloc_stats_record(void)
195{
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700196 unsigned int nr_rxqs = bpf_map__def(rx_queue_index_map)->max_entries;
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100197 struct stats_record *rec;
198 int i;
199
200 rec = malloc(sizeof(*rec));
201 memset(rec, 0, sizeof(*rec));
202 if (!rec) {
203 fprintf(stderr, "Mem alloc error\n");
204 exit(EXIT_FAIL_MEM);
205 }
206 rec->rxq = alloc_record_per_rxq();
207 for (i = 0; i < nr_rxqs; i++)
208 rec->rxq[i].cpu = alloc_record_per_cpu();
209
210 rec->stats.cpu = alloc_record_per_cpu();
211 return rec;
212}
213
214static void free_stats_record(struct stats_record *r)
215{
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700216 unsigned int nr_rxqs = bpf_map__def(rx_queue_index_map)->max_entries;
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100217 int i;
218
219 for (i = 0; i < nr_rxqs; i++)
220 free(r->rxq[i].cpu);
221
222 free(r->rxq);
223 free(r->stats.cpu);
224 free(r);
225}
226
227static bool map_collect_percpu(int fd, __u32 key, struct record *rec)
228{
229 /* For percpu maps, userspace gets a value per possible CPU */
230 unsigned int nr_cpus = bpf_num_possible_cpus();
231 struct datarec values[nr_cpus];
232 __u64 sum_processed = 0;
233 __u64 sum_issue = 0;
234 int i;
235
236 if ((bpf_map_lookup_elem(fd, &key, values)) != 0) {
237 fprintf(stderr,
238 "ERR: bpf_map_lookup_elem failed key:0x%X\n", key);
239 return false;
240 }
241 /* Get time as close as possible to reading map contents */
242 rec->timestamp = gettime();
243
244 /* Record and sum values from each CPU */
245 for (i = 0; i < nr_cpus; i++) {
246 rec->cpu[i].processed = values[i].processed;
247 sum_processed += values[i].processed;
248 rec->cpu[i].issue = values[i].issue;
249 sum_issue += values[i].issue;
250 }
251 rec->total.processed = sum_processed;
252 rec->total.issue = sum_issue;
253 return true;
254}
255
256static void stats_collect(struct stats_record *rec)
257{
258 int fd, i, max_rxqs;
259
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700260 fd = bpf_map__fd(stats_global_map);
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100261 map_collect_percpu(fd, 0, &rec->stats);
262
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700263 fd = bpf_map__fd(rx_queue_index_map);
264 max_rxqs = bpf_map__def(rx_queue_index_map)->max_entries;
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100265 for (i = 0; i < max_rxqs; i++)
266 map_collect_percpu(fd, i, &rec->rxq[i]);
267}
268
269static double calc_period(struct record *r, struct record *p)
270{
271 double period_ = 0;
272 __u64 period = 0;
273
274 period = r->timestamp - p->timestamp;
275 if (period > 0)
276 period_ = ((double) period / NANOSEC_PER_SEC);
277
278 return period_;
279}
280
281static __u64 calc_pps(struct datarec *r, struct datarec *p, double period_)
282{
283 __u64 packets = 0;
284 __u64 pps = 0;
285
286 if (period_ > 0) {
287 packets = r->processed - p->processed;
288 pps = packets / period_;
289 }
290 return pps;
291}
292
293static __u64 calc_errs_pps(struct datarec *r,
294 struct datarec *p, double period_)
295{
296 __u64 packets = 0;
297 __u64 pps = 0;
298
299 if (period_ > 0) {
300 packets = r->issue - p->issue;
301 pps = packets / period_;
302 }
303 return pps;
304}
305
306static void stats_print(struct stats_record *stats_rec,
307 struct stats_record *stats_prev,
308 int action)
309{
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700310 unsigned int nr_rxqs = bpf_map__def(rx_queue_index_map)->max_entries;
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100311 unsigned int nr_cpus = bpf_num_possible_cpus();
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100312 double pps = 0, err = 0;
313 struct record *rec, *prev;
314 double t;
315 int rxq;
316 int i;
317
318 /* Header */
319 printf("\nRunning XDP on dev:%s (ifindex:%d) action:%s\n",
320 ifname, ifindex, action2str(action));
321
322 /* stats_global_map */
323 {
324 char *fmt_rx = "%-15s %-7d %'-11.0f %'-10.0f %s\n";
325 char *fm2_rx = "%-15s %-7s %'-11.0f\n";
326 char *errstr = "";
327
328 printf("%-15s %-7s %-11s %-11s\n",
329 "XDP stats", "CPU", "pps", "issue-pps");
330
331 rec = &stats_rec->stats;
332 prev = &stats_prev->stats;
333 t = calc_period(rec, prev);
334 for (i = 0; i < nr_cpus; i++) {
335 struct datarec *r = &rec->cpu[i];
336 struct datarec *p = &prev->cpu[i];
337
338 pps = calc_pps (r, p, t);
339 err = calc_errs_pps(r, p, t);
340 if (err > 0)
341 errstr = "invalid-ifindex";
342 if (pps > 0)
343 printf(fmt_rx, "XDP-RX CPU",
344 i, pps, err, errstr);
345 }
346 pps = calc_pps (&rec->total, &prev->total, t);
347 err = calc_errs_pps(&rec->total, &prev->total, t);
348 printf(fm2_rx, "XDP-RX CPU", "total", pps, err);
349 }
350
351 /* rx_queue_index_map */
352 printf("\n%-15s %-7s %-11s %-11s\n",
353 "RXQ stats", "RXQ:CPU", "pps", "issue-pps");
354
355 for (rxq = 0; rxq < nr_rxqs; rxq++) {
356 char *fmt_rx = "%-15s %3d:%-3d %'-11.0f %'-10.0f %s\n";
357 char *fm2_rx = "%-15s %3d:%-3s %'-11.0f\n";
358 char *errstr = "";
359 int rxq_ = rxq;
360
361 /* Last RXQ in map catch overflows */
362 if (rxq_ == nr_rxqs - 1)
363 rxq_ = -1;
364
365 rec = &stats_rec->rxq[rxq];
366 prev = &stats_prev->rxq[rxq];
367 t = calc_period(rec, prev);
368 for (i = 0; i < nr_cpus; i++) {
369 struct datarec *r = &rec->cpu[i];
370 struct datarec *p = &prev->cpu[i];
371
372 pps = calc_pps (r, p, t);
373 err = calc_errs_pps(r, p, t);
374 if (err > 0) {
375 if (rxq_ == -1)
376 errstr = "map-overflow-RXQ";
377 else
378 errstr = "err";
379 }
380 if (pps > 0)
381 printf(fmt_rx, "rx_queue_index",
382 rxq_, i, pps, err, errstr);
383 }
384 pps = calc_pps (&rec->total, &prev->total, t);
385 err = calc_errs_pps(&rec->total, &prev->total, t);
386 if (pps || err)
387 printf(fm2_rx, "rx_queue_index", rxq_, "sum", pps, err);
388 }
389}
390
391
392/* Pointer swap trick */
393static inline void swap(struct stats_record **a, struct stats_record **b)
394{
395 struct stats_record *tmp;
396
397 tmp = *a;
398 *a = *b;
399 *b = tmp;
400}
401
402static void stats_poll(int interval, int action)
403{
404 struct stats_record *record, *prev;
405
406 record = alloc_stats_record();
407 prev = alloc_stats_record();
408 stats_collect(record);
409
410 while (1) {
411 swap(&prev, &record);
412 stats_collect(record);
413 stats_print(record, prev, action);
414 sleep(interval);
415 }
416
417 free_stats_record(record);
418 free_stats_record(prev);
419}
420
421
422int main(int argc, char **argv)
423{
424 struct rlimit r = {10 * 1024 * 1024, RLIM_INFINITY};
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700425 struct bpf_prog_load_attr prog_load_attr = {
426 .prog_type = BPF_PROG_TYPE_XDP,
427 };
428 int prog_fd, map_fd, opt, err;
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100429 bool use_separators = true;
430 struct config cfg = { 0 };
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700431 struct bpf_object *obj;
432 struct bpf_map *map;
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100433 char filename[256];
434 int longindex = 0;
435 int interval = 2;
436 __u32 key = 0;
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100437
438 char action_str_buf[XDP_ACTION_MAX_STRLEN + 1 /* for \0 */] = { 0 };
439 int action = XDP_PASS; /* Default action */
440 char *action_str = NULL;
441
442 snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700443 prog_load_attr.file = filename;
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100444
445 if (setrlimit(RLIMIT_MEMLOCK, &r)) {
446 perror("setrlimit(RLIMIT_MEMLOCK)");
447 return 1;
448 }
449
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700450 if (bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd))
451 return EXIT_FAIL;
452
453 map = bpf_map__next(NULL, obj);
454 stats_global_map = bpf_map__next(map, obj);
455 rx_queue_index_map = bpf_map__next(stats_global_map, obj);
456 if (!map || !stats_global_map || !rx_queue_index_map) {
457 printf("finding a map in obj file failed\n");
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100458 return EXIT_FAIL;
459 }
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700460 map_fd = bpf_map__fd(map);
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100461
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700462 if (!prog_fd) {
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100463 fprintf(stderr, "ERR: load_bpf_file: %s\n", strerror(errno));
464 return EXIT_FAIL;
465 }
466
467 /* Parse commands line args */
468 while ((opt = getopt_long(argc, argv, "hSd:",
469 long_options, &longindex)) != -1) {
470 switch (opt) {
471 case 'd':
472 if (strlen(optarg) >= IF_NAMESIZE) {
473 fprintf(stderr, "ERR: --dev name too long\n");
474 goto error;
475 }
476 ifname = (char *)&ifname_buf;
477 strncpy(ifname, optarg, IF_NAMESIZE);
478 ifindex = if_nametoindex(ifname);
479 if (ifindex == 0) {
480 fprintf(stderr,
481 "ERR: --dev name unknown err(%d):%s\n",
482 errno, strerror(errno));
483 goto error;
484 }
485 break;
486 case 's':
487 interval = atoi(optarg);
488 break;
489 case 'S':
490 xdp_flags |= XDP_FLAGS_SKB_MODE;
491 break;
492 case 'z':
493 use_separators = false;
494 break;
495 case 'a':
496 action_str = (char *)&action_str_buf;
497 strncpy(action_str, optarg, XDP_ACTION_MAX_STRLEN);
498 break;
499 case 'h':
500 error:
501 default:
502 usage(argv);
503 return EXIT_FAIL_OPTION;
504 }
505 }
506 /* Required option */
507 if (ifindex == -1) {
508 fprintf(stderr, "ERR: required option --dev missing\n");
509 usage(argv);
510 return EXIT_FAIL_OPTION;
511 }
512 cfg.ifindex = ifindex;
513
514 /* Parse action string */
515 if (action_str) {
516 action = parse_xdp_action(action_str);
517 if (action < 0) {
518 fprintf(stderr, "ERR: Invalid XDP --action: %s\n",
519 action_str);
520 list_xdp_actions();
521 return EXIT_FAIL_OPTION;
522 }
523 }
524 cfg.action = action;
525
526 /* Trick to pretty printf with thousands separators use %' */
527 if (use_separators)
528 setlocale(LC_NUMERIC, "en_US");
529
530 /* User-side setup ifindex in config_map */
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700531 err = bpf_map_update_elem(map_fd, &key, &cfg, 0);
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100532 if (err) {
533 fprintf(stderr, "Store config failed (err:%d)\n", err);
534 exit(EXIT_FAIL_BPF);
535 }
536
537 /* Remove XDP program when program is interrupted */
538 signal(SIGINT, int_exit);
539
Jakub Kicinskibe5bca42018-05-10 10:24:43 -0700540 if (bpf_set_link_xdp_fd(ifindex, prog_fd, xdp_flags) < 0) {
Jesper Dangaard Brouer0fca9312018-01-03 11:26:19 +0100541 fprintf(stderr, "link set xdp fd failed\n");
542 return EXIT_FAIL_XDP;
543 }
544
545 stats_poll(interval, action);
546 return EXIT_OK;
547}