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