// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) /* Copyright (C) 2018 Netronome Systems, Inc. */ /* This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #define MMAP_PAGE_CNT 16 static volatile sig_atomic_t stop; struct perf_event_sample { struct perf_event_header header; __u64 time; __u32 size; unsigned char data[]; }; struct perf_event_lost { struct perf_event_header header; __u64 id; __u64 lost; }; static void int_exit(int signo) { stop = true; } struct event_pipe_ctx { bool all_cpus; int cpu; int idx; }; static enum bpf_perf_event_ret print_bpf_output(void *private_data, int cpu, struct perf_event_header *event) { struct perf_event_sample *e = container_of(event, struct perf_event_sample, header); struct perf_event_lost *lost = container_of(event, struct perf_event_lost, header); struct event_pipe_ctx *ctx = private_data; int idx = ctx->all_cpus ? cpu : ctx->idx; if (json_output) { jsonw_start_object(json_wtr); jsonw_name(json_wtr, "type"); jsonw_uint(json_wtr, e->header.type); jsonw_name(json_wtr, "cpu"); jsonw_uint(json_wtr, cpu); jsonw_name(json_wtr, "index"); jsonw_uint(json_wtr, idx); if (e->header.type == PERF_RECORD_SAMPLE) { jsonw_name(json_wtr, "timestamp"); jsonw_uint(json_wtr, e->time); jsonw_name(json_wtr, "data"); print_data_json(e->data, e->size); } else if (e->header.type == PERF_RECORD_LOST) { jsonw_name(json_wtr, "lost"); jsonw_start_object(json_wtr); jsonw_name(json_wtr, "id"); jsonw_uint(json_wtr, lost->id); jsonw_name(json_wtr, "count"); jsonw_uint(json_wtr, lost->lost); jsonw_end_object(json_wtr); } jsonw_end_object(json_wtr); } else { if (e->header.type == PERF_RECORD_SAMPLE) { printf("== @%llu.%09llu CPU: %d index: %d =====\n", e->time / 1000000000ULL, e->time % 1000000000ULL, cpu, idx); fprint_hex(stdout, e->data, e->size, " "); printf("\n"); } else if (e->header.type == PERF_RECORD_LOST) { printf("lost %llu events\n", lost->lost); } else { printf("unknown event type=%u size=%u\n", e->header.type, e->header.size); } } return LIBBPF_PERF_EVENT_CONT; } static int print_ringbuf_output(void *ctx, void *data, size_t size) { if (json_output) { jsonw_start_object(json_wtr); jsonw_uint_field(json_wtr, "size", size); jsonw_name(json_wtr, "data"); print_data_json(data, size); jsonw_end_object(json_wtr); } else { printf("== size: %zu =====\n", size); fprint_hex(stdout, data, size, " "); printf("\n"); } if (fflush(stdout)) return errno ? -errno : -EIO; /* A producer can keep poll() busy even after a signal arrives. */ return stop ? -EINTR : 0; } int do_event_pipe(int argc, char **argv) { struct perf_event_attr perf_attr = { .sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_TIME, .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_BPF_OUTPUT, .sample_period = 1, .wakeup_events = 1, }; struct bpf_map_info map_info = {}; LIBBPF_OPTS(perf_buffer_raw_opts, opts); struct event_pipe_ctx ctx = { .all_cpus = true, .cpu = -1, .idx = -1, }; struct perf_buffer *pb = NULL; struct ring_buffer *rb = NULL; __u32 map_info_len; int err, map_fd; stop = false; map_info_len = sizeof(map_info); map_fd = map_parse_fd_and_info(&argc, &argv, &map_info, &map_info_len, 0); if (map_fd < 0) return -1; if (map_info.type != BPF_MAP_TYPE_PERF_EVENT_ARRAY && map_info.type != BPF_MAP_TYPE_RINGBUF) { p_err("map is not a perf event array or ring buffer"); goto err_close_map; } if (map_info.type == BPF_MAP_TYPE_RINGBUF && argc && (is_prefix(*argv, "cpu") || is_prefix(*argv, "index"))) { p_err("ring buffer maps do not support cpu or index arguments"); goto err_close_map; } while (argc) { if (argc < 2) { BAD_ARG(); goto err_close_map; } if (is_prefix(*argv, "cpu")) { char *endptr; NEXT_ARG(); ctx.cpu = strtoul(*argv, &endptr, 0); if (*endptr) { p_err("can't parse %s as CPU ID", *argv); goto err_close_map; } NEXT_ARG(); } else if (is_prefix(*argv, "index")) { char *endptr; NEXT_ARG(); ctx.idx = strtoul(*argv, &endptr, 0); if (*endptr) { p_err("can't parse %s as index", *argv); goto err_close_map; } NEXT_ARG(); } else { BAD_ARG(); goto err_close_map; } ctx.all_cpus = false; } if (!ctx.all_cpus) { if (ctx.idx == -1 || ctx.cpu == -1) { p_err("cpu and index must be specified together"); goto err_close_map; } } else { ctx.cpu = 0; ctx.idx = 0; } if (map_info.type == BPF_MAP_TYPE_RINGBUF) { rb = ring_buffer__new(map_fd, print_ringbuf_output, NULL, NULL); if (!rb) { p_err("failed to create ring buffer: %s (%d)", strerror(errno), errno); goto err_close_map; } } else { opts.cpu_cnt = ctx.all_cpus ? 0 : 1; opts.cpus = &ctx.cpu; opts.map_keys = &ctx.idx; pb = perf_buffer__new_raw(map_fd, MMAP_PAGE_CNT, &perf_attr, print_bpf_output, &ctx, &opts); if (!pb) { p_err("failed to create perf buffer: %s (%d)", strerror(errno), errno); goto err_close_map; } } signal(SIGINT, int_exit); signal(SIGHUP, int_exit); signal(SIGTERM, int_exit); if (json_output) jsonw_start_array(json_wtr); err = 0; while (!stop) { err = rb ? ring_buffer__poll(rb, 200) : perf_buffer__poll(pb, 200); if (err < 0 && err != -EINTR) { p_err("%s buffer polling failed: %s (%d)", rb ? "ring" : "perf", strerror(-err), -err); break; } err = 0; } if (stop) fprintf(stderr, "Stopping...\n"); if (json_output) jsonw_end_array(json_wtr); if (fflush(stdout)) { p_err("failed to write events: %s", strerror(errno)); err = -1; } ring_buffer__free(rb); perf_buffer__free(pb); close(map_fd); return err < 0 ? -1 : 0; err_close_map: close(map_fd); return -1; }