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> | |
0094024a | 6 | * Copyright (c) 2020 Changbin Du <changbin.du@gmail.com>, significant enhancement. |
d01f4e8d NK |
7 | */ |
8 | ||
9 | #include "builtin.h" | |
d01f4e8d | 10 | |
a43783ae | 11 | #include <errno.h> |
d01f4e8d NK |
12 | #include <unistd.h> |
13 | #include <signal.h> | |
f2a39fe8 | 14 | #include <stdlib.h> |
a9af6be5 | 15 | #include <fcntl.h> |
53be5028 | 16 | #include <math.h> |
4208735d | 17 | #include <poll.h> |
53be5028 | 18 | #include <ctype.h> |
c766f3df | 19 | #include <linux/capability.h> |
8520a98d | 20 | #include <linux/string.h> |
d01f4e8d NK |
21 | |
22 | #include "debug.h" | |
8520a98d | 23 | #include <subcmd/pager.h> |
d01f4e8d | 24 | #include <subcmd/parse-options.h> |
20a9ed28 | 25 | #include <api/fs/tracing_path.h> |
d01f4e8d NK |
26 | #include "evlist.h" |
27 | #include "target.h" | |
dc231032 | 28 | #include "cpumap.h" |
d01f4e8d | 29 | #include "thread_map.h" |
2ae05fe0 | 30 | #include "strfilter.h" |
c766f3df | 31 | #include "util/cap.h" |
b05d1093 | 32 | #include "util/config.h" |
177f4eac | 33 | #include "util/ftrace.h" |
846e1939 | 34 | #include "util/units.h" |
b1d84af6 | 35 | #include "util/parse-sublevel-options.h" |
d01f4e8d | 36 | |
d01f4e8d NK |
37 | #define DEFAULT_TRACER "function_graph" |
38 | ||
51a09d8f | 39 | static volatile int workload_exec_errno; |
d01f4e8d NK |
40 | static bool done; |
41 | ||
42 | static void sig_handler(int sig __maybe_unused) | |
43 | { | |
44 | done = true; | |
45 | } | |
46 | ||
47 | /* | |
7b392ef0 | 48 | * evlist__prepare_workload will send a SIGUSR1 if the fork fails, since |
d01f4e8d NK |
49 | * we asked by setting its exec_error to the function below, |
50 | * ftrace__workload_exec_failed_signal. | |
51 | * | |
52 | * XXX We need to handle this more appropriately, emitting an error, etc. | |
53 | */ | |
54 | static void ftrace__workload_exec_failed_signal(int signo __maybe_unused, | |
55 | siginfo_t *info __maybe_unused, | |
56 | void *ucontext __maybe_unused) | |
57 | { | |
51a09d8f | 58 | workload_exec_errno = info->si_value.sival_int; |
d01f4e8d NK |
59 | done = true; |
60 | } | |
61 | ||
a9af6be5 | 62 | static int __write_tracing_file(const char *name, const char *val, bool append) |
d01f4e8d NK |
63 | { |
64 | char *file; | |
65 | int fd, ret = -1; | |
66 | ssize_t size = strlen(val); | |
a9af6be5 | 67 | int flags = O_WRONLY; |
e7bd9ba2 | 68 | char errbuf[512]; |
63cd02d8 | 69 | char *val_copy; |
d01f4e8d NK |
70 | |
71 | file = get_tracing_file(name); | |
72 | if (!file) { | |
73 | pr_debug("cannot get tracing file: %s\n", name); | |
74 | return -1; | |
75 | } | |
76 | ||
a9af6be5 NK |
77 | if (append) |
78 | flags |= O_APPEND; | |
79 | else | |
80 | flags |= O_TRUNC; | |
81 | ||
82 | fd = open(file, flags); | |
d01f4e8d | 83 | if (fd < 0) { |
e7bd9ba2 NK |
84 | pr_debug("cannot open tracing file: %s: %s\n", |
85 | name, str_error_r(errno, errbuf, sizeof(errbuf))); | |
d01f4e8d NK |
86 | goto out; |
87 | } | |
88 | ||
63cd02d8 CD |
89 | /* |
90 | * Copy the original value and append a '\n'. Without this, | |
91 | * the kernel can hide possible errors. | |
92 | */ | |
93 | val_copy = strdup(val); | |
94 | if (!val_copy) | |
95 | goto out_close; | |
96 | val_copy[size] = '\n'; | |
97 | ||
98 | if (write(fd, val_copy, size + 1) == size + 1) | |
d01f4e8d NK |
99 | ret = 0; |
100 | else | |
e7bd9ba2 NK |
101 | pr_debug("write '%s' to tracing/%s failed: %s\n", |
102 | val, name, str_error_r(errno, errbuf, sizeof(errbuf))); | |
d01f4e8d | 103 | |
63cd02d8 CD |
104 | free(val_copy); |
105 | out_close: | |
d01f4e8d NK |
106 | close(fd); |
107 | out: | |
108 | put_tracing_file(file); | |
109 | return ret; | |
110 | } | |
111 | ||
a9af6be5 NK |
112 | static int write_tracing_file(const char *name, const char *val) |
113 | { | |
114 | return __write_tracing_file(name, val, false); | |
115 | } | |
116 | ||
117 | static int append_tracing_file(const char *name, const char *val) | |
118 | { | |
119 | return __write_tracing_file(name, val, true); | |
120 | } | |
121 | ||
d6d81bfe CD |
122 | static int read_tracing_file_to_stdout(const char *name) |
123 | { | |
124 | char buf[4096]; | |
125 | char *file; | |
126 | int fd; | |
127 | int ret = -1; | |
128 | ||
129 | file = get_tracing_file(name); | |
130 | if (!file) { | |
131 | pr_debug("cannot get tracing file: %s\n", name); | |
132 | return -1; | |
133 | } | |
134 | ||
135 | fd = open(file, O_RDONLY); | |
136 | if (fd < 0) { | |
137 | pr_debug("cannot open tracing file: %s: %s\n", | |
138 | name, str_error_r(errno, buf, sizeof(buf))); | |
139 | goto out; | |
140 | } | |
141 | ||
142 | /* read contents to stdout */ | |
143 | while (true) { | |
144 | int n = read(fd, buf, sizeof(buf)); | |
145 | if (n == 0) | |
146 | break; | |
147 | else if (n < 0) | |
148 | goto out_close; | |
149 | ||
150 | if (fwrite(buf, n, 1, stdout) != 1) | |
151 | goto out_close; | |
152 | } | |
153 | ret = 0; | |
154 | ||
155 | out_close: | |
156 | close(fd); | |
157 | out: | |
158 | put_tracing_file(file); | |
159 | return ret; | |
160 | } | |
161 | ||
2ae05fe0 CD |
162 | static int read_tracing_file_by_line(const char *name, |
163 | void (*cb)(char *str, void *arg), | |
164 | void *cb_arg) | |
165 | { | |
166 | char *line = NULL; | |
167 | size_t len = 0; | |
168 | char *file; | |
169 | FILE *fp; | |
170 | ||
171 | file = get_tracing_file(name); | |
172 | if (!file) { | |
173 | pr_debug("cannot get tracing file: %s\n", name); | |
174 | return -1; | |
175 | } | |
176 | ||
177 | fp = fopen(file, "r"); | |
178 | if (fp == NULL) { | |
179 | pr_debug("cannot open tracing file: %s\n", name); | |
180 | put_tracing_file(file); | |
181 | return -1; | |
182 | } | |
183 | ||
184 | while (getline(&line, &len, fp) != -1) { | |
185 | cb(line, cb_arg); | |
186 | } | |
187 | ||
188 | if (line) | |
189 | free(line); | |
190 | ||
191 | fclose(fp); | |
192 | put_tracing_file(file); | |
193 | return 0; | |
194 | } | |
195 | ||
68faab0f CD |
196 | static int write_tracing_file_int(const char *name, int value) |
197 | { | |
198 | char buf[16]; | |
199 | ||
200 | snprintf(buf, sizeof(buf), "%d", value); | |
201 | if (write_tracing_file(name, buf) < 0) | |
202 | return -1; | |
203 | ||
204 | return 0; | |
205 | } | |
206 | ||
5b347472 CD |
207 | static int write_tracing_option_file(const char *name, const char *val) |
208 | { | |
209 | char *file; | |
210 | int ret; | |
211 | ||
212 | if (asprintf(&file, "options/%s", name) < 0) | |
213 | return -1; | |
214 | ||
215 | ret = __write_tracing_file(file, val, false); | |
216 | free(file); | |
217 | return ret; | |
218 | } | |
219 | ||
dc231032 | 220 | static int reset_tracing_cpu(void); |
78b83e8b | 221 | static void reset_tracing_filters(void); |
dc231032 | 222 | |
5b347472 CD |
223 | static void reset_tracing_options(struct perf_ftrace *ftrace __maybe_unused) |
224 | { | |
225 | write_tracing_option_file("function-fork", "0"); | |
b1d84af6 | 226 | write_tracing_option_file("func_stack_trace", "0"); |
38988f2e | 227 | write_tracing_option_file("sleep-time", "1"); |
d1bcf17c | 228 | write_tracing_option_file("funcgraph-irqs", "1"); |
59486fb0 CD |
229 | write_tracing_option_file("funcgraph-proc", "0"); |
230 | write_tracing_option_file("funcgraph-abstime", "0"); | |
231 | write_tracing_option_file("latency-format", "0"); | |
c81fc34e | 232 | write_tracing_option_file("irq-info", "0"); |
5b347472 CD |
233 | } |
234 | ||
d01f4e8d NK |
235 | static int reset_tracing_files(struct perf_ftrace *ftrace __maybe_unused) |
236 | { | |
237 | if (write_tracing_file("tracing_on", "0") < 0) | |
238 | return -1; | |
239 | ||
240 | if (write_tracing_file("current_tracer", "nop") < 0) | |
241 | return -1; | |
242 | ||
243 | if (write_tracing_file("set_ftrace_pid", " ") < 0) | |
244 | return -1; | |
245 | ||
dc231032 NK |
246 | if (reset_tracing_cpu() < 0) |
247 | return -1; | |
248 | ||
1096c35a NK |
249 | if (write_tracing_file("max_graph_depth", "0") < 0) |
250 | return -1; | |
251 | ||
00c85d5f CD |
252 | if (write_tracing_file("tracing_thresh", "0") < 0) |
253 | return -1; | |
254 | ||
78b83e8b | 255 | reset_tracing_filters(); |
5b347472 | 256 | reset_tracing_options(ftrace); |
d01f4e8d NK |
257 | return 0; |
258 | } | |
259 | ||
a9af6be5 NK |
260 | static int set_tracing_pid(struct perf_ftrace *ftrace) |
261 | { | |
262 | int i; | |
263 | char buf[16]; | |
264 | ||
265 | if (target__has_cpu(&ftrace->target)) | |
266 | return 0; | |
267 | ||
a2f354e3 | 268 | for (i = 0; i < perf_thread_map__nr(ftrace->evlist->core.threads); i++) { |
a9af6be5 | 269 | scnprintf(buf, sizeof(buf), "%d", |
671b60cb | 270 | perf_thread_map__pid(ftrace->evlist->core.threads, i)); |
a9af6be5 NK |
271 | if (append_tracing_file("set_ftrace_pid", buf) < 0) |
272 | return -1; | |
273 | } | |
274 | return 0; | |
275 | } | |
276 | ||
f854839b | 277 | static int set_tracing_cpumask(struct perf_cpu_map *cpumap) |
dc231032 NK |
278 | { |
279 | char *cpumask; | |
280 | size_t mask_size; | |
281 | int ret; | |
282 | int last_cpu; | |
283 | ||
44028699 | 284 | last_cpu = perf_cpu_map__cpu(cpumap, perf_cpu_map__nr(cpumap) - 1).cpu; |
cf30ae72 | 285 | mask_size = last_cpu / 4 + 2; /* one more byte for EOS */ |
dc231032 NK |
286 | mask_size += last_cpu / 32; /* ',' is needed for every 32th cpus */ |
287 | ||
288 | cpumask = malloc(mask_size); | |
289 | if (cpumask == NULL) { | |
290 | pr_debug("failed to allocate cpu mask\n"); | |
291 | return -1; | |
292 | } | |
293 | ||
294 | cpu_map__snprint_mask(cpumap, cpumask, mask_size); | |
295 | ||
296 | ret = write_tracing_file("tracing_cpumask", cpumask); | |
297 | ||
298 | free(cpumask); | |
299 | return ret; | |
300 | } | |
301 | ||
302 | static int set_tracing_cpu(struct perf_ftrace *ftrace) | |
303 | { | |
0df6ade7 | 304 | struct perf_cpu_map *cpumap = ftrace->evlist->core.user_requested_cpus; |
dc231032 NK |
305 | |
306 | if (!target__has_cpu(&ftrace->target)) | |
307 | return 0; | |
308 | ||
309 | return set_tracing_cpumask(cpumap); | |
310 | } | |
311 | ||
b1d84af6 CD |
312 | static int set_tracing_func_stack_trace(struct perf_ftrace *ftrace) |
313 | { | |
314 | if (!ftrace->func_stack_trace) | |
315 | return 0; | |
316 | ||
317 | if (write_tracing_option_file("func_stack_trace", "1") < 0) | |
318 | return -1; | |
319 | ||
320 | return 0; | |
321 | } | |
322 | ||
c81fc34e CD |
323 | static int set_tracing_func_irqinfo(struct perf_ftrace *ftrace) |
324 | { | |
325 | if (!ftrace->func_irq_info) | |
326 | return 0; | |
327 | ||
328 | if (write_tracing_option_file("irq-info", "1") < 0) | |
329 | return -1; | |
330 | ||
331 | return 0; | |
332 | } | |
333 | ||
dc231032 NK |
334 | static int reset_tracing_cpu(void) |
335 | { | |
9c3516d1 | 336 | struct perf_cpu_map *cpumap = perf_cpu_map__new(NULL); |
dc231032 NK |
337 | int ret; |
338 | ||
339 | ret = set_tracing_cpumask(cpumap); | |
38f01d8d | 340 | perf_cpu_map__put(cpumap); |
dc231032 NK |
341 | return ret; |
342 | } | |
343 | ||
78b83e8b NK |
344 | static int __set_tracing_filter(const char *filter_file, struct list_head *funcs) |
345 | { | |
346 | struct filter_entry *pos; | |
347 | ||
348 | list_for_each_entry(pos, funcs, list) { | |
349 | if (append_tracing_file(filter_file, pos->name) < 0) | |
350 | return -1; | |
351 | } | |
352 | ||
353 | return 0; | |
354 | } | |
355 | ||
356 | static int set_tracing_filters(struct perf_ftrace *ftrace) | |
357 | { | |
358 | int ret; | |
359 | ||
360 | ret = __set_tracing_filter("set_ftrace_filter", &ftrace->filters); | |
361 | if (ret < 0) | |
362 | return ret; | |
363 | ||
364 | ret = __set_tracing_filter("set_ftrace_notrace", &ftrace->notrace); | |
365 | if (ret < 0) | |
366 | return ret; | |
367 | ||
368 | ret = __set_tracing_filter("set_graph_function", &ftrace->graph_funcs); | |
369 | if (ret < 0) | |
370 | return ret; | |
371 | ||
372 | /* old kernels do not have this filter */ | |
373 | __set_tracing_filter("set_graph_notrace", &ftrace->nograph_funcs); | |
374 | ||
375 | return ret; | |
376 | } | |
377 | ||
378 | static void reset_tracing_filters(void) | |
379 | { | |
380 | write_tracing_file("set_ftrace_filter", " "); | |
381 | write_tracing_file("set_ftrace_notrace", " "); | |
382 | write_tracing_file("set_graph_function", " "); | |
383 | write_tracing_file("set_graph_notrace", " "); | |
384 | } | |
385 | ||
1096c35a NK |
386 | static int set_tracing_depth(struct perf_ftrace *ftrace) |
387 | { | |
1096c35a NK |
388 | if (ftrace->graph_depth == 0) |
389 | return 0; | |
390 | ||
391 | if (ftrace->graph_depth < 0) { | |
392 | pr_err("invalid graph depth: %d\n", ftrace->graph_depth); | |
393 | return -1; | |
394 | } | |
395 | ||
68faab0f | 396 | if (write_tracing_file_int("max_graph_depth", ftrace->graph_depth) < 0) |
1096c35a NK |
397 | return -1; |
398 | ||
399 | return 0; | |
400 | } | |
401 | ||
846e1939 CD |
402 | static int set_tracing_percpu_buffer_size(struct perf_ftrace *ftrace) |
403 | { | |
404 | int ret; | |
405 | ||
406 | if (ftrace->percpu_buffer_size == 0) | |
407 | return 0; | |
408 | ||
409 | ret = write_tracing_file_int("buffer_size_kb", | |
410 | ftrace->percpu_buffer_size / 1024); | |
411 | if (ret < 0) | |
412 | return ret; | |
413 | ||
414 | return 0; | |
415 | } | |
416 | ||
5b347472 CD |
417 | static int set_tracing_trace_inherit(struct perf_ftrace *ftrace) |
418 | { | |
419 | if (!ftrace->inherit) | |
420 | return 0; | |
421 | ||
422 | if (write_tracing_option_file("function-fork", "1") < 0) | |
423 | return -1; | |
424 | ||
425 | return 0; | |
426 | } | |
427 | ||
38988f2e CD |
428 | static int set_tracing_sleep_time(struct perf_ftrace *ftrace) |
429 | { | |
430 | if (!ftrace->graph_nosleep_time) | |
431 | return 0; | |
432 | ||
433 | if (write_tracing_option_file("sleep-time", "0") < 0) | |
434 | return -1; | |
435 | ||
436 | return 0; | |
437 | } | |
438 | ||
d1bcf17c CD |
439 | static int set_tracing_funcgraph_irqs(struct perf_ftrace *ftrace) |
440 | { | |
441 | if (!ftrace->graph_noirqs) | |
442 | return 0; | |
443 | ||
444 | if (write_tracing_option_file("funcgraph-irqs", "0") < 0) | |
445 | return -1; | |
446 | ||
447 | return 0; | |
448 | } | |
449 | ||
59486fb0 CD |
450 | static int set_tracing_funcgraph_verbose(struct perf_ftrace *ftrace) |
451 | { | |
452 | if (!ftrace->graph_verbose) | |
453 | return 0; | |
454 | ||
455 | if (write_tracing_option_file("funcgraph-proc", "1") < 0) | |
456 | return -1; | |
457 | ||
458 | if (write_tracing_option_file("funcgraph-abstime", "1") < 0) | |
459 | return -1; | |
460 | ||
461 | if (write_tracing_option_file("latency-format", "1") < 0) | |
462 | return -1; | |
463 | ||
464 | return 0; | |
465 | } | |
466 | ||
00c85d5f CD |
467 | static int set_tracing_thresh(struct perf_ftrace *ftrace) |
468 | { | |
469 | int ret; | |
470 | ||
471 | if (ftrace->graph_thresh == 0) | |
472 | return 0; | |
473 | ||
474 | ret = write_tracing_file_int("tracing_thresh", ftrace->graph_thresh); | |
475 | if (ret < 0) | |
476 | return ret; | |
477 | ||
478 | return 0; | |
479 | } | |
480 | ||
3c4dc21b | 481 | static int set_tracing_options(struct perf_ftrace *ftrace) |
d01f4e8d | 482 | { |
a9af6be5 NK |
483 | if (set_tracing_pid(ftrace) < 0) { |
484 | pr_err("failed to set ftrace pid\n"); | |
3c4dc21b | 485 | return -1; |
d01f4e8d NK |
486 | } |
487 | ||
dc231032 NK |
488 | if (set_tracing_cpu(ftrace) < 0) { |
489 | pr_err("failed to set tracing cpumask\n"); | |
3c4dc21b | 490 | return -1; |
dc231032 NK |
491 | } |
492 | ||
b1d84af6 CD |
493 | if (set_tracing_func_stack_trace(ftrace) < 0) { |
494 | pr_err("failed to set tracing option func_stack_trace\n"); | |
3c4dc21b | 495 | return -1; |
b1d84af6 CD |
496 | } |
497 | ||
c81fc34e CD |
498 | if (set_tracing_func_irqinfo(ftrace) < 0) { |
499 | pr_err("failed to set tracing option irq-info\n"); | |
3c4dc21b | 500 | return -1; |
c81fc34e CD |
501 | } |
502 | ||
78b83e8b NK |
503 | if (set_tracing_filters(ftrace) < 0) { |
504 | pr_err("failed to set tracing filters\n"); | |
3c4dc21b | 505 | return -1; |
78b83e8b NK |
506 | } |
507 | ||
1096c35a NK |
508 | if (set_tracing_depth(ftrace) < 0) { |
509 | pr_err("failed to set graph depth\n"); | |
3c4dc21b | 510 | return -1; |
1096c35a NK |
511 | } |
512 | ||
846e1939 CD |
513 | if (set_tracing_percpu_buffer_size(ftrace) < 0) { |
514 | pr_err("failed to set tracing per-cpu buffer size\n"); | |
3c4dc21b | 515 | return -1; |
846e1939 CD |
516 | } |
517 | ||
5b347472 CD |
518 | if (set_tracing_trace_inherit(ftrace) < 0) { |
519 | pr_err("failed to set tracing option function-fork\n"); | |
3c4dc21b | 520 | return -1; |
5b347472 CD |
521 | } |
522 | ||
38988f2e CD |
523 | if (set_tracing_sleep_time(ftrace) < 0) { |
524 | pr_err("failed to set tracing option sleep-time\n"); | |
3c4dc21b | 525 | return -1; |
38988f2e CD |
526 | } |
527 | ||
d1bcf17c CD |
528 | if (set_tracing_funcgraph_irqs(ftrace) < 0) { |
529 | pr_err("failed to set tracing option funcgraph-irqs\n"); | |
3c4dc21b | 530 | return -1; |
d1bcf17c CD |
531 | } |
532 | ||
59486fb0 CD |
533 | if (set_tracing_funcgraph_verbose(ftrace) < 0) { |
534 | pr_err("failed to set tracing option funcgraph-proc/funcgraph-abstime\n"); | |
3c4dc21b | 535 | return -1; |
59486fb0 CD |
536 | } |
537 | ||
00c85d5f CD |
538 | if (set_tracing_thresh(ftrace) < 0) { |
539 | pr_err("failed to set tracing thresh\n"); | |
3c4dc21b CD |
540 | return -1; |
541 | } | |
542 | ||
543 | return 0; | |
544 | } | |
545 | ||
a9b8ae8a NK |
546 | static void select_tracer(struct perf_ftrace *ftrace) |
547 | { | |
548 | bool graph = !list_empty(&ftrace->graph_funcs) || | |
549 | !list_empty(&ftrace->nograph_funcs); | |
550 | bool func = !list_empty(&ftrace->filters) || | |
551 | !list_empty(&ftrace->notrace); | |
552 | ||
553 | /* The function_graph has priority over function tracer. */ | |
554 | if (graph) | |
555 | ftrace->tracer = "function_graph"; | |
556 | else if (func) | |
557 | ftrace->tracer = "function"; | |
558 | /* Otherwise, the default tracer is used. */ | |
559 | ||
560 | pr_debug("%s tracer is used\n", ftrace->tracer); | |
561 | } | |
562 | ||
563 | static int __cmd_ftrace(struct perf_ftrace *ftrace) | |
3c4dc21b CD |
564 | { |
565 | char *trace_file; | |
566 | int trace_fd; | |
567 | char buf[4096]; | |
568 | struct pollfd pollfd = { | |
569 | .events = POLLIN, | |
570 | }; | |
571 | ||
572 | if (!(perf_cap__capable(CAP_PERFMON) || | |
573 | perf_cap__capable(CAP_SYS_ADMIN))) { | |
574 | pr_err("ftrace only works for %s!\n", | |
575 | #ifdef HAVE_LIBCAP_SUPPORT | |
576 | "users with the CAP_PERFMON or CAP_SYS_ADMIN capability" | |
577 | #else | |
578 | "root" | |
579 | #endif | |
580 | ); | |
581 | return -1; | |
00c85d5f CD |
582 | } |
583 | ||
a9b8ae8a | 584 | select_tracer(ftrace); |
3c4dc21b | 585 | |
3c4dc21b CD |
586 | if (reset_tracing_files(ftrace) < 0) { |
587 | pr_err("failed to reset ftrace\n"); | |
588 | goto out; | |
589 | } | |
590 | ||
591 | /* reset ftrace buffer */ | |
592 | if (write_tracing_file("trace", "0") < 0) | |
593 | goto out; | |
594 | ||
3c4dc21b CD |
595 | if (set_tracing_options(ftrace) < 0) |
596 | goto out_reset; | |
597 | ||
a9af6be5 NK |
598 | if (write_tracing_file("current_tracer", ftrace->tracer) < 0) { |
599 | pr_err("failed to set current_tracer to %s\n", ftrace->tracer); | |
600 | goto out_reset; | |
d01f4e8d NK |
601 | } |
602 | ||
29681bc5 NK |
603 | setup_pager(); |
604 | ||
d01f4e8d NK |
605 | trace_file = get_tracing_file("trace_pipe"); |
606 | if (!trace_file) { | |
607 | pr_err("failed to open trace_pipe\n"); | |
a9af6be5 | 608 | goto out_reset; |
d01f4e8d NK |
609 | } |
610 | ||
611 | trace_fd = open(trace_file, O_RDONLY); | |
612 | ||
613 | put_tracing_file(trace_file); | |
614 | ||
615 | if (trace_fd < 0) { | |
616 | pr_err("failed to open trace_pipe\n"); | |
a9af6be5 | 617 | goto out_reset; |
d01f4e8d NK |
618 | } |
619 | ||
620 | fcntl(trace_fd, F_SETFL, O_NONBLOCK); | |
621 | pollfd.fd = trace_fd; | |
622 | ||
81523c1e CD |
623 | /* display column headers */ |
624 | read_tracing_file_to_stdout("trace"); | |
625 | ||
6555c2f6 CD |
626 | if (!ftrace->initial_delay) { |
627 | if (write_tracing_file("tracing_on", "1") < 0) { | |
628 | pr_err("can't enable tracing\n"); | |
629 | goto out_close_fd; | |
630 | } | |
d01f4e8d NK |
631 | } |
632 | ||
7b392ef0 | 633 | evlist__start_workload(ftrace->evlist); |
d01f4e8d | 634 | |
6555c2f6 CD |
635 | if (ftrace->initial_delay) { |
636 | usleep(ftrace->initial_delay * 1000); | |
637 | if (write_tracing_file("tracing_on", "1") < 0) { | |
638 | pr_err("can't enable tracing\n"); | |
639 | goto out_close_fd; | |
640 | } | |
641 | } | |
642 | ||
d01f4e8d NK |
643 | while (!done) { |
644 | if (poll(&pollfd, 1, -1) < 0) | |
645 | break; | |
646 | ||
647 | if (pollfd.revents & POLLIN) { | |
648 | int n = read(trace_fd, buf, sizeof(buf)); | |
649 | if (n < 0) | |
650 | break; | |
651 | if (fwrite(buf, n, 1, stdout) != 1) | |
652 | break; | |
653 | } | |
654 | } | |
655 | ||
656 | write_tracing_file("tracing_on", "0"); | |
657 | ||
51a09d8f CD |
658 | if (workload_exec_errno) { |
659 | const char *emsg = str_error_r(workload_exec_errno, buf, sizeof(buf)); | |
660 | /* flush stdout first so below error msg appears at the end. */ | |
661 | fflush(stdout); | |
662 | pr_err("workload failed: %s\n", emsg); | |
663 | goto out_close_fd; | |
664 | } | |
665 | ||
d01f4e8d NK |
666 | /* read remaining buffer contents */ |
667 | while (true) { | |
668 | int n = read(trace_fd, buf, sizeof(buf)); | |
669 | if (n <= 0) | |
670 | break; | |
671 | if (fwrite(buf, n, 1, stdout) != 1) | |
672 | break; | |
673 | } | |
674 | ||
675 | out_close_fd: | |
676 | close(trace_fd); | |
a9af6be5 | 677 | out_reset: |
d01f4e8d | 678 | reset_tracing_files(ftrace); |
a9af6be5 | 679 | out: |
51a09d8f | 680 | return (done && !workload_exec_errno) ? 0 : -1; |
d01f4e8d NK |
681 | } |
682 | ||
84005bb6 NK |
683 | static void make_histogram(int buckets[], char *buf, size_t len, char *linebuf, |
684 | bool use_nsec) | |
53be5028 NK |
685 | { |
686 | char *p, *q; | |
687 | char *unit; | |
688 | double num; | |
689 | int i; | |
690 | ||
691 | /* ensure NUL termination */ | |
692 | buf[len] = '\0'; | |
693 | ||
694 | /* handle data line by line */ | |
695 | for (p = buf; (q = strchr(p, '\n')) != NULL; p = q + 1) { | |
696 | *q = '\0'; | |
697 | /* move it to the line buffer */ | |
698 | strcat(linebuf, p); | |
699 | ||
700 | /* | |
701 | * parse trace output to get function duration like in | |
702 | * | |
703 | * # tracer: function_graph | |
704 | * # | |
705 | * # CPU DURATION FUNCTION CALLS | |
706 | * # | | | | | | | | |
707 | * 1) + 10.291 us | do_filp_open(); | |
708 | * 1) 4.889 us | do_filp_open(); | |
709 | * 1) 6.086 us | do_filp_open(); | |
710 | * | |
711 | */ | |
712 | if (linebuf[0] == '#') | |
713 | goto next; | |
714 | ||
715 | /* ignore CPU */ | |
716 | p = strchr(linebuf, ')'); | |
717 | if (p == NULL) | |
718 | p = linebuf; | |
719 | ||
720 | while (*p && !isdigit(*p) && (*p != '|')) | |
721 | p++; | |
722 | ||
723 | /* no duration */ | |
724 | if (*p == '\0' || *p == '|') | |
725 | goto next; | |
726 | ||
727 | num = strtod(p, &unit); | |
728 | if (!unit || strncmp(unit, " us", 3)) | |
729 | goto next; | |
730 | ||
84005bb6 NK |
731 | if (use_nsec) |
732 | num *= 1000; | |
733 | ||
53be5028 NK |
734 | i = log2(num); |
735 | if (i < 0) | |
736 | i = 0; | |
737 | if (i >= NUM_BUCKET) | |
738 | i = NUM_BUCKET - 1; | |
739 | ||
740 | buckets[i]++; | |
741 | ||
742 | next: | |
743 | /* empty the line buffer for the next output */ | |
744 | linebuf[0] = '\0'; | |
745 | } | |
746 | ||
747 | /* preserve any remaining output (before newline) */ | |
748 | strcat(linebuf, p); | |
749 | } | |
750 | ||
84005bb6 | 751 | static void display_histogram(int buckets[], bool use_nsec) |
53be5028 NK |
752 | { |
753 | int i; | |
754 | int total = 0; | |
755 | int bar_total = 46; /* to fit in 80 column */ | |
756 | char bar[] = "###############################################"; | |
757 | int bar_len; | |
758 | ||
759 | for (i = 0; i < NUM_BUCKET; i++) | |
760 | total += buckets[i]; | |
761 | ||
762 | if (total == 0) { | |
763 | printf("No data found\n"); | |
764 | return; | |
765 | } | |
766 | ||
767 | printf("# %14s | %10s | %-*s |\n", | |
768 | " DURATION ", "COUNT", bar_total, "GRAPH"); | |
769 | ||
770 | bar_len = buckets[0] * bar_total / total; | |
771 | printf(" %4d - %-4d %s | %10d | %.*s%*s |\n", | |
772 | 0, 1, "us", buckets[0], bar_len, bar, bar_total - bar_len, ""); | |
773 | ||
774 | for (i = 1; i < NUM_BUCKET - 1; i++) { | |
775 | int start = (1 << (i - 1)); | |
776 | int stop = 1 << i; | |
84005bb6 | 777 | const char *unit = use_nsec ? "ns" : "us"; |
53be5028 NK |
778 | |
779 | if (start >= 1024) { | |
780 | start >>= 10; | |
781 | stop >>= 10; | |
84005bb6 | 782 | unit = use_nsec ? "us" : "ms"; |
53be5028 NK |
783 | } |
784 | bar_len = buckets[i] * bar_total / total; | |
785 | printf(" %4d - %-4d %s | %10d | %.*s%*s |\n", | |
786 | start, stop, unit, buckets[i], bar_len, bar, | |
787 | bar_total - bar_len, ""); | |
788 | } | |
789 | ||
790 | bar_len = buckets[NUM_BUCKET - 1] * bar_total / total; | |
791 | printf(" %4d - %-4s %s | %10d | %.*s%*s |\n", | |
84005bb6 NK |
792 | 1, "...", use_nsec ? "ms" : " s", buckets[NUM_BUCKET - 1], |
793 | bar_len, bar, bar_total - bar_len, ""); | |
53be5028 NK |
794 | |
795 | } | |
796 | ||
177f4eac | 797 | static int prepare_func_latency(struct perf_ftrace *ftrace) |
53be5028 NK |
798 | { |
799 | char *trace_file; | |
177f4eac | 800 | int fd; |
53be5028 | 801 | |
177f4eac NK |
802 | if (ftrace->target.use_bpf) |
803 | return perf_ftrace__latency_prepare_bpf(ftrace); | |
53be5028 NK |
804 | |
805 | if (reset_tracing_files(ftrace) < 0) { | |
806 | pr_err("failed to reset ftrace\n"); | |
177f4eac | 807 | return -1; |
53be5028 NK |
808 | } |
809 | ||
810 | /* reset ftrace buffer */ | |
811 | if (write_tracing_file("trace", "0") < 0) | |
177f4eac | 812 | return -1; |
53be5028 NK |
813 | |
814 | if (set_tracing_options(ftrace) < 0) | |
177f4eac | 815 | return -1; |
53be5028 NK |
816 | |
817 | /* force to use the function_graph tracer to track duration */ | |
818 | if (write_tracing_file("current_tracer", "function_graph") < 0) { | |
819 | pr_err("failed to set current_tracer to function_graph\n"); | |
177f4eac | 820 | return -1; |
53be5028 NK |
821 | } |
822 | ||
823 | trace_file = get_tracing_file("trace_pipe"); | |
824 | if (!trace_file) { | |
825 | pr_err("failed to open trace_pipe\n"); | |
177f4eac | 826 | return -1; |
53be5028 NK |
827 | } |
828 | ||
177f4eac NK |
829 | fd = open(trace_file, O_RDONLY); |
830 | if (fd < 0) | |
831 | pr_err("failed to open trace_pipe\n"); | |
53be5028 NK |
832 | |
833 | put_tracing_file(trace_file); | |
177f4eac NK |
834 | return fd; |
835 | } | |
53be5028 | 836 | |
177f4eac NK |
837 | static int start_func_latency(struct perf_ftrace *ftrace) |
838 | { | |
839 | if (ftrace->target.use_bpf) | |
840 | return perf_ftrace__latency_start_bpf(ftrace); | |
841 | ||
842 | if (write_tracing_file("tracing_on", "1") < 0) { | |
843 | pr_err("can't enable tracing\n"); | |
844 | return -1; | |
53be5028 NK |
845 | } |
846 | ||
177f4eac NK |
847 | return 0; |
848 | } | |
849 | ||
850 | static int stop_func_latency(struct perf_ftrace *ftrace) | |
851 | { | |
852 | if (ftrace->target.use_bpf) | |
853 | return perf_ftrace__latency_stop_bpf(ftrace); | |
854 | ||
855 | write_tracing_file("tracing_on", "0"); | |
856 | return 0; | |
857 | } | |
858 | ||
859 | static int read_func_latency(struct perf_ftrace *ftrace, int buckets[]) | |
860 | { | |
861 | if (ftrace->target.use_bpf) | |
862 | return perf_ftrace__latency_read_bpf(ftrace, buckets); | |
863 | ||
864 | return 0; | |
865 | } | |
866 | ||
867 | static int cleanup_func_latency(struct perf_ftrace *ftrace) | |
868 | { | |
869 | if (ftrace->target.use_bpf) | |
870 | return perf_ftrace__latency_cleanup_bpf(ftrace); | |
871 | ||
872 | reset_tracing_files(ftrace); | |
873 | return 0; | |
874 | } | |
875 | ||
876 | static int __cmd_latency(struct perf_ftrace *ftrace) | |
877 | { | |
878 | int trace_fd; | |
879 | char buf[4096]; | |
880 | char line[256]; | |
881 | struct pollfd pollfd = { | |
882 | .events = POLLIN, | |
883 | }; | |
884 | int buckets[NUM_BUCKET] = { }; | |
885 | ||
886 | if (!(perf_cap__capable(CAP_PERFMON) || | |
887 | perf_cap__capable(CAP_SYS_ADMIN))) { | |
888 | pr_err("ftrace only works for %s!\n", | |
889 | #ifdef HAVE_LIBCAP_SUPPORT | |
890 | "users with the CAP_PERFMON or CAP_SYS_ADMIN capability" | |
891 | #else | |
892 | "root" | |
893 | #endif | |
894 | ); | |
895 | return -1; | |
896 | } | |
897 | ||
898 | trace_fd = prepare_func_latency(ftrace); | |
899 | if (trace_fd < 0) | |
900 | goto out; | |
901 | ||
53be5028 NK |
902 | fcntl(trace_fd, F_SETFL, O_NONBLOCK); |
903 | pollfd.fd = trace_fd; | |
904 | ||
177f4eac NK |
905 | if (start_func_latency(ftrace) < 0) |
906 | goto out; | |
53be5028 NK |
907 | |
908 | evlist__start_workload(ftrace->evlist); | |
909 | ||
910 | line[0] = '\0'; | |
911 | while (!done) { | |
912 | if (poll(&pollfd, 1, -1) < 0) | |
913 | break; | |
914 | ||
915 | if (pollfd.revents & POLLIN) { | |
916 | int n = read(trace_fd, buf, sizeof(buf) - 1); | |
917 | if (n < 0) | |
918 | break; | |
919 | ||
84005bb6 | 920 | make_histogram(buckets, buf, n, line, ftrace->use_nsec); |
53be5028 NK |
921 | } |
922 | } | |
923 | ||
177f4eac | 924 | stop_func_latency(ftrace); |
53be5028 NK |
925 | |
926 | if (workload_exec_errno) { | |
927 | const char *emsg = str_error_r(workload_exec_errno, buf, sizeof(buf)); | |
928 | pr_err("workload failed: %s\n", emsg); | |
177f4eac | 929 | goto out; |
53be5028 NK |
930 | } |
931 | ||
932 | /* read remaining buffer contents */ | |
177f4eac | 933 | while (!ftrace->target.use_bpf) { |
53be5028 NK |
934 | int n = read(trace_fd, buf, sizeof(buf) - 1); |
935 | if (n <= 0) | |
936 | break; | |
84005bb6 | 937 | make_histogram(buckets, buf, n, line, ftrace->use_nsec); |
53be5028 NK |
938 | } |
939 | ||
177f4eac NK |
940 | read_func_latency(ftrace, buckets); |
941 | ||
84005bb6 | 942 | display_histogram(buckets, ftrace->use_nsec); |
53be5028 | 943 | |
53be5028 | 944 | out: |
177f4eac NK |
945 | close(trace_fd); |
946 | cleanup_func_latency(ftrace); | |
947 | ||
53be5028 NK |
948 | return (done && !workload_exec_errno) ? 0 : -1; |
949 | } | |
950 | ||
b05d1093 TS |
951 | static int perf_ftrace_config(const char *var, const char *value, void *cb) |
952 | { | |
953 | struct perf_ftrace *ftrace = cb; | |
954 | ||
8e99b6d4 | 955 | if (!strstarts(var, "ftrace.")) |
b05d1093 TS |
956 | return 0; |
957 | ||
958 | if (strcmp(var, "ftrace.tracer")) | |
959 | return -1; | |
960 | ||
961 | if (!strcmp(value, "function_graph") || | |
962 | !strcmp(value, "function")) { | |
963 | ftrace->tracer = value; | |
964 | return 0; | |
965 | } | |
966 | ||
967 | pr_err("Please select \"function_graph\" (default) or \"function\"\n"); | |
968 | return -1; | |
969 | } | |
970 | ||
2ae05fe0 CD |
971 | static void list_function_cb(char *str, void *arg) |
972 | { | |
973 | struct strfilter *filter = (struct strfilter *)arg; | |
974 | ||
975 | if (strfilter__compare(filter, str)) | |
976 | printf("%s", str); | |
977 | } | |
978 | ||
979 | static int opt_list_avail_functions(const struct option *opt __maybe_unused, | |
980 | const char *str, int unset) | |
981 | { | |
982 | struct strfilter *filter; | |
983 | const char *err = NULL; | |
984 | int ret; | |
985 | ||
986 | if (unset || !str) | |
987 | return -1; | |
988 | ||
989 | filter = strfilter__new(str, &err); | |
990 | if (!filter) | |
991 | return err ? -EINVAL : -ENOMEM; | |
992 | ||
993 | ret = strfilter__or(filter, str, &err); | |
994 | if (ret == -EINVAL) { | |
995 | pr_err("Filter parse error at %td.\n", err - str + 1); | |
996 | pr_err("Source: \"%s\"\n", str); | |
997 | pr_err(" %*c\n", (int)(err - str + 1), '^'); | |
998 | strfilter__delete(filter); | |
999 | return ret; | |
1000 | } | |
1001 | ||
1002 | ret = read_tracing_file_by_line("available_filter_functions", | |
1003 | list_function_cb, filter); | |
1004 | strfilter__delete(filter); | |
1005 | if (ret < 0) | |
1006 | return ret; | |
1007 | ||
1008 | exit(0); | |
1009 | } | |
1010 | ||
78b83e8b NK |
1011 | static int parse_filter_func(const struct option *opt, const char *str, |
1012 | int unset __maybe_unused) | |
1013 | { | |
1014 | struct list_head *head = opt->value; | |
1015 | struct filter_entry *entry; | |
1016 | ||
1017 | entry = malloc(sizeof(*entry) + strlen(str) + 1); | |
1018 | if (entry == NULL) | |
1019 | return -ENOMEM; | |
1020 | ||
1021 | strcpy(entry->name, str); | |
1022 | list_add_tail(&entry->list, head); | |
1023 | ||
1024 | return 0; | |
1025 | } | |
1026 | ||
1027 | static void delete_filter_func(struct list_head *head) | |
1028 | { | |
1029 | struct filter_entry *pos, *tmp; | |
1030 | ||
1031 | list_for_each_entry_safe(pos, tmp, head, list) { | |
e56fbc9d | 1032 | list_del_init(&pos->list); |
78b83e8b NK |
1033 | free(pos); |
1034 | } | |
1035 | } | |
1036 | ||
846e1939 CD |
1037 | static int parse_buffer_size(const struct option *opt, |
1038 | const char *str, int unset) | |
1039 | { | |
1040 | unsigned long *s = (unsigned long *)opt->value; | |
1041 | static struct parse_tag tags_size[] = { | |
1042 | { .tag = 'B', .mult = 1 }, | |
1043 | { .tag = 'K', .mult = 1 << 10 }, | |
1044 | { .tag = 'M', .mult = 1 << 20 }, | |
1045 | { .tag = 'G', .mult = 1 << 30 }, | |
1046 | { .tag = 0 }, | |
1047 | }; | |
1048 | unsigned long val; | |
1049 | ||
1050 | if (unset) { | |
1051 | *s = 0; | |
1052 | return 0; | |
1053 | } | |
1054 | ||
1055 | val = parse_tag_value(str, tags_size); | |
1056 | if (val != (unsigned long) -1) { | |
1057 | if (val < 1024) { | |
1058 | pr_err("buffer size too small, must larger than 1KB."); | |
1059 | return -1; | |
1060 | } | |
1061 | *s = val; | |
1062 | return 0; | |
1063 | } | |
1064 | ||
1065 | return -1; | |
1066 | } | |
1067 | ||
b1d84af6 CD |
1068 | static int parse_func_tracer_opts(const struct option *opt, |
1069 | const char *str, int unset) | |
1070 | { | |
1071 | int ret; | |
1072 | struct perf_ftrace *ftrace = (struct perf_ftrace *) opt->value; | |
1073 | struct sublevel_option func_tracer_opts[] = { | |
1074 | { .name = "call-graph", .value_ptr = &ftrace->func_stack_trace }, | |
c81fc34e | 1075 | { .name = "irq-info", .value_ptr = &ftrace->func_irq_info }, |
b1d84af6 CD |
1076 | { .name = NULL, } |
1077 | }; | |
1078 | ||
1079 | if (unset) | |
1080 | return 0; | |
1081 | ||
1082 | ret = perf_parse_sublevel_options(str, func_tracer_opts); | |
1083 | if (ret) | |
1084 | return ret; | |
1085 | ||
1086 | return 0; | |
1087 | } | |
1088 | ||
38988f2e CD |
1089 | static int parse_graph_tracer_opts(const struct option *opt, |
1090 | const char *str, int unset) | |
1091 | { | |
1092 | int ret; | |
1093 | struct perf_ftrace *ftrace = (struct perf_ftrace *) opt->value; | |
1094 | struct sublevel_option graph_tracer_opts[] = { | |
1095 | { .name = "nosleep-time", .value_ptr = &ftrace->graph_nosleep_time }, | |
d1bcf17c | 1096 | { .name = "noirqs", .value_ptr = &ftrace->graph_noirqs }, |
59486fb0 | 1097 | { .name = "verbose", .value_ptr = &ftrace->graph_verbose }, |
00c85d5f | 1098 | { .name = "thresh", .value_ptr = &ftrace->graph_thresh }, |
a8f87a5c | 1099 | { .name = "depth", .value_ptr = &ftrace->graph_depth }, |
38988f2e CD |
1100 | { .name = NULL, } |
1101 | }; | |
1102 | ||
1103 | if (unset) | |
1104 | return 0; | |
1105 | ||
1106 | ret = perf_parse_sublevel_options(str, graph_tracer_opts); | |
1107 | if (ret) | |
1108 | return ret; | |
1109 | ||
1110 | return 0; | |
1111 | } | |
1112 | ||
53be5028 NK |
1113 | enum perf_ftrace_subcommand { |
1114 | PERF_FTRACE_NONE, | |
1115 | PERF_FTRACE_TRACE, | |
1116 | PERF_FTRACE_LATENCY, | |
1117 | }; | |
1118 | ||
b0ad8ea6 | 1119 | int cmd_ftrace(int argc, const char **argv) |
d01f4e8d NK |
1120 | { |
1121 | int ret; | |
fceb6212 | 1122 | int (*cmd_func)(struct perf_ftrace *) = NULL; |
d01f4e8d | 1123 | struct perf_ftrace ftrace = { |
bf062bd2 | 1124 | .tracer = DEFAULT_TRACER, |
d01f4e8d NK |
1125 | .target = { .uid = UINT_MAX, }, |
1126 | }; | |
416e15ad | 1127 | const struct option common_options[] = { |
a9af6be5 | 1128 | OPT_STRING('p', "pid", &ftrace.target.pid, "pid", |
492e4edb | 1129 | "Trace on existing process id"), |
42145d71 CD |
1130 | /* TODO: Add short option -t after -t/--tracer can be removed. */ |
1131 | OPT_STRING(0, "tid", &ftrace.target.tid, "tid", | |
492e4edb | 1132 | "Trace on existing thread id (exclusive to --pid)"), |
d01f4e8d | 1133 | OPT_INCR('v', "verbose", &verbose, |
492e4edb | 1134 | "Be more verbose"), |
dc231032 | 1135 | OPT_BOOLEAN('a', "all-cpus", &ftrace.target.system_wide, |
492e4edb | 1136 | "System-wide collection from all CPUs"), |
dc231032 | 1137 | OPT_STRING('C', "cpu", &ftrace.target.cpu_list, "cpu", |
492e4edb | 1138 | "List of cpus to monitor"), |
416e15ad NK |
1139 | OPT_END() |
1140 | }; | |
1141 | const struct option ftrace_options[] = { | |
1142 | OPT_STRING('t', "tracer", &ftrace.tracer, "tracer", | |
1143 | "Tracer to use: function_graph(default) or function"), | |
1144 | OPT_CALLBACK_DEFAULT('F', "funcs", NULL, "[FILTER]", | |
1145 | "Show available functions to filter", | |
1146 | opt_list_avail_functions, "*"), | |
78b83e8b | 1147 | OPT_CALLBACK('T', "trace-funcs", &ftrace.filters, "func", |
492e4edb | 1148 | "Trace given functions using function tracer", |
eb6d31ae | 1149 | parse_filter_func), |
78b83e8b | 1150 | OPT_CALLBACK('N', "notrace-funcs", &ftrace.notrace, "func", |
492e4edb | 1151 | "Do not trace given functions", parse_filter_func), |
b1d84af6 | 1152 | OPT_CALLBACK(0, "func-opts", &ftrace, "options", |
492e4edb | 1153 | "Function tracer options, available options: call-graph,irq-info", |
b1d84af6 | 1154 | parse_func_tracer_opts), |
78b83e8b | 1155 | OPT_CALLBACK('G', "graph-funcs", &ftrace.graph_funcs, "func", |
492e4edb | 1156 | "Trace given functions using function_graph tracer", |
eb6d31ae | 1157 | parse_filter_func), |
78b83e8b NK |
1158 | OPT_CALLBACK('g', "nograph-funcs", &ftrace.nograph_funcs, "func", |
1159 | "Set nograph filter on given functions", parse_filter_func), | |
38988f2e | 1160 | OPT_CALLBACK(0, "graph-opts", &ftrace, "options", |
492e4edb | 1161 | "Graph tracer options, available options: nosleep-time,noirqs,verbose,thresh=<n>,depth=<n>", |
38988f2e | 1162 | parse_graph_tracer_opts), |
846e1939 | 1163 | OPT_CALLBACK('m', "buffer-size", &ftrace.percpu_buffer_size, "size", |
492e4edb | 1164 | "Size of per cpu buffer, needs to use a B, K, M or G suffix.", parse_buffer_size), |
5b347472 | 1165 | OPT_BOOLEAN(0, "inherit", &ftrace.inherit, |
492e4edb | 1166 | "Trace children processes"), |
6555c2f6 | 1167 | OPT_UINTEGER('D', "delay", &ftrace.initial_delay, |
492e4edb | 1168 | "Number of milliseconds to wait before starting tracing after program start"), |
416e15ad NK |
1169 | OPT_PARENT(common_options), |
1170 | }; | |
53be5028 NK |
1171 | const struct option latency_options[] = { |
1172 | OPT_CALLBACK('T', "trace-funcs", &ftrace.filters, "func", | |
1173 | "Show latency of given function", parse_filter_func), | |
177f4eac NK |
1174 | #ifdef HAVE_BPF_SKEL |
1175 | OPT_BOOLEAN('b', "use-bpf", &ftrace.target.use_bpf, | |
1176 | "Use BPF to measure function latency"), | |
1177 | #endif | |
84005bb6 NK |
1178 | OPT_BOOLEAN('n', "--use-nsec", &ftrace.use_nsec, |
1179 | "Use nano-second histogram"), | |
53be5028 NK |
1180 | OPT_PARENT(common_options), |
1181 | }; | |
1182 | const struct option *options = ftrace_options; | |
416e15ad NK |
1183 | |
1184 | const char * const ftrace_usage[] = { | |
1185 | "perf ftrace [<options>] [<command>]", | |
1186 | "perf ftrace [<options>] -- [<command>] [<options>]", | |
53be5028 NK |
1187 | "perf ftrace {trace|latency} [<options>] [<command>]", |
1188 | "perf ftrace {trace|latency} [<options>] -- [<command>] [<options>]", | |
416e15ad | 1189 | NULL |
d01f4e8d | 1190 | }; |
53be5028 | 1191 | enum perf_ftrace_subcommand subcmd = PERF_FTRACE_NONE; |
d01f4e8d | 1192 | |
78b83e8b NK |
1193 | INIT_LIST_HEAD(&ftrace.filters); |
1194 | INIT_LIST_HEAD(&ftrace.notrace); | |
1195 | INIT_LIST_HEAD(&ftrace.graph_funcs); | |
1196 | INIT_LIST_HEAD(&ftrace.nograph_funcs); | |
1197 | ||
a9b8ae8a NK |
1198 | signal(SIGINT, sig_handler); |
1199 | signal(SIGUSR1, sig_handler); | |
1200 | signal(SIGCHLD, sig_handler); | |
1201 | signal(SIGPIPE, sig_handler); | |
1202 | ||
b05d1093 TS |
1203 | ret = perf_config(perf_ftrace_config, &ftrace); |
1204 | if (ret < 0) | |
1205 | return -1; | |
1206 | ||
53be5028 NK |
1207 | if (argc > 1) { |
1208 | if (!strcmp(argv[1], "trace")) { | |
1209 | subcmd = PERF_FTRACE_TRACE; | |
1210 | } else if (!strcmp(argv[1], "latency")) { | |
1211 | subcmd = PERF_FTRACE_LATENCY; | |
1212 | options = latency_options; | |
1213 | } | |
1214 | ||
1215 | if (subcmd != PERF_FTRACE_NONE) { | |
1216 | argc--; | |
1217 | argv++; | |
1218 | } | |
416e15ad | 1219 | } |
53be5028 NK |
1220 | /* for backward compatibility */ |
1221 | if (subcmd == PERF_FTRACE_NONE) | |
1222 | subcmd = PERF_FTRACE_TRACE; | |
416e15ad | 1223 | |
53be5028 | 1224 | argc = parse_options(argc, argv, options, ftrace_usage, |
d01f4e8d | 1225 | PARSE_OPT_STOP_AT_NON_OPTION); |
53be5028 NK |
1226 | if (argc < 0) { |
1227 | ret = -EINVAL; | |
1228 | goto out_delete_filters; | |
1229 | } | |
d01f4e8d | 1230 | |
fceb6212 CD |
1231 | switch (subcmd) { |
1232 | case PERF_FTRACE_TRACE: | |
1233 | if (!argc && target__none(&ftrace.target)) | |
1234 | ftrace.target.system_wide = true; | |
1235 | cmd_func = __cmd_ftrace; | |
1236 | break; | |
1237 | case PERF_FTRACE_LATENCY: | |
1238 | if (list_empty(&ftrace.filters)) { | |
1239 | pr_err("Should provide a function to measure\n"); | |
1240 | parse_options_usage(ftrace_usage, options, "T", 1); | |
1241 | ret = -EINVAL; | |
1242 | goto out_delete_filters; | |
1243 | } | |
1244 | cmd_func = __cmd_latency; | |
1245 | break; | |
1246 | case PERF_FTRACE_NONE: | |
1247 | default: | |
1248 | pr_err("Invalid subcommand\n"); | |
1249 | ret = -EINVAL; | |
1250 | goto out_delete_filters; | |
1251 | } | |
1252 | ||
a9af6be5 NK |
1253 | ret = target__validate(&ftrace.target); |
1254 | if (ret) { | |
1255 | char errbuf[512]; | |
1256 | ||
1257 | target__strerror(&ftrace.target, ret, errbuf, 512); | |
1258 | pr_err("%s\n", errbuf); | |
78b83e8b | 1259 | goto out_delete_filters; |
a9af6be5 NK |
1260 | } |
1261 | ||
0f98b11c | 1262 | ftrace.evlist = evlist__new(); |
78b83e8b NK |
1263 | if (ftrace.evlist == NULL) { |
1264 | ret = -ENOMEM; | |
1265 | goto out_delete_filters; | |
1266 | } | |
d01f4e8d | 1267 | |
7748bb71 | 1268 | ret = evlist__create_maps(ftrace.evlist, &ftrace.target); |
d01f4e8d NK |
1269 | if (ret < 0) |
1270 | goto out_delete_evlist; | |
1271 | ||
a9b8ae8a NK |
1272 | if (argc) { |
1273 | ret = evlist__prepare_workload(ftrace.evlist, &ftrace.target, | |
1274 | argv, false, | |
1275 | ftrace__workload_exec_failed_signal); | |
1276 | if (ret < 0) | |
1277 | goto out_delete_evlist; | |
1278 | } | |
1279 | ||
fceb6212 | 1280 | ret = cmd_func(&ftrace); |
d01f4e8d NK |
1281 | |
1282 | out_delete_evlist: | |
c12995a5 | 1283 | evlist__delete(ftrace.evlist); |
d01f4e8d | 1284 | |
78b83e8b NK |
1285 | out_delete_filters: |
1286 | delete_filter_func(&ftrace.filters); | |
1287 | delete_filter_func(&ftrace.notrace); | |
1288 | delete_filter_func(&ftrace.graph_funcs); | |
1289 | delete_filter_func(&ftrace.nograph_funcs); | |
1290 | ||
d01f4e8d NK |
1291 | return ret; |
1292 | } |