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