]>
Commit | Line | Data |
---|---|---|
1c6bec5b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
d01f4e8d NK |
2 | /* |
3 | * builtin-ftrace.c | |
4 | * | |
5 | * Copyright (c) 2013 LG Electronics, Namhyung Kim <namhyung@kernel.org> | |
d01f4e8d NK |
6 | */ |
7 | ||
8 | #include "builtin.h" | |
d01f4e8d | 9 | |
a43783ae | 10 | #include <errno.h> |
d01f4e8d NK |
11 | #include <unistd.h> |
12 | #include <signal.h> | |
f2a39fe8 | 13 | #include <stdlib.h> |
a9af6be5 | 14 | #include <fcntl.h> |
4208735d | 15 | #include <poll.h> |
c766f3df | 16 | #include <linux/capability.h> |
8520a98d | 17 | #include <linux/string.h> |
d01f4e8d NK |
18 | |
19 | #include "debug.h" | |
8520a98d | 20 | #include <subcmd/pager.h> |
d01f4e8d | 21 | #include <subcmd/parse-options.h> |
20a9ed28 | 22 | #include <api/fs/tracing_path.h> |
d01f4e8d NK |
23 | #include "evlist.h" |
24 | #include "target.h" | |
dc231032 | 25 | #include "cpumap.h" |
d01f4e8d | 26 | #include "thread_map.h" |
c766f3df | 27 | #include "util/cap.h" |
b05d1093 | 28 | #include "util/config.h" |
d01f4e8d | 29 | |
d01f4e8d NK |
30 | #define DEFAULT_TRACER "function_graph" |
31 | ||
32 | struct perf_ftrace { | |
63503dba | 33 | struct evlist *evlist; |
78b83e8b NK |
34 | struct target target; |
35 | const char *tracer; | |
36 | struct list_head filters; | |
37 | struct list_head notrace; | |
38 | struct list_head graph_funcs; | |
39 | struct list_head nograph_funcs; | |
1096c35a | 40 | int graph_depth; |
78b83e8b NK |
41 | }; |
42 | ||
43 | struct filter_entry { | |
44 | struct list_head list; | |
45 | char name[]; | |
d01f4e8d NK |
46 | }; |
47 | ||
48 | static bool done; | |
49 | ||
50 | static void sig_handler(int sig __maybe_unused) | |
51 | { | |
52 | done = true; | |
53 | } | |
54 | ||
55 | /* | |
56 | * perf_evlist__prepare_workload will send a SIGUSR1 if the fork fails, since | |
57 | * we asked by setting its exec_error to the function below, | |
58 | * ftrace__workload_exec_failed_signal. | |
59 | * | |
60 | * XXX We need to handle this more appropriately, emitting an error, etc. | |
61 | */ | |
62 | static void ftrace__workload_exec_failed_signal(int signo __maybe_unused, | |
63 | siginfo_t *info __maybe_unused, | |
64 | void *ucontext __maybe_unused) | |
65 | { | |
66 | /* workload_exec_errno = info->si_value.sival_int; */ | |
67 | done = true; | |
68 | } | |
69 | ||
a9af6be5 | 70 | static int __write_tracing_file(const char *name, const char *val, bool append) |
d01f4e8d NK |
71 | { |
72 | char *file; | |
73 | int fd, ret = -1; | |
74 | ssize_t size = strlen(val); | |
a9af6be5 | 75 | int flags = O_WRONLY; |
e7bd9ba2 | 76 | char errbuf[512]; |
63cd02d8 | 77 | char *val_copy; |
d01f4e8d NK |
78 | |
79 | file = get_tracing_file(name); | |
80 | if (!file) { | |
81 | pr_debug("cannot get tracing file: %s\n", name); | |
82 | return -1; | |
83 | } | |
84 | ||
a9af6be5 NK |
85 | if (append) |
86 | flags |= O_APPEND; | |
87 | else | |
88 | flags |= O_TRUNC; | |
89 | ||
90 | fd = open(file, flags); | |
d01f4e8d | 91 | if (fd < 0) { |
e7bd9ba2 NK |
92 | pr_debug("cannot open tracing file: %s: %s\n", |
93 | name, str_error_r(errno, errbuf, sizeof(errbuf))); | |
d01f4e8d NK |
94 | goto out; |
95 | } | |
96 | ||
63cd02d8 CD |
97 | /* |
98 | * Copy the original value and append a '\n'. Without this, | |
99 | * the kernel can hide possible errors. | |
100 | */ | |
101 | val_copy = strdup(val); | |
102 | if (!val_copy) | |
103 | goto out_close; | |
104 | val_copy[size] = '\n'; | |
105 | ||
106 | if (write(fd, val_copy, size + 1) == size + 1) | |
d01f4e8d NK |
107 | ret = 0; |
108 | else | |
e7bd9ba2 NK |
109 | pr_debug("write '%s' to tracing/%s failed: %s\n", |
110 | val, name, str_error_r(errno, errbuf, sizeof(errbuf))); | |
d01f4e8d | 111 | |
63cd02d8 CD |
112 | free(val_copy); |
113 | out_close: | |
d01f4e8d NK |
114 | close(fd); |
115 | out: | |
116 | put_tracing_file(file); | |
117 | return ret; | |
118 | } | |
119 | ||
a9af6be5 NK |
120 | static int write_tracing_file(const char *name, const char *val) |
121 | { | |
122 | return __write_tracing_file(name, val, false); | |
123 | } | |
124 | ||
125 | static int append_tracing_file(const char *name, const char *val) | |
126 | { | |
127 | return __write_tracing_file(name, val, true); | |
128 | } | |
129 | ||
dc231032 | 130 | static int reset_tracing_cpu(void); |
78b83e8b | 131 | static void reset_tracing_filters(void); |
dc231032 | 132 | |
d01f4e8d NK |
133 | static int reset_tracing_files(struct perf_ftrace *ftrace __maybe_unused) |
134 | { | |
135 | if (write_tracing_file("tracing_on", "0") < 0) | |
136 | return -1; | |
137 | ||
138 | if (write_tracing_file("current_tracer", "nop") < 0) | |
139 | return -1; | |
140 | ||
141 | if (write_tracing_file("set_ftrace_pid", " ") < 0) | |
142 | return -1; | |
143 | ||
dc231032 NK |
144 | if (reset_tracing_cpu() < 0) |
145 | return -1; | |
146 | ||
1096c35a NK |
147 | if (write_tracing_file("max_graph_depth", "0") < 0) |
148 | return -1; | |
149 | ||
78b83e8b | 150 | reset_tracing_filters(); |
d01f4e8d NK |
151 | return 0; |
152 | } | |
153 | ||
a9af6be5 NK |
154 | static int set_tracing_pid(struct perf_ftrace *ftrace) |
155 | { | |
156 | int i; | |
157 | char buf[16]; | |
158 | ||
159 | if (target__has_cpu(&ftrace->target)) | |
160 | return 0; | |
161 | ||
a2f354e3 | 162 | for (i = 0; i < perf_thread_map__nr(ftrace->evlist->core.threads); i++) { |
a9af6be5 | 163 | scnprintf(buf, sizeof(buf), "%d", |
03617c22 | 164 | ftrace->evlist->core.threads->map[i]); |
a9af6be5 NK |
165 | if (append_tracing_file("set_ftrace_pid", buf) < 0) |
166 | return -1; | |
167 | } | |
168 | return 0; | |
169 | } | |
170 | ||
f854839b | 171 | static int set_tracing_cpumask(struct perf_cpu_map *cpumap) |
dc231032 NK |
172 | { |
173 | char *cpumask; | |
174 | size_t mask_size; | |
175 | int ret; | |
176 | int last_cpu; | |
177 | ||
178 | last_cpu = cpu_map__cpu(cpumap, cpumap->nr - 1); | |
cf30ae72 | 179 | mask_size = last_cpu / 4 + 2; /* one more byte for EOS */ |
dc231032 NK |
180 | mask_size += last_cpu / 32; /* ',' is needed for every 32th cpus */ |
181 | ||
182 | cpumask = malloc(mask_size); | |
183 | if (cpumask == NULL) { | |
184 | pr_debug("failed to allocate cpu mask\n"); | |
185 | return -1; | |
186 | } | |
187 | ||
188 | cpu_map__snprint_mask(cpumap, cpumask, mask_size); | |
189 | ||
190 | ret = write_tracing_file("tracing_cpumask", cpumask); | |
191 | ||
192 | free(cpumask); | |
193 | return ret; | |
194 | } | |
195 | ||
196 | static int set_tracing_cpu(struct perf_ftrace *ftrace) | |
197 | { | |
f72f901d | 198 | struct perf_cpu_map *cpumap = ftrace->evlist->core.cpus; |
dc231032 NK |
199 | |
200 | if (!target__has_cpu(&ftrace->target)) | |
201 | return 0; | |
202 | ||
203 | return set_tracing_cpumask(cpumap); | |
204 | } | |
205 | ||
206 | static int reset_tracing_cpu(void) | |
207 | { | |
9c3516d1 | 208 | struct perf_cpu_map *cpumap = perf_cpu_map__new(NULL); |
dc231032 NK |
209 | int ret; |
210 | ||
211 | ret = set_tracing_cpumask(cpumap); | |
38f01d8d | 212 | perf_cpu_map__put(cpumap); |
dc231032 NK |
213 | return ret; |
214 | } | |
215 | ||
78b83e8b NK |
216 | static int __set_tracing_filter(const char *filter_file, struct list_head *funcs) |
217 | { | |
218 | struct filter_entry *pos; | |
219 | ||
220 | list_for_each_entry(pos, funcs, list) { | |
221 | if (append_tracing_file(filter_file, pos->name) < 0) | |
222 | return -1; | |
223 | } | |
224 | ||
225 | return 0; | |
226 | } | |
227 | ||
228 | static int set_tracing_filters(struct perf_ftrace *ftrace) | |
229 | { | |
230 | int ret; | |
231 | ||
232 | ret = __set_tracing_filter("set_ftrace_filter", &ftrace->filters); | |
233 | if (ret < 0) | |
234 | return ret; | |
235 | ||
236 | ret = __set_tracing_filter("set_ftrace_notrace", &ftrace->notrace); | |
237 | if (ret < 0) | |
238 | return ret; | |
239 | ||
240 | ret = __set_tracing_filter("set_graph_function", &ftrace->graph_funcs); | |
241 | if (ret < 0) | |
242 | return ret; | |
243 | ||
244 | /* old kernels do not have this filter */ | |
245 | __set_tracing_filter("set_graph_notrace", &ftrace->nograph_funcs); | |
246 | ||
247 | return ret; | |
248 | } | |
249 | ||
250 | static void reset_tracing_filters(void) | |
251 | { | |
252 | write_tracing_file("set_ftrace_filter", " "); | |
253 | write_tracing_file("set_ftrace_notrace", " "); | |
254 | write_tracing_file("set_graph_function", " "); | |
255 | write_tracing_file("set_graph_notrace", " "); | |
256 | } | |
257 | ||
1096c35a NK |
258 | static int set_tracing_depth(struct perf_ftrace *ftrace) |
259 | { | |
260 | char buf[16]; | |
261 | ||
262 | if (ftrace->graph_depth == 0) | |
263 | return 0; | |
264 | ||
265 | if (ftrace->graph_depth < 0) { | |
266 | pr_err("invalid graph depth: %d\n", ftrace->graph_depth); | |
267 | return -1; | |
268 | } | |
269 | ||
270 | snprintf(buf, sizeof(buf), "%d", ftrace->graph_depth); | |
271 | ||
272 | if (write_tracing_file("max_graph_depth", buf) < 0) | |
273 | return -1; | |
274 | ||
275 | return 0; | |
276 | } | |
277 | ||
d01f4e8d NK |
278 | static int __cmd_ftrace(struct perf_ftrace *ftrace, int argc, const char **argv) |
279 | { | |
280 | char *trace_file; | |
281 | int trace_fd; | |
d01f4e8d NK |
282 | char buf[4096]; |
283 | struct pollfd pollfd = { | |
284 | .events = POLLIN, | |
285 | }; | |
286 | ||
c766f3df | 287 | if (!perf_cap__capable(CAP_SYS_ADMIN)) { |
73e5de70 ACM |
288 | pr_err("ftrace only works for %s!\n", |
289 | #ifdef HAVE_LIBCAP_SUPPORT | |
290 | "users with the SYS_ADMIN capability" | |
291 | #else | |
292 | "root" | |
293 | #endif | |
294 | ); | |
d01f4e8d NK |
295 | return -1; |
296 | } | |
297 | ||
d01f4e8d NK |
298 | signal(SIGINT, sig_handler); |
299 | signal(SIGUSR1, sig_handler); | |
300 | signal(SIGCHLD, sig_handler); | |
58335964 | 301 | signal(SIGPIPE, sig_handler); |
d01f4e8d | 302 | |
63cd02d8 CD |
303 | if (reset_tracing_files(ftrace) < 0) { |
304 | pr_err("failed to reset ftrace\n"); | |
a9af6be5 | 305 | goto out; |
63cd02d8 | 306 | } |
d01f4e8d NK |
307 | |
308 | /* reset ftrace buffer */ | |
309 | if (write_tracing_file("trace", "0") < 0) | |
310 | goto out; | |
311 | ||
a9af6be5 NK |
312 | if (argc && perf_evlist__prepare_workload(ftrace->evlist, |
313 | &ftrace->target, argv, false, | |
314 | ftrace__workload_exec_failed_signal) < 0) { | |
d01f4e8d NK |
315 | goto out; |
316 | } | |
317 | ||
a9af6be5 NK |
318 | if (set_tracing_pid(ftrace) < 0) { |
319 | pr_err("failed to set ftrace pid\n"); | |
320 | goto out_reset; | |
d01f4e8d NK |
321 | } |
322 | ||
dc231032 NK |
323 | if (set_tracing_cpu(ftrace) < 0) { |
324 | pr_err("failed to set tracing cpumask\n"); | |
325 | goto out_reset; | |
326 | } | |
327 | ||
78b83e8b NK |
328 | if (set_tracing_filters(ftrace) < 0) { |
329 | pr_err("failed to set tracing filters\n"); | |
330 | goto out_reset; | |
331 | } | |
332 | ||
1096c35a NK |
333 | if (set_tracing_depth(ftrace) < 0) { |
334 | pr_err("failed to set graph depth\n"); | |
335 | goto out_reset; | |
336 | } | |
337 | ||
a9af6be5 NK |
338 | if (write_tracing_file("current_tracer", ftrace->tracer) < 0) { |
339 | pr_err("failed to set current_tracer to %s\n", ftrace->tracer); | |
340 | goto out_reset; | |
d01f4e8d NK |
341 | } |
342 | ||
29681bc5 NK |
343 | setup_pager(); |
344 | ||
d01f4e8d NK |
345 | trace_file = get_tracing_file("trace_pipe"); |
346 | if (!trace_file) { | |
347 | pr_err("failed to open trace_pipe\n"); | |
a9af6be5 | 348 | goto out_reset; |
d01f4e8d NK |
349 | } |
350 | ||
351 | trace_fd = open(trace_file, O_RDONLY); | |
352 | ||
353 | put_tracing_file(trace_file); | |
354 | ||
355 | if (trace_fd < 0) { | |
356 | pr_err("failed to open trace_pipe\n"); | |
a9af6be5 | 357 | goto out_reset; |
d01f4e8d NK |
358 | } |
359 | ||
360 | fcntl(trace_fd, F_SETFL, O_NONBLOCK); | |
361 | pollfd.fd = trace_fd; | |
362 | ||
363 | if (write_tracing_file("tracing_on", "1") < 0) { | |
364 | pr_err("can't enable tracing\n"); | |
365 | goto out_close_fd; | |
366 | } | |
367 | ||
368 | perf_evlist__start_workload(ftrace->evlist); | |
369 | ||
370 | while (!done) { | |
371 | if (poll(&pollfd, 1, -1) < 0) | |
372 | break; | |
373 | ||
374 | if (pollfd.revents & POLLIN) { | |
375 | int n = read(trace_fd, buf, sizeof(buf)); | |
376 | if (n < 0) | |
377 | break; | |
378 | if (fwrite(buf, n, 1, stdout) != 1) | |
379 | break; | |
380 | } | |
381 | } | |
382 | ||
383 | write_tracing_file("tracing_on", "0"); | |
384 | ||
385 | /* read remaining buffer contents */ | |
386 | while (true) { | |
387 | int n = read(trace_fd, buf, sizeof(buf)); | |
388 | if (n <= 0) | |
389 | break; | |
390 | if (fwrite(buf, n, 1, stdout) != 1) | |
391 | break; | |
392 | } | |
393 | ||
394 | out_close_fd: | |
395 | close(trace_fd); | |
a9af6be5 | 396 | out_reset: |
d01f4e8d | 397 | reset_tracing_files(ftrace); |
a9af6be5 | 398 | out: |
d01f4e8d NK |
399 | return done ? 0 : -1; |
400 | } | |
401 | ||
b05d1093 TS |
402 | static int perf_ftrace_config(const char *var, const char *value, void *cb) |
403 | { | |
404 | struct perf_ftrace *ftrace = cb; | |
405 | ||
8e99b6d4 | 406 | if (!strstarts(var, "ftrace.")) |
b05d1093 TS |
407 | return 0; |
408 | ||
409 | if (strcmp(var, "ftrace.tracer")) | |
410 | return -1; | |
411 | ||
412 | if (!strcmp(value, "function_graph") || | |
413 | !strcmp(value, "function")) { | |
414 | ftrace->tracer = value; | |
415 | return 0; | |
416 | } | |
417 | ||
418 | pr_err("Please select \"function_graph\" (default) or \"function\"\n"); | |
419 | return -1; | |
420 | } | |
421 | ||
78b83e8b NK |
422 | static int parse_filter_func(const struct option *opt, const char *str, |
423 | int unset __maybe_unused) | |
424 | { | |
425 | struct list_head *head = opt->value; | |
426 | struct filter_entry *entry; | |
427 | ||
428 | entry = malloc(sizeof(*entry) + strlen(str) + 1); | |
429 | if (entry == NULL) | |
430 | return -ENOMEM; | |
431 | ||
432 | strcpy(entry->name, str); | |
433 | list_add_tail(&entry->list, head); | |
434 | ||
435 | return 0; | |
436 | } | |
437 | ||
438 | static void delete_filter_func(struct list_head *head) | |
439 | { | |
440 | struct filter_entry *pos, *tmp; | |
441 | ||
442 | list_for_each_entry_safe(pos, tmp, head, list) { | |
e56fbc9d | 443 | list_del_init(&pos->list); |
78b83e8b NK |
444 | free(pos); |
445 | } | |
446 | } | |
447 | ||
b0ad8ea6 | 448 | int cmd_ftrace(int argc, const char **argv) |
d01f4e8d NK |
449 | { |
450 | int ret; | |
451 | struct perf_ftrace ftrace = { | |
bf062bd2 | 452 | .tracer = DEFAULT_TRACER, |
d01f4e8d NK |
453 | .target = { .uid = UINT_MAX, }, |
454 | }; | |
455 | const char * const ftrace_usage[] = { | |
a9af6be5 | 456 | "perf ftrace [<options>] [<command>]", |
d01f4e8d NK |
457 | "perf ftrace [<options>] -- <command> [<options>]", |
458 | NULL | |
459 | }; | |
460 | const struct option ftrace_options[] = { | |
461 | OPT_STRING('t', "tracer", &ftrace.tracer, "tracer", | |
ec347870 | 462 | "tracer to use: function_graph(default) or function"), |
a9af6be5 NK |
463 | OPT_STRING('p', "pid", &ftrace.target.pid, "pid", |
464 | "trace on existing process id"), | |
d01f4e8d NK |
465 | OPT_INCR('v', "verbose", &verbose, |
466 | "be more verbose"), | |
dc231032 NK |
467 | OPT_BOOLEAN('a', "all-cpus", &ftrace.target.system_wide, |
468 | "system-wide collection from all CPUs"), | |
469 | OPT_STRING('C', "cpu", &ftrace.target.cpu_list, "cpu", | |
470 | "list of cpus to monitor"), | |
78b83e8b NK |
471 | OPT_CALLBACK('T', "trace-funcs", &ftrace.filters, "func", |
472 | "trace given functions only", parse_filter_func), | |
473 | OPT_CALLBACK('N', "notrace-funcs", &ftrace.notrace, "func", | |
474 | "do not trace given functions", parse_filter_func), | |
475 | OPT_CALLBACK('G', "graph-funcs", &ftrace.graph_funcs, "func", | |
476 | "Set graph filter on given functions", parse_filter_func), | |
477 | OPT_CALLBACK('g', "nograph-funcs", &ftrace.nograph_funcs, "func", | |
478 | "Set nograph filter on given functions", parse_filter_func), | |
1096c35a NK |
479 | OPT_INTEGER('D', "graph-depth", &ftrace.graph_depth, |
480 | "Max depth for function graph tracer"), | |
d01f4e8d NK |
481 | OPT_END() |
482 | }; | |
483 | ||
78b83e8b NK |
484 | INIT_LIST_HEAD(&ftrace.filters); |
485 | INIT_LIST_HEAD(&ftrace.notrace); | |
486 | INIT_LIST_HEAD(&ftrace.graph_funcs); | |
487 | INIT_LIST_HEAD(&ftrace.nograph_funcs); | |
488 | ||
b05d1093 TS |
489 | ret = perf_config(perf_ftrace_config, &ftrace); |
490 | if (ret < 0) | |
491 | return -1; | |
492 | ||
d01f4e8d NK |
493 | argc = parse_options(argc, argv, ftrace_options, ftrace_usage, |
494 | PARSE_OPT_STOP_AT_NON_OPTION); | |
a9af6be5 | 495 | if (!argc && target__none(&ftrace.target)) |
d01f4e8d NK |
496 | usage_with_options(ftrace_usage, ftrace_options); |
497 | ||
a9af6be5 NK |
498 | ret = target__validate(&ftrace.target); |
499 | if (ret) { | |
500 | char errbuf[512]; | |
501 | ||
502 | target__strerror(&ftrace.target, ret, errbuf, 512); | |
503 | pr_err("%s\n", errbuf); | |
78b83e8b | 504 | goto out_delete_filters; |
a9af6be5 NK |
505 | } |
506 | ||
0f98b11c | 507 | ftrace.evlist = evlist__new(); |
78b83e8b NK |
508 | if (ftrace.evlist == NULL) { |
509 | ret = -ENOMEM; | |
510 | goto out_delete_filters; | |
511 | } | |
d01f4e8d NK |
512 | |
513 | ret = perf_evlist__create_maps(ftrace.evlist, &ftrace.target); | |
514 | if (ret < 0) | |
515 | goto out_delete_evlist; | |
516 | ||
d01f4e8d NK |
517 | ret = __cmd_ftrace(&ftrace, argc, argv); |
518 | ||
519 | out_delete_evlist: | |
c12995a5 | 520 | evlist__delete(ftrace.evlist); |
d01f4e8d | 521 | |
78b83e8b NK |
522 | out_delete_filters: |
523 | delete_filter_func(&ftrace.filters); | |
524 | delete_filter_func(&ftrace.notrace); | |
525 | delete_filter_func(&ftrace.graph_funcs); | |
526 | delete_filter_func(&ftrace.nograph_funcs); | |
527 | ||
d01f4e8d NK |
528 | return ret; |
529 | } |