1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | /* Copyright (C) 2018 Netronome Systems, Inc. */ |
3 | /* This program is free software; you can redistribute it and/or |
4 | * modify it under the terms of version 2 of the GNU General Public |
5 | * License as published by the Free Software Foundation. |
6 | */ |
7 | #include <errno.h> |
8 | #include <fcntl.h> |
9 | #include <bpf/libbpf.h> |
10 | #include <poll.h> |
11 | #include <signal.h> |
12 | #include <stdbool.h> |
13 | #include <stdio.h> |
14 | #include <stdlib.h> |
15 | #include <string.h> |
16 | #include <time.h> |
17 | #include <unistd.h> |
18 | #include <linux/bpf.h> |
19 | #include <linux/perf_event.h> |
20 | #include <sys/ioctl.h> |
21 | #include <sys/mman.h> |
22 | #include <sys/syscall.h> |
23 | |
24 | #include <bpf/bpf.h> |
25 | |
26 | #include "main.h" |
27 | |
28 | #define MMAP_PAGE_CNT 16 |
29 | |
30 | static volatile bool stop; |
31 | |
32 | struct perf_event_sample { |
33 | struct perf_event_header ; |
34 | __u64 time; |
35 | __u32 size; |
36 | unsigned char data[]; |
37 | }; |
38 | |
39 | struct perf_event_lost { |
40 | struct perf_event_header ; |
41 | __u64 id; |
42 | __u64 lost; |
43 | }; |
44 | |
45 | static void int_exit(int signo) |
46 | { |
47 | fprintf(stderr, "Stopping...\n" ); |
48 | stop = true; |
49 | } |
50 | |
51 | struct event_pipe_ctx { |
52 | bool all_cpus; |
53 | int cpu; |
54 | int idx; |
55 | }; |
56 | |
57 | static enum bpf_perf_event_ret |
58 | print_bpf_output(void *private_data, int cpu, struct perf_event_header *event) |
59 | { |
60 | struct perf_event_sample *e = container_of(event, |
61 | struct perf_event_sample, |
62 | header); |
63 | struct perf_event_lost *lost = container_of(event, |
64 | struct perf_event_lost, |
65 | header); |
66 | struct event_pipe_ctx *ctx = private_data; |
67 | int idx = ctx->all_cpus ? cpu : ctx->idx; |
68 | |
69 | if (json_output) { |
70 | jsonw_start_object(self: json_wtr); |
71 | jsonw_name(self: json_wtr, name: "type" ); |
72 | jsonw_uint(self: json_wtr, number: e->header.type); |
73 | jsonw_name(self: json_wtr, name: "cpu" ); |
74 | jsonw_uint(self: json_wtr, number: cpu); |
75 | jsonw_name(self: json_wtr, name: "index" ); |
76 | jsonw_uint(self: json_wtr, number: idx); |
77 | if (e->header.type == PERF_RECORD_SAMPLE) { |
78 | jsonw_name(self: json_wtr, name: "timestamp" ); |
79 | jsonw_uint(self: json_wtr, number: e->time); |
80 | jsonw_name(self: json_wtr, name: "data" ); |
81 | print_data_json(data: e->data, len: e->size); |
82 | } else if (e->header.type == PERF_RECORD_LOST) { |
83 | jsonw_name(self: json_wtr, name: "lost" ); |
84 | jsonw_start_object(self: json_wtr); |
85 | jsonw_name(self: json_wtr, name: "id" ); |
86 | jsonw_uint(self: json_wtr, number: lost->id); |
87 | jsonw_name(self: json_wtr, name: "count" ); |
88 | jsonw_uint(self: json_wtr, number: lost->lost); |
89 | jsonw_end_object(self: json_wtr); |
90 | } |
91 | jsonw_end_object(self: json_wtr); |
92 | } else { |
93 | if (e->header.type == PERF_RECORD_SAMPLE) { |
94 | printf("== @%lld.%09lld CPU: %d index: %d =====\n" , |
95 | e->time / 1000000000ULL, e->time % 1000000000ULL, |
96 | cpu, idx); |
97 | fprint_hex(stdout, e->data, e->size, " " ); |
98 | printf("\n" ); |
99 | } else if (e->header.type == PERF_RECORD_LOST) { |
100 | printf("lost %lld events\n" , lost->lost); |
101 | } else { |
102 | printf("unknown event type=%d size=%d\n" , |
103 | e->header.type, e->header.size); |
104 | } |
105 | } |
106 | |
107 | return LIBBPF_PERF_EVENT_CONT; |
108 | } |
109 | |
110 | int do_event_pipe(int argc, char **argv) |
111 | { |
112 | struct perf_event_attr perf_attr = { |
113 | .sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_TIME, |
114 | .type = PERF_TYPE_SOFTWARE, |
115 | .config = PERF_COUNT_SW_BPF_OUTPUT, |
116 | .sample_period = 1, |
117 | .wakeup_events = 1, |
118 | }; |
119 | struct bpf_map_info map_info = {}; |
120 | LIBBPF_OPTS(perf_buffer_raw_opts, opts); |
121 | struct event_pipe_ctx ctx = { |
122 | .all_cpus = true, |
123 | .cpu = -1, |
124 | .idx = -1, |
125 | }; |
126 | struct perf_buffer *pb; |
127 | __u32 map_info_len; |
128 | int err, map_fd; |
129 | |
130 | map_info_len = sizeof(map_info); |
131 | map_fd = map_parse_fd_and_info(argc: &argc, argv: &argv, info: &map_info, info_len: &map_info_len); |
132 | if (map_fd < 0) |
133 | return -1; |
134 | |
135 | if (map_info.type != BPF_MAP_TYPE_PERF_EVENT_ARRAY) { |
136 | p_err(fmt: "map is not a perf event array" ); |
137 | goto err_close_map; |
138 | } |
139 | |
140 | while (argc) { |
141 | if (argc < 2) { |
142 | BAD_ARG(); |
143 | goto err_close_map; |
144 | } |
145 | |
146 | if (is_prefix(pfx: *argv, str: "cpu" )) { |
147 | char *endptr; |
148 | |
149 | NEXT_ARG(); |
150 | ctx.cpu = strtoul(*argv, &endptr, 0); |
151 | if (*endptr) { |
152 | p_err(fmt: "can't parse %s as CPU ID" , *argv); |
153 | goto err_close_map; |
154 | } |
155 | |
156 | NEXT_ARG(); |
157 | } else if (is_prefix(pfx: *argv, str: "index" )) { |
158 | char *endptr; |
159 | |
160 | NEXT_ARG(); |
161 | ctx.idx = strtoul(*argv, &endptr, 0); |
162 | if (*endptr) { |
163 | p_err(fmt: "can't parse %s as index" , *argv); |
164 | goto err_close_map; |
165 | } |
166 | |
167 | NEXT_ARG(); |
168 | } else { |
169 | BAD_ARG(); |
170 | goto err_close_map; |
171 | } |
172 | |
173 | ctx.all_cpus = false; |
174 | } |
175 | |
176 | if (!ctx.all_cpus) { |
177 | if (ctx.idx == -1 || ctx.cpu == -1) { |
178 | p_err(fmt: "cpu and index must be specified together" ); |
179 | goto err_close_map; |
180 | } |
181 | } else { |
182 | ctx.cpu = 0; |
183 | ctx.idx = 0; |
184 | } |
185 | |
186 | opts.cpu_cnt = ctx.all_cpus ? 0 : 1; |
187 | opts.cpus = &ctx.cpu; |
188 | opts.map_keys = &ctx.idx; |
189 | pb = perf_buffer__new_raw(map_fd, MMAP_PAGE_CNT, &perf_attr, |
190 | print_bpf_output, &ctx, &opts); |
191 | if (!pb) { |
192 | p_err(fmt: "failed to create perf buffer: %s (%d)" , |
193 | strerror(errno), errno); |
194 | goto err_close_map; |
195 | } |
196 | |
197 | signal(SIGINT, int_exit); |
198 | signal(SIGHUP, int_exit); |
199 | signal(SIGTERM, int_exit); |
200 | |
201 | if (json_output) |
202 | jsonw_start_array(self: json_wtr); |
203 | |
204 | while (!stop) { |
205 | err = perf_buffer__poll(pb, 200); |
206 | if (err < 0 && err != -EINTR) { |
207 | p_err(fmt: "perf buffer polling failed: %s (%d)" , |
208 | strerror(errno), errno); |
209 | goto err_close_pb; |
210 | } |
211 | } |
212 | |
213 | if (json_output) |
214 | jsonw_end_array(self: json_wtr); |
215 | |
216 | perf_buffer__free(pb); |
217 | close(map_fd); |
218 | |
219 | return 0; |
220 | |
221 | err_close_pb: |
222 | perf_buffer__free(pb); |
223 | err_close_map: |
224 | close(map_fd); |
225 | return -1; |
226 | } |
227 | |