| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2020 Facebook */ |
| #include <asm/barrier.h> |
| #include <linux/perf_event.h> |
| #include <linux/ring_buffer.h> |
| #include <sys/epoll.h> |
| #include <sys/mman.h> |
| #include <argp.h> |
| #include <stdlib.h> |
| #include "bench.h" |
| #include "ringbuf_bench.skel.h" |
| #include "perfbuf_bench.skel.h" |
| |
| static struct { |
| bool back2back; |
| int batch_cnt; |
| bool sampled; |
| int sample_rate; |
| int ringbuf_sz; /* per-ringbuf, in bytes */ |
| bool ringbuf_use_output; /* use slower output API */ |
| int perfbuf_sz; /* per-CPU size, in pages */ |
| } args = { |
| .back2back = false, |
| .batch_cnt = 500, |
| .sampled = false, |
| .sample_rate = 500, |
| .ringbuf_sz = 512 * 1024, |
| .ringbuf_use_output = false, |
| .perfbuf_sz = 128, |
| }; |
| |
| enum { |
| ARG_RB_BACK2BACK = 2000, |
| ARG_RB_USE_OUTPUT = 2001, |
| ARG_RB_BATCH_CNT = 2002, |
| ARG_RB_SAMPLED = 2003, |
| ARG_RB_SAMPLE_RATE = 2004, |
| }; |
| |
| static const struct argp_option opts[] = { |
| { "rb-b2b", ARG_RB_BACK2BACK, NULL, 0, "Back-to-back mode"}, |
| { "rb-use-output", ARG_RB_USE_OUTPUT, NULL, 0, "Use bpf_ringbuf_output() instead of bpf_ringbuf_reserve()"}, |
| { "rb-batch-cnt", ARG_RB_BATCH_CNT, "CNT", 0, "Set BPF-side record batch count"}, |
| { "rb-sampled", ARG_RB_SAMPLED, NULL, 0, "Notification sampling"}, |
| { "rb-sample-rate", ARG_RB_SAMPLE_RATE, "RATE", 0, "Notification sample rate"}, |
| {}, |
| }; |
| |
| static error_t parse_arg(int key, char *arg, struct argp_state *state) |
| { |
| switch (key) { |
| case ARG_RB_BACK2BACK: |
| args.back2back = true; |
| break; |
| case ARG_RB_USE_OUTPUT: |
| args.ringbuf_use_output = true; |
| break; |
| case ARG_RB_BATCH_CNT: |
| args.batch_cnt = strtol(arg, NULL, 10); |
| if (args.batch_cnt < 0) { |
| fprintf(stderr, "Invalid batch count."); |
| argp_usage(state); |
| } |
| break; |
| case ARG_RB_SAMPLED: |
| args.sampled = true; |
| break; |
| case ARG_RB_SAMPLE_RATE: |
| args.sample_rate = strtol(arg, NULL, 10); |
| if (args.sample_rate < 0) { |
| fprintf(stderr, "Invalid perfbuf sample rate."); |
| argp_usage(state); |
| } |
| break; |
| default: |
| return ARGP_ERR_UNKNOWN; |
| } |
| return 0; |
| } |
| |
| /* exported into benchmark runner */ |
| const struct argp bench_ringbufs_argp = { |
| .options = opts, |
| .parser = parse_arg, |
| }; |
| |
| /* RINGBUF-LIBBPF benchmark */ |
| |
| static struct counter buf_hits; |
| |
| static inline void bufs_trigger_batch() |
| { |
| (void)syscall(__NR_getpgid); |
| } |
| |
| static void bufs_validate() |
| { |
| if (env.consumer_cnt != 1) { |
| fprintf(stderr, "rb-libbpf benchmark doesn't support multi-consumer!\n"); |
| exit(1); |
| } |
| |
| if (args.back2back && env.producer_cnt > 1) { |
| fprintf(stderr, "back-to-back mode makes sense only for single-producer case!\n"); |
| exit(1); |
| } |
| } |
| |
| static void *bufs_sample_producer(void *input) |
| { |
| if (args.back2back) { |
| /* initial batch to get everything started */ |
| bufs_trigger_batch(); |
| return NULL; |
| } |
| |
| while (true) |
| bufs_trigger_batch(); |
| return NULL; |
| } |
| |
| static struct ringbuf_libbpf_ctx { |
| struct ringbuf_bench *skel; |
| struct ring_buffer *ringbuf; |
| } ringbuf_libbpf_ctx; |
| |
| static void ringbuf_libbpf_measure(struct bench_res *res) |
| { |
| struct ringbuf_libbpf_ctx *ctx = &ringbuf_libbpf_ctx; |
| |
| res->hits = atomic_swap(&buf_hits.value, 0); |
| res->drops = atomic_swap(&ctx->skel->bss->dropped, 0); |
| } |
| |
| static struct ringbuf_bench *ringbuf_setup_skeleton() |
| { |
| struct ringbuf_bench *skel; |
| |
| setup_libbpf(); |
| |
| skel = ringbuf_bench__open(); |
| if (!skel) { |
| fprintf(stderr, "failed to open skeleton\n"); |
| exit(1); |
| } |
| |
| skel->rodata->batch_cnt = args.batch_cnt; |
| skel->rodata->use_output = args.ringbuf_use_output ? 1 : 0; |
| |
| if (args.sampled) |
| /* record data + header take 16 bytes */ |
| skel->rodata->wakeup_data_size = args.sample_rate * 16; |
| |
| bpf_map__resize(skel->maps.ringbuf, args.ringbuf_sz); |
| |
| if (ringbuf_bench__load(skel)) { |
| fprintf(stderr, "failed to load skeleton\n"); |
| exit(1); |
| } |
| |
| return skel; |
| } |
| |
| static int buf_process_sample(void *ctx, void *data, size_t len) |
| { |
| atomic_inc(&buf_hits.value); |
| return 0; |
| } |
| |
| static void ringbuf_libbpf_setup() |
| { |
| struct ringbuf_libbpf_ctx *ctx = &ringbuf_libbpf_ctx; |
| struct bpf_link *link; |
| |
| ctx->skel = ringbuf_setup_skeleton(); |
| ctx->ringbuf = ring_buffer__new(bpf_map__fd(ctx->skel->maps.ringbuf), |
| buf_process_sample, NULL, NULL); |
| if (!ctx->ringbuf) { |
| fprintf(stderr, "failed to create ringbuf\n"); |
| exit(1); |
| } |
| |
| link = bpf_program__attach(ctx->skel->progs.bench_ringbuf); |
| if (!link) { |
| fprintf(stderr, "failed to attach program!\n"); |
| exit(1); |
| } |
| } |
| |
| static void *ringbuf_libbpf_consumer(void *input) |
| { |
| struct ringbuf_libbpf_ctx *ctx = &ringbuf_libbpf_ctx; |
| |
| while (ring_buffer__poll(ctx->ringbuf, -1) >= 0) { |
| if (args.back2back) |
| bufs_trigger_batch(); |
| } |
| fprintf(stderr, "ringbuf polling failed!\n"); |
| return NULL; |
| } |
| |
| /* RINGBUF-CUSTOM benchmark */ |
| struct ringbuf_custom { |
| __u64 *consumer_pos; |
| __u64 *producer_pos; |
| __u64 mask; |
| void *data; |
| int map_fd; |
| }; |
| |
| static struct ringbuf_custom_ctx { |
| struct ringbuf_bench *skel; |
| struct ringbuf_custom ringbuf; |
| int epoll_fd; |
| struct epoll_event event; |
| } ringbuf_custom_ctx; |
| |
| static void ringbuf_custom_measure(struct bench_res *res) |
| { |
| struct ringbuf_custom_ctx *ctx = &ringbuf_custom_ctx; |
| |
| res->hits = atomic_swap(&buf_hits.value, 0); |
| res->drops = atomic_swap(&ctx->skel->bss->dropped, 0); |
| } |
| |
| static void ringbuf_custom_setup() |
| { |
| struct ringbuf_custom_ctx *ctx = &ringbuf_custom_ctx; |
| const size_t page_size = getpagesize(); |
| struct bpf_link *link; |
| struct ringbuf_custom *r; |
| void *tmp; |
| int err; |
| |
| ctx->skel = ringbuf_setup_skeleton(); |
| |
| ctx->epoll_fd = epoll_create1(EPOLL_CLOEXEC); |
| if (ctx->epoll_fd < 0) { |
| fprintf(stderr, "failed to create epoll fd: %d\n", -errno); |
| exit(1); |
| } |
| |
| r = &ctx->ringbuf; |
| r->map_fd = bpf_map__fd(ctx->skel->maps.ringbuf); |
| r->mask = args.ringbuf_sz - 1; |
| |
| /* Map writable consumer page */ |
| tmp = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, |
| r->map_fd, 0); |
| if (tmp == MAP_FAILED) { |
| fprintf(stderr, "failed to mmap consumer page: %d\n", -errno); |
| exit(1); |
| } |
| r->consumer_pos = tmp; |
| |
| /* Map read-only producer page and data pages. */ |
| tmp = mmap(NULL, page_size + 2 * args.ringbuf_sz, PROT_READ, MAP_SHARED, |
| r->map_fd, page_size); |
| if (tmp == MAP_FAILED) { |
| fprintf(stderr, "failed to mmap data pages: %d\n", -errno); |
| exit(1); |
| } |
| r->producer_pos = tmp; |
| r->data = tmp + page_size; |
| |
| ctx->event.events = EPOLLIN; |
| err = epoll_ctl(ctx->epoll_fd, EPOLL_CTL_ADD, r->map_fd, &ctx->event); |
| if (err < 0) { |
| fprintf(stderr, "failed to epoll add ringbuf: %d\n", -errno); |
| exit(1); |
| } |
| |
| link = bpf_program__attach(ctx->skel->progs.bench_ringbuf); |
| if (!link) { |
| fprintf(stderr, "failed to attach program\n"); |
| exit(1); |
| } |
| } |
| |
| #define RINGBUF_BUSY_BIT (1 << 31) |
| #define RINGBUF_DISCARD_BIT (1 << 30) |
| #define RINGBUF_META_LEN 8 |
| |
| static inline int roundup_len(__u32 len) |
| { |
| /* clear out top 2 bits */ |
| len <<= 2; |
| len >>= 2; |
| /* add length prefix */ |
| len += RINGBUF_META_LEN; |
| /* round up to 8 byte alignment */ |
| return (len + 7) / 8 * 8; |
| } |
| |
| static void ringbuf_custom_process_ring(struct ringbuf_custom *r) |
| { |
| unsigned long cons_pos, prod_pos; |
| int *len_ptr, len; |
| bool got_new_data; |
| |
| cons_pos = smp_load_acquire(r->consumer_pos); |
| while (true) { |
| got_new_data = false; |
| prod_pos = smp_load_acquire(r->producer_pos); |
| while (cons_pos < prod_pos) { |
| len_ptr = r->data + (cons_pos & r->mask); |
| len = smp_load_acquire(len_ptr); |
| |
| /* sample not committed yet, bail out for now */ |
| if (len & RINGBUF_BUSY_BIT) |
| return; |
| |
| got_new_data = true; |
| cons_pos += roundup_len(len); |
| |
| atomic_inc(&buf_hits.value); |
| } |
| if (got_new_data) |
| smp_store_release(r->consumer_pos, cons_pos); |
| else |
| break; |
| } |
| } |
| |
| static void *ringbuf_custom_consumer(void *input) |
| { |
| struct ringbuf_custom_ctx *ctx = &ringbuf_custom_ctx; |
| int cnt; |
| |
| do { |
| if (args.back2back) |
| bufs_trigger_batch(); |
| cnt = epoll_wait(ctx->epoll_fd, &ctx->event, 1, -1); |
| if (cnt > 0) |
| ringbuf_custom_process_ring(&ctx->ringbuf); |
| } while (cnt >= 0); |
| fprintf(stderr, "ringbuf polling failed!\n"); |
| return 0; |
| } |
| |
| /* PERFBUF-LIBBPF benchmark */ |
| static struct perfbuf_libbpf_ctx { |
| struct perfbuf_bench *skel; |
| struct perf_buffer *perfbuf; |
| } perfbuf_libbpf_ctx; |
| |
| static void perfbuf_measure(struct bench_res *res) |
| { |
| struct perfbuf_libbpf_ctx *ctx = &perfbuf_libbpf_ctx; |
| |
| res->hits = atomic_swap(&buf_hits.value, 0); |
| res->drops = atomic_swap(&ctx->skel->bss->dropped, 0); |
| } |
| |
| static struct perfbuf_bench *perfbuf_setup_skeleton() |
| { |
| struct perfbuf_bench *skel; |
| |
| setup_libbpf(); |
| |
| skel = perfbuf_bench__open(); |
| if (!skel) { |
| fprintf(stderr, "failed to open skeleton\n"); |
| exit(1); |
| } |
| |
| skel->rodata->batch_cnt = args.batch_cnt; |
| |
| if (perfbuf_bench__load(skel)) { |
| fprintf(stderr, "failed to load skeleton\n"); |
| exit(1); |
| } |
| |
| return skel; |
| } |
| |
| static enum bpf_perf_event_ret |
| perfbuf_process_sample_raw(void *input_ctx, int cpu, |
| struct perf_event_header *e) |
| { |
| switch (e->type) { |
| case PERF_RECORD_SAMPLE: |
| atomic_inc(&buf_hits.value); |
| break; |
| case PERF_RECORD_LOST: |
| break; |
| default: |
| return LIBBPF_PERF_EVENT_ERROR; |
| } |
| return LIBBPF_PERF_EVENT_CONT; |
| } |
| |
| static void perfbuf_libbpf_setup() |
| { |
| struct perfbuf_libbpf_ctx *ctx = &perfbuf_libbpf_ctx; |
| struct perf_event_attr attr; |
| struct perf_buffer_raw_opts pb_opts = { |
| .event_cb = perfbuf_process_sample_raw, |
| .ctx = (void *)(long)0, |
| .attr = &attr, |
| }; |
| struct bpf_link *link; |
| |
| ctx->skel = perfbuf_setup_skeleton(); |
| |
| memset(&attr, 0, sizeof(attr)); |
| attr.config = PERF_COUNT_SW_BPF_OUTPUT, |
| attr.type = PERF_TYPE_SOFTWARE; |
| attr.sample_type = PERF_SAMPLE_RAW; |
| /* notify only every Nth sample */ |
| if (args.sampled) { |
| attr.sample_period = args.sample_rate; |
| attr.wakeup_events = args.sample_rate; |
| } else { |
| attr.sample_period = 1; |
| attr.wakeup_events = 1; |
| } |
| |
| if (args.sample_rate > args.batch_cnt) { |
| fprintf(stderr, "sample rate %d is too high for given batch count %d\n", |
| args.sample_rate, args.batch_cnt); |
| exit(1); |
| } |
| |
| ctx->perfbuf = perf_buffer__new_raw(bpf_map__fd(ctx->skel->maps.perfbuf), |
| args.perfbuf_sz, &pb_opts); |
| if (!ctx->perfbuf) { |
| fprintf(stderr, "failed to create perfbuf\n"); |
| exit(1); |
| } |
| |
| link = bpf_program__attach(ctx->skel->progs.bench_perfbuf); |
| if (!link) { |
| fprintf(stderr, "failed to attach program\n"); |
| exit(1); |
| } |
| } |
| |
| static void *perfbuf_libbpf_consumer(void *input) |
| { |
| struct perfbuf_libbpf_ctx *ctx = &perfbuf_libbpf_ctx; |
| |
| while (perf_buffer__poll(ctx->perfbuf, -1) >= 0) { |
| if (args.back2back) |
| bufs_trigger_batch(); |
| } |
| fprintf(stderr, "perfbuf polling failed!\n"); |
| return NULL; |
| } |
| |
| /* PERFBUF-CUSTOM benchmark */ |
| |
| /* copies of internal libbpf definitions */ |
| struct perf_cpu_buf { |
| struct perf_buffer *pb; |
| void *base; /* mmap()'ed memory */ |
| void *buf; /* for reconstructing segmented data */ |
| size_t buf_size; |
| int fd; |
| int cpu; |
| int map_key; |
| }; |
| |
| struct perf_buffer { |
| perf_buffer_event_fn event_cb; |
| perf_buffer_sample_fn sample_cb; |
| perf_buffer_lost_fn lost_cb; |
| void *ctx; /* passed into callbacks */ |
| |
| size_t page_size; |
| size_t mmap_size; |
| struct perf_cpu_buf **cpu_bufs; |
| struct epoll_event *events; |
| int cpu_cnt; /* number of allocated CPU buffers */ |
| int epoll_fd; /* perf event FD */ |
| int map_fd; /* BPF_MAP_TYPE_PERF_EVENT_ARRAY BPF map FD */ |
| }; |
| |
| static void *perfbuf_custom_consumer(void *input) |
| { |
| struct perfbuf_libbpf_ctx *ctx = &perfbuf_libbpf_ctx; |
| struct perf_buffer *pb = ctx->perfbuf; |
| struct perf_cpu_buf *cpu_buf; |
| struct perf_event_mmap_page *header; |
| size_t mmap_mask = pb->mmap_size - 1; |
| struct perf_event_header *ehdr; |
| __u64 data_head, data_tail; |
| size_t ehdr_size; |
| void *base; |
| int i, cnt; |
| |
| while (true) { |
| if (args.back2back) |
| bufs_trigger_batch(); |
| cnt = epoll_wait(pb->epoll_fd, pb->events, pb->cpu_cnt, -1); |
| if (cnt <= 0) { |
| fprintf(stderr, "perf epoll failed: %d\n", -errno); |
| exit(1); |
| } |
| |
| for (i = 0; i < cnt; ++i) { |
| cpu_buf = pb->events[i].data.ptr; |
| header = cpu_buf->base; |
| base = ((void *)header) + pb->page_size; |
| |
| data_head = ring_buffer_read_head(header); |
| data_tail = header->data_tail; |
| while (data_head != data_tail) { |
| ehdr = base + (data_tail & mmap_mask); |
| ehdr_size = ehdr->size; |
| |
| if (ehdr->type == PERF_RECORD_SAMPLE) |
| atomic_inc(&buf_hits.value); |
| |
| data_tail += ehdr_size; |
| } |
| ring_buffer_write_tail(header, data_tail); |
| } |
| } |
| return NULL; |
| } |
| |
| const struct bench bench_rb_libbpf = { |
| .name = "rb-libbpf", |
| .validate = bufs_validate, |
| .setup = ringbuf_libbpf_setup, |
| .producer_thread = bufs_sample_producer, |
| .consumer_thread = ringbuf_libbpf_consumer, |
| .measure = ringbuf_libbpf_measure, |
| .report_progress = hits_drops_report_progress, |
| .report_final = hits_drops_report_final, |
| }; |
| |
| const struct bench bench_rb_custom = { |
| .name = "rb-custom", |
| .validate = bufs_validate, |
| .setup = ringbuf_custom_setup, |
| .producer_thread = bufs_sample_producer, |
| .consumer_thread = ringbuf_custom_consumer, |
| .measure = ringbuf_custom_measure, |
| .report_progress = hits_drops_report_progress, |
| .report_final = hits_drops_report_final, |
| }; |
| |
| const struct bench bench_pb_libbpf = { |
| .name = "pb-libbpf", |
| .validate = bufs_validate, |
| .setup = perfbuf_libbpf_setup, |
| .producer_thread = bufs_sample_producer, |
| .consumer_thread = perfbuf_libbpf_consumer, |
| .measure = perfbuf_measure, |
| .report_progress = hits_drops_report_progress, |
| .report_final = hits_drops_report_final, |
| }; |
| |
| const struct bench bench_pb_custom = { |
| .name = "pb-custom", |
| .validate = bufs_validate, |
| .setup = perfbuf_libbpf_setup, |
| .producer_thread = bufs_sample_producer, |
| .consumer_thread = perfbuf_custom_consumer, |
| .measure = perfbuf_measure, |
| .report_progress = hits_drops_report_progress, |
| .report_final = hits_drops_report_final, |
| }; |
| |