1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * sampleip: sample instruction pointer and frequency count in a BPF map. |
4 | * |
5 | * Copyright 2016 Netflix, Inc. |
6 | */ |
7 | #include <stdio.h> |
8 | #include <stdlib.h> |
9 | #include <unistd.h> |
10 | #include <errno.h> |
11 | #include <signal.h> |
12 | #include <string.h> |
13 | #include <linux/perf_event.h> |
14 | #include <linux/ptrace.h> |
15 | #include <linux/bpf.h> |
16 | #include <bpf/bpf.h> |
17 | #include <bpf/libbpf.h> |
18 | #include "perf-sys.h" |
19 | #include "trace_helpers.h" |
20 | |
21 | #define DEFAULT_FREQ 99 |
22 | #define DEFAULT_SECS 5 |
23 | #define MAX_IPS 8192 |
24 | |
25 | static int map_fd; |
26 | static int nr_cpus; |
27 | static long _text_addr; |
28 | |
29 | static void usage(void) |
30 | { |
31 | printf(format: "USAGE: sampleip [-F freq] [duration]\n" ); |
32 | printf(format: " -F freq # sample frequency (Hertz), default 99\n" ); |
33 | printf(format: " duration # sampling duration (seconds), default 5\n" ); |
34 | } |
35 | |
36 | static int sampling_start(int freq, struct bpf_program *prog, |
37 | struct bpf_link *links[]) |
38 | { |
39 | int i, pmu_fd; |
40 | |
41 | struct perf_event_attr pe_sample_attr = { |
42 | .type = PERF_TYPE_SOFTWARE, |
43 | .freq = 1, |
44 | .sample_period = freq, |
45 | .config = PERF_COUNT_SW_CPU_CLOCK, |
46 | .inherit = 1, |
47 | }; |
48 | |
49 | for (i = 0; i < nr_cpus; i++) { |
50 | pmu_fd = sys_perf_event_open(&pe_sample_attr, -1 /* pid */, i, |
51 | -1 /* group_fd */, 0 /* flags */); |
52 | if (pmu_fd < 0) { |
53 | fprintf(stderr, format: "ERROR: Initializing perf sampling\n" ); |
54 | return 1; |
55 | } |
56 | links[i] = bpf_program__attach_perf_event(prog, pmu_fd); |
57 | if (libbpf_get_error(links[i])) { |
58 | fprintf(stderr, format: "ERROR: Attach perf event\n" ); |
59 | links[i] = NULL; |
60 | close(fd: pmu_fd); |
61 | return 1; |
62 | } |
63 | } |
64 | |
65 | return 0; |
66 | } |
67 | |
68 | static void sampling_end(struct bpf_link *links[]) |
69 | { |
70 | int i; |
71 | |
72 | for (i = 0; i < nr_cpus; i++) |
73 | bpf_link__destroy(links[i]); |
74 | } |
75 | |
76 | struct ipcount { |
77 | __u64 ip; |
78 | __u32 count; |
79 | }; |
80 | |
81 | /* used for sorting */ |
82 | struct ipcount counts[MAX_IPS]; |
83 | |
84 | static int count_cmp(const void *p1, const void *p2) |
85 | { |
86 | return ((struct ipcount *)p1)->count - ((struct ipcount *)p2)->count; |
87 | } |
88 | |
89 | static void print_ip_map(int fd) |
90 | { |
91 | struct ksym *sym; |
92 | __u64 key, next_key; |
93 | __u32 value; |
94 | int i, max; |
95 | |
96 | printf(format: "%-19s %-32s %s\n" , "ADDR" , "KSYM" , "COUNT" ); |
97 | |
98 | /* fetch IPs and counts */ |
99 | key = 0, i = 0; |
100 | while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { |
101 | bpf_map_lookup_elem(fd, &next_key, &value); |
102 | counts[i].ip = next_key; |
103 | counts[i++].count = value; |
104 | key = next_key; |
105 | } |
106 | max = i; |
107 | |
108 | /* sort and print */ |
109 | qsort(base: counts, nmemb: max, size: sizeof(struct ipcount), compar: count_cmp); |
110 | for (i = 0; i < max; i++) { |
111 | if (counts[i].ip > _text_addr) { |
112 | sym = ksym_search(counts[i].ip); |
113 | if (!sym) { |
114 | printf(format: "ksym not found. Is kallsyms loaded?\n" ); |
115 | continue; |
116 | } |
117 | |
118 | printf(format: "0x%-17llx %-32s %u\n" , counts[i].ip, sym->name, |
119 | counts[i].count); |
120 | } else { |
121 | printf(format: "0x%-17llx %-32s %u\n" , counts[i].ip, "(user)" , |
122 | counts[i].count); |
123 | } |
124 | } |
125 | |
126 | if (max == MAX_IPS) { |
127 | printf(format: "WARNING: IP hash was full (max %d entries); " , max); |
128 | printf(format: "may have dropped samples\n" ); |
129 | } |
130 | } |
131 | |
132 | static void int_exit(int sig) |
133 | { |
134 | printf(format: "\n" ); |
135 | print_ip_map(fd: map_fd); |
136 | exit(status: 0); |
137 | } |
138 | |
139 | int main(int argc, char **argv) |
140 | { |
141 | int opt, freq = DEFAULT_FREQ, secs = DEFAULT_SECS, error = 1; |
142 | struct bpf_object *obj = NULL; |
143 | struct bpf_program *prog; |
144 | struct bpf_link **links; |
145 | char filename[256]; |
146 | |
147 | /* process arguments */ |
148 | while ((opt = getopt(argc: argc, argv: argv, shortopts: "F:h" )) != -1) { |
149 | switch (opt) { |
150 | case 'F': |
151 | freq = atoi(nptr: optarg); |
152 | break; |
153 | case 'h': |
154 | default: |
155 | usage(); |
156 | return 0; |
157 | } |
158 | } |
159 | if (argc - optind == 1) |
160 | secs = atoi(nptr: argv[optind]); |
161 | if (freq == 0 || secs == 0) { |
162 | usage(); |
163 | return 1; |
164 | } |
165 | |
166 | /* initialize kernel symbol translation */ |
167 | if (load_kallsyms()) { |
168 | fprintf(stderr, format: "ERROR: loading /proc/kallsyms\n" ); |
169 | return 2; |
170 | } |
171 | |
172 | /* used to determine whether the address is kernel space */ |
173 | _text_addr = ksym_get_addr("_text" ); |
174 | if (!_text_addr) { |
175 | fprintf(stderr, format: "ERROR: no '_text' in /proc/kallsyms\n" ); |
176 | return 3; |
177 | } |
178 | |
179 | /* create perf FDs for each CPU */ |
180 | nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); |
181 | links = calloc(nmemb: nr_cpus, size: sizeof(struct bpf_link *)); |
182 | if (!links) { |
183 | fprintf(stderr, format: "ERROR: malloc of links\n" ); |
184 | goto cleanup; |
185 | } |
186 | |
187 | snprintf(s: filename, maxlen: sizeof(filename), format: "%s_kern.o" , argv[0]); |
188 | obj = bpf_object__open_file(filename, NULL); |
189 | if (libbpf_get_error(obj)) { |
190 | fprintf(stderr, format: "ERROR: opening BPF object file failed\n" ); |
191 | obj = NULL; |
192 | goto cleanup; |
193 | } |
194 | |
195 | prog = bpf_object__find_program_by_name(obj, "do_sample" ); |
196 | if (!prog) { |
197 | fprintf(stderr, format: "ERROR: finding a prog in obj file failed\n" ); |
198 | goto cleanup; |
199 | } |
200 | |
201 | /* load BPF program */ |
202 | if (bpf_object__load(obj)) { |
203 | fprintf(stderr, format: "ERROR: loading BPF object file failed\n" ); |
204 | goto cleanup; |
205 | } |
206 | |
207 | map_fd = bpf_object__find_map_fd_by_name(obj, "ip_map" ); |
208 | if (map_fd < 0) { |
209 | fprintf(stderr, format: "ERROR: finding a map in obj file failed\n" ); |
210 | goto cleanup; |
211 | } |
212 | |
213 | signal(SIGINT, handler: int_exit); |
214 | signal(SIGTERM, handler: int_exit); |
215 | |
216 | /* do sampling */ |
217 | printf(format: "Sampling at %d Hertz for %d seconds. Ctrl-C also ends.\n" , |
218 | freq, secs); |
219 | if (sampling_start(freq, prog, links) != 0) |
220 | goto cleanup; |
221 | |
222 | sleep(seconds: secs); |
223 | error = 0; |
224 | |
225 | cleanup: |
226 | sampling_end(links); |
227 | /* output sample counts */ |
228 | if (!error) |
229 | print_ip_map(fd: map_fd); |
230 | |
231 | free(ptr: links); |
232 | bpf_object__close(obj); |
233 | return error; |
234 | } |
235 | |