1/* 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 25#include <bpf/bpf.h> 26#include <bpf/libbpf.h> 27#include "bpf_util.h" 28 29static int ifindex = -1; 30static char ifname_buf[IF_NAMESIZE]; 31static char *ifname; 32static __u32 prog_id; 33 34static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; 35 36static struct bpf_map *stats_global_map; 37static struct bpf_map *rx_queue_index_map; 38 39/* Exit return codes */ 40#define EXIT_OK 0 41#define EXIT_FAIL 1 42#define EXIT_FAIL_OPTION 2 43#define EXIT_FAIL_XDP 3 44#define EXIT_FAIL_BPF 4 45#define EXIT_FAIL_MEM 5 46 47static const struct option long_options[] = { 48 {"help", no_argument, NULL, 'h' }, 49 {"dev", required_argument, NULL, 'd' }, 50 {"skb-mode", no_argument, NULL, 'S' }, 51 {"sec", required_argument, NULL, 's' }, 52 {"no-separators", no_argument, NULL, 'z' }, 53 {"action", required_argument, NULL, 'a' }, 54 {"readmem", no_argument, NULL, 'r' }, 55 {"swapmac", no_argument, NULL, 'm' }, 56 {"force", no_argument, NULL, 'F' }, 57 {0, 0, NULL, 0 } 58}; 59 60static void int_exit(int sig) 61{ 62 __u32 curr_prog_id = 0; 63 64 if (ifindex > -1) { 65 if (bpf_get_link_xdp_id(ifindex, &curr_prog_id, xdp_flags)) { 66 printf("bpf_get_link_xdp_id failed\n"); 67 exit(EXIT_FAIL); 68 } 69 if (prog_id == curr_prog_id) { 70 fprintf(stderr, 71 "Interrupted: Removing XDP program on ifindex:%d device:%s\n", 72 ifindex, ifname); 73 bpf_set_link_xdp_fd(ifindex, -1, xdp_flags); 74 } else if (!curr_prog_id) { 75 printf("couldn't find a prog id on a given iface\n"); 76 } else { 77 printf("program on interface changed, not removing\n"); 78 } 79 } 80 exit(EXIT_OK); 81} 82 83struct config { 84 __u32 action; 85 int ifindex; 86 __u32 options; 87}; 88enum cfg_options_flags { 89 NO_TOUCH = 0x0U, 90 READ_MEM = 0x1U, 91 SWAP_MAC = 0x2U, 92}; 93#define XDP_ACTION_MAX (XDP_TX + 1) 94#define XDP_ACTION_MAX_STRLEN 11 95static const char *xdp_action_names[XDP_ACTION_MAX] = { 96 [XDP_ABORTED] = "XDP_ABORTED", 97 [XDP_DROP] = "XDP_DROP", 98 [XDP_PASS] = "XDP_PASS", 99 [XDP_TX] = "XDP_TX", 100}; 101 102static const char *action2str(int action) 103{ 104 if (action < XDP_ACTION_MAX) 105 return xdp_action_names[action]; 106 return NULL; 107} 108 109static int parse_xdp_action(char *action_str) 110{ 111 size_t maxlen; 112 __u64 action = -1; 113 int i; 114 115 for (i = 0; i < XDP_ACTION_MAX; i++) { 116 maxlen = XDP_ACTION_MAX_STRLEN; 117 if (strncmp(xdp_action_names[i], action_str, maxlen) == 0) { 118 action = i; 119 break; 120 } 121 } 122 return action; 123} 124 125static void list_xdp_actions(void) 126{ 127 int i; 128 129 printf("Available XDP --action <options>\n"); 130 for (i = 0; i < XDP_ACTION_MAX; i++) 131 printf("\t%s\n", xdp_action_names[i]); 132 printf("\n"); 133} 134 135static char* options2str(enum cfg_options_flags flag) 136{ 137 if (flag == NO_TOUCH) 138 return "no_touch"; 139 if (flag & SWAP_MAC) 140 return "swapmac"; 141 if (flag & READ_MEM) 142 return "read"; 143 fprintf(stderr, "ERR: Unknown config option flags"); 144 exit(EXIT_FAIL); 145} 146 147static void usage(char *argv[]) 148{ 149 int i; 150 151 printf("\nDOCUMENTATION:\n%s\n", __doc__); 152 printf(" Usage: %s (options-see-below)\n", argv[0]); 153 printf(" Listing options:\n"); 154 for (i = 0; long_options[i].name != 0; i++) { 155 printf(" --%-12s", long_options[i].name); 156 if (long_options[i].flag != NULL) 157 printf(" flag (internal value:%d)", 158 *long_options[i].flag); 159 else 160 printf(" short-option: -%c", 161 long_options[i].val); 162 printf("\n"); 163 } 164 printf("\n"); 165 list_xdp_actions(); 166} 167 168#define NANOSEC_PER_SEC 1000000000 /* 10^9 */ 169static __u64 gettime(void) 170{ 171 struct timespec t; 172 int res; 173 174 res = clock_gettime(CLOCK_MONOTONIC, &t); 175 if (res < 0) { 176 fprintf(stderr, "Error with gettimeofday! (%i)\n", res); 177 exit(EXIT_FAIL); 178 } 179 return (__u64) t.tv_sec * NANOSEC_PER_SEC + t.tv_nsec; 180} 181 182/* Common stats data record shared with _kern.c */ 183struct datarec { 184 __u64 processed; 185 __u64 issue; 186}; 187struct record { 188 __u64 timestamp; 189 struct datarec total; 190 struct datarec *cpu; 191}; 192struct stats_record { 193 struct record stats; 194 struct record *rxq; 195}; 196 197static struct datarec *alloc_record_per_cpu(void) 198{ 199 unsigned int nr_cpus = bpf_num_possible_cpus(); 200 struct datarec *array; 201 202 array = calloc(nr_cpus, sizeof(struct datarec)); 203 if (!array) { 204 fprintf(stderr, "Mem alloc error (nr_cpus:%u)\n", nr_cpus); 205 exit(EXIT_FAIL_MEM); 206 } 207 return array; 208} 209 210static struct record *alloc_record_per_rxq(void) 211{ 212 unsigned int nr_rxqs = bpf_map__def(rx_queue_index_map)->max_entries; 213 struct record *array; 214 215 array = calloc(nr_rxqs, sizeof(struct record)); 216 if (!array) { 217 fprintf(stderr, "Mem alloc error (nr_rxqs:%u)\n", nr_rxqs); 218 exit(EXIT_FAIL_MEM); 219 } 220 return array; 221} 222 223static struct stats_record *alloc_stats_record(void) 224{ 225 unsigned int nr_rxqs = bpf_map__def(rx_queue_index_map)->max_entries; 226 struct stats_record *rec; 227 int i; 228 229 rec = calloc(1, sizeof(struct stats_record)); 230 if (!rec) { 231 fprintf(stderr, "Mem alloc error\n"); 232 exit(EXIT_FAIL_MEM); 233 } 234 rec->rxq = alloc_record_per_rxq(); 235 for (i = 0; i < nr_rxqs; i++) 236 rec->rxq[i].cpu = alloc_record_per_cpu(); 237 238 rec->stats.cpu = alloc_record_per_cpu(); 239 return rec; 240} 241 242static void free_stats_record(struct stats_record *r) 243{ 244 unsigned int nr_rxqs = bpf_map__def(rx_queue_index_map)->max_entries; 245 int i; 246 247 for (i = 0; i < nr_rxqs; i++) 248 free(r->rxq[i].cpu); 249 250 free(r->rxq); 251 free(r->stats.cpu); 252 free(r); 253} 254 255static bool map_collect_percpu(int fd, __u32 key, struct record *rec) 256{ 257 /* For percpu maps, userspace gets a value per possible CPU */ 258 unsigned int nr_cpus = bpf_num_possible_cpus(); 259 struct datarec values[nr_cpus]; 260 __u64 sum_processed = 0; 261 __u64 sum_issue = 0; 262 int i; 263 264 if ((bpf_map_lookup_elem(fd, &key, values)) != 0) { 265 fprintf(stderr, 266 "ERR: bpf_map_lookup_elem failed key:0x%X\n", key); 267 return false; 268 } 269 /* Get time as close as possible to reading map contents */ 270 rec->timestamp = gettime(); 271 272 /* Record and sum values from each CPU */ 273 for (i = 0; i < nr_cpus; i++) { 274 rec->cpu[i].processed = values[i].processed; 275 sum_processed += values[i].processed; 276 rec->cpu[i].issue = values[i].issue; 277 sum_issue += values[i].issue; 278 } 279 rec->total.processed = sum_processed; 280 rec->total.issue = sum_issue; 281 return true; 282} 283 284static void stats_collect(struct stats_record *rec) 285{ 286 int fd, i, max_rxqs; 287 288 fd = bpf_map__fd(stats_global_map); 289 map_collect_percpu(fd, 0, &rec->stats); 290 291 fd = bpf_map__fd(rx_queue_index_map); 292 max_rxqs = bpf_map__def(rx_queue_index_map)->max_entries; 293 for (i = 0; i < max_rxqs; i++) 294 map_collect_percpu(fd, i, &rec->rxq[i]); 295} 296 297static double calc_period(struct record *r, struct record *p) 298{ 299 double period_ = 0; 300 __u64 period = 0; 301 302 period = r->timestamp - p->timestamp; 303 if (period > 0) 304 period_ = ((double) period / NANOSEC_PER_SEC); 305 306 return period_; 307} 308 309static __u64 calc_pps(struct datarec *r, struct datarec *p, double period_) 310{ 311 __u64 packets = 0; 312 __u64 pps = 0; 313 314 if (period_ > 0) { 315 packets = r->processed - p->processed; 316 pps = packets / period_; 317 } 318 return pps; 319} 320 321static __u64 calc_errs_pps(struct datarec *r, 322 struct datarec *p, double period_) 323{ 324 __u64 packets = 0; 325 __u64 pps = 0; 326 327 if (period_ > 0) { 328 packets = r->issue - p->issue; 329 pps = packets / period_; 330 } 331 return pps; 332} 333 334static void stats_print(struct stats_record *stats_rec, 335 struct stats_record *stats_prev, 336 int action, __u32 cfg_opt) 337{ 338 unsigned int nr_rxqs = bpf_map__def(rx_queue_index_map)->max_entries; 339 unsigned int nr_cpus = bpf_num_possible_cpus(); 340 double pps = 0, err = 0; 341 struct record *rec, *prev; 342 double t; 343 int rxq; 344 int i; 345 346 /* Header */ 347 printf("\nRunning XDP on dev:%s (ifindex:%d) action:%s options:%s\n", 348 ifname, ifindex, action2str(action), options2str(cfg_opt)); 349 350 /* stats_global_map */ 351 { 352 char *fmt_rx = "%-15s %-7d %'-11.0f %'-10.0f %s\n"; 353 char *fm2_rx = "%-15s %-7s %'-11.0f\n"; 354 char *errstr = ""; 355 356 printf("%-15s %-7s %-11s %-11s\n", 357 "XDP stats", "CPU", "pps", "issue-pps"); 358 359 rec = &stats_rec->stats; 360 prev = &stats_prev->stats; 361 t = calc_period(rec, prev); 362 for (i = 0; i < nr_cpus; i++) { 363 struct datarec *r = &rec->cpu[i]; 364 struct datarec *p = &prev->cpu[i]; 365 366 pps = calc_pps (r, p, t); 367 err = calc_errs_pps(r, p, t); 368 if (err > 0) 369 errstr = "invalid-ifindex"; 370 if (pps > 0) 371 printf(fmt_rx, "XDP-RX CPU", 372 i, pps, err, errstr); 373 } 374 pps = calc_pps (&rec->total, &prev->total, t); 375 err = calc_errs_pps(&rec->total, &prev->total, t); 376 printf(fm2_rx, "XDP-RX CPU", "total", pps, err); 377 } 378 379 /* rx_queue_index_map */ 380 printf("\n%-15s %-7s %-11s %-11s\n", 381 "RXQ stats", "RXQ:CPU", "pps", "issue-pps"); 382 383 for (rxq = 0; rxq < nr_rxqs; rxq++) { 384 char *fmt_rx = "%-15s %3d:%-3d %'-11.0f %'-10.0f %s\n"; 385 char *fm2_rx = "%-15s %3d:%-3s %'-11.0f\n"; 386 char *errstr = ""; 387 int rxq_ = rxq; 388 389 /* Last RXQ in map catch overflows */ 390 if (rxq_ == nr_rxqs - 1) 391 rxq_ = -1; 392 393 rec = &stats_rec->rxq[rxq]; 394 prev = &stats_prev->rxq[rxq]; 395 t = calc_period(rec, prev); 396 for (i = 0; i < nr_cpus; i++) { 397 struct datarec *r = &rec->cpu[i]; 398 struct datarec *p = &prev->cpu[i]; 399 400 pps = calc_pps (r, p, t); 401 err = calc_errs_pps(r, p, t); 402 if (err > 0) { 403 if (rxq_ == -1) 404 errstr = "map-overflow-RXQ"; 405 else 406 errstr = "err"; 407 } 408 if (pps > 0) 409 printf(fmt_rx, "rx_queue_index", 410 rxq_, i, pps, err, errstr); 411 } 412 pps = calc_pps (&rec->total, &prev->total, t); 413 err = calc_errs_pps(&rec->total, &prev->total, t); 414 if (pps || err) 415 printf(fm2_rx, "rx_queue_index", rxq_, "sum", pps, err); 416 } 417} 418 419 420/* Pointer swap trick */ 421static inline void swap(struct stats_record **a, struct stats_record **b) 422{ 423 struct stats_record *tmp; 424 425 tmp = *a; 426 *a = *b; 427 *b = tmp; 428} 429 430static void stats_poll(int interval, int action, __u32 cfg_opt) 431{ 432 struct stats_record *record, *prev; 433 434 record = alloc_stats_record(); 435 prev = alloc_stats_record(); 436 stats_collect(record); 437 438 while (1) { 439 swap(&prev, &record); 440 stats_collect(record); 441 stats_print(record, prev, action, cfg_opt); 442 sleep(interval); 443 } 444 445 free_stats_record(record); 446 free_stats_record(prev); 447} 448 449 450int main(int argc, char **argv) 451{ 452 __u32 cfg_options= NO_TOUCH ; /* Default: Don't touch packet memory */ 453 struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; 454 struct bpf_prog_load_attr prog_load_attr = { 455 .prog_type = BPF_PROG_TYPE_XDP, 456 }; 457 struct bpf_prog_info info = {}; 458 __u32 info_len = sizeof(info); 459 int prog_fd, map_fd, opt, err; 460 bool use_separators = true; 461 struct config cfg = { 0 }; 462 struct bpf_object *obj; 463 struct bpf_map *map; 464 char filename[256]; 465 int longindex = 0; 466 int interval = 2; 467 __u32 key = 0; 468 469 470 char action_str_buf[XDP_ACTION_MAX_STRLEN + 1 /* for \0 */] = { 0 }; 471 int action = XDP_PASS; /* Default action */ 472 char *action_str = NULL; 473 474 snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]); 475 prog_load_attr.file = filename; 476 477 if (setrlimit(RLIMIT_MEMLOCK, &r)) { 478 perror("setrlimit(RLIMIT_MEMLOCK)"); 479 return 1; 480 } 481 482 if (bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd)) 483 return EXIT_FAIL; 484 485 map = bpf_object__find_map_by_name(obj, "config_map"); 486 stats_global_map = bpf_object__find_map_by_name(obj, "stats_global_map"); 487 rx_queue_index_map = bpf_object__find_map_by_name(obj, "rx_queue_index_map"); 488 if (!map || !stats_global_map || !rx_queue_index_map) { 489 printf("finding a map in obj file failed\n"); 490 return EXIT_FAIL; 491 } 492 map_fd = bpf_map__fd(map); 493 494 if (!prog_fd) { 495 fprintf(stderr, "ERR: bpf_prog_load_xattr: %s\n", strerror(errno)); 496 return EXIT_FAIL; 497 } 498 499 /* Parse commands line args */ 500 while ((opt = getopt_long(argc, argv, "FhSrmzd:s:a:", 501 long_options, &longindex)) != -1) { 502 switch (opt) { 503 case 'd': 504 if (strlen(optarg) >= IF_NAMESIZE) { 505 fprintf(stderr, "ERR: --dev name too long\n"); 506 goto error; 507 } 508 ifname = (char *)&ifname_buf; 509 strncpy(ifname, optarg, IF_NAMESIZE); 510 ifindex = if_nametoindex(ifname); 511 if (ifindex == 0) { 512 fprintf(stderr, 513 "ERR: --dev name unknown err(%d):%s\n", 514 errno, strerror(errno)); 515 goto error; 516 } 517 break; 518 case 's': 519 interval = atoi(optarg); 520 break; 521 case 'S': 522 xdp_flags |= XDP_FLAGS_SKB_MODE; 523 break; 524 case 'z': 525 use_separators = false; 526 break; 527 case 'a': 528 action_str = (char *)&action_str_buf; 529 strncpy(action_str, optarg, XDP_ACTION_MAX_STRLEN); 530 break; 531 case 'r': 532 cfg_options |= READ_MEM; 533 break; 534 case 'm': 535 cfg_options |= SWAP_MAC; 536 break; 537 case 'F': 538 xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST; 539 break; 540 case 'h': 541 error: 542 default: 543 usage(argv); 544 return EXIT_FAIL_OPTION; 545 } 546 } 547 548 if (!(xdp_flags & XDP_FLAGS_SKB_MODE)) 549 xdp_flags |= XDP_FLAGS_DRV_MODE; 550 551 /* Required option */ 552 if (ifindex == -1) { 553 fprintf(stderr, "ERR: required option --dev missing\n"); 554 usage(argv); 555 return EXIT_FAIL_OPTION; 556 } 557 cfg.ifindex = ifindex; 558 559 /* Parse action string */ 560 if (action_str) { 561 action = parse_xdp_action(action_str); 562 if (action < 0) { 563 fprintf(stderr, "ERR: Invalid XDP --action: %s\n", 564 action_str); 565 list_xdp_actions(); 566 return EXIT_FAIL_OPTION; 567 } 568 } 569 cfg.action = action; 570 571 /* XDP_TX requires changing MAC-addrs, else HW may drop */ 572 if (action == XDP_TX) 573 cfg_options |= SWAP_MAC; 574 cfg.options = cfg_options; 575 576 /* Trick to pretty printf with thousands separators use %' */ 577 if (use_separators) 578 setlocale(LC_NUMERIC, "en_US"); 579 580 /* User-side setup ifindex in config_map */ 581 err = bpf_map_update_elem(map_fd, &key, &cfg, 0); 582 if (err) { 583 fprintf(stderr, "Store config failed (err:%d)\n", err); 584 exit(EXIT_FAIL_BPF); 585 } 586 587 /* Remove XDP program when program is interrupted or killed */ 588 signal(SIGINT, int_exit); 589 signal(SIGTERM, int_exit); 590 591 if (bpf_set_link_xdp_fd(ifindex, prog_fd, xdp_flags) < 0) { 592 fprintf(stderr, "link set xdp fd failed\n"); 593 return EXIT_FAIL_XDP; 594 } 595 596 err = bpf_obj_get_info_by_fd(prog_fd, &info, &info_len); 597 if (err) { 598 printf("can't get prog info - %s\n", strerror(errno)); 599 return err; 600 } 601 prog_id = info.id; 602 603 stats_poll(interval, action, cfg_options); 604 return EXIT_OK; 605} 606