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> |
0f223813 | 16 | #include <inttypes.h> |
53be5028 | 17 | #include <math.h> |
4208735d | 18 | #include <poll.h> |
53be5028 | 19 | #include <ctype.h> |
c766f3df | 20 | #include <linux/capability.h> |
8520a98d | 21 | #include <linux/string.h> |
d01f4e8d NK |
22 | |
23 | #include "debug.h" | |
8520a98d | 24 | #include <subcmd/pager.h> |
d01f4e8d | 25 | #include <subcmd/parse-options.h> |
0f223813 | 26 | #include <api/io.h> |
20a9ed28 | 27 | #include <api/fs/tracing_path.h> |
d01f4e8d NK |
28 | #include "evlist.h" |
29 | #include "target.h" | |
dc231032 | 30 | #include "cpumap.h" |
0f223813 | 31 | #include "hashmap.h" |
d01f4e8d | 32 | #include "thread_map.h" |
2ae05fe0 | 33 | #include "strfilter.h" |
c766f3df | 34 | #include "util/cap.h" |
b05d1093 | 35 | #include "util/config.h" |
177f4eac | 36 | #include "util/ftrace.h" |
0f223813 | 37 | #include "util/stat.h" |
846e1939 | 38 | #include "util/units.h" |
b1d84af6 | 39 | #include "util/parse-sublevel-options.h" |
d01f4e8d | 40 | |
d01f4e8d NK |
41 | #define DEFAULT_TRACER "function_graph" |
42 | ||
853596fb IR |
43 | static volatile sig_atomic_t workload_exec_errno; |
44 | static volatile sig_atomic_t done; | |
d01f4e8d NK |
45 | |
46 | static void sig_handler(int sig __maybe_unused) | |
47 | { | |
48 | done = true; | |
49 | } | |
50 | ||
51 | /* | |
7b392ef0 | 52 | * evlist__prepare_workload will send a SIGUSR1 if the fork fails, since |
d01f4e8d NK |
53 | * we asked by setting its exec_error to the function below, |
54 | * ftrace__workload_exec_failed_signal. | |
55 | * | |
56 | * XXX We need to handle this more appropriately, emitting an error, etc. | |
57 | */ | |
58 | static void ftrace__workload_exec_failed_signal(int signo __maybe_unused, | |
59 | siginfo_t *info __maybe_unused, | |
60 | void *ucontext __maybe_unused) | |
61 | { | |
51a09d8f | 62 | workload_exec_errno = info->si_value.sival_int; |
d01f4e8d NK |
63 | done = true; |
64 | } | |
65 | ||
e25ebda7 | 66 | static bool check_ftrace_capable(void) |
608585f4 | 67 | { |
e25ebda7 IR |
68 | bool used_root; |
69 | ||
70 | if (perf_cap__capable(CAP_PERFMON, &used_root)) | |
71 | return true; | |
72 | ||
73 | if (!used_root && perf_cap__capable(CAP_SYS_ADMIN, &used_root)) | |
74 | return true; | |
75 | ||
76 | pr_err("ftrace only works for %s!\n", | |
77 | used_root ? "root" | |
78 | : "users with the CAP_PERFMON or CAP_SYS_ADMIN capability" | |
608585f4 | 79 | ); |
e25ebda7 | 80 | return false; |
608585f4 NK |
81 | } |
82 | ||
74298dd8 CD |
83 | static bool is_ftrace_supported(void) |
84 | { | |
85 | char *file; | |
86 | bool supported = false; | |
87 | ||
88 | file = get_tracing_file("set_ftrace_pid"); | |
89 | if (!file) { | |
90 | pr_debug("cannot get tracing file set_ftrace_pid\n"); | |
91 | return false; | |
92 | } | |
93 | ||
94 | if (!access(file, F_OK)) | |
95 | supported = true; | |
96 | ||
97 | put_tracing_file(file); | |
98 | return supported; | |
99 | } | |
100 | ||
a9af6be5 | 101 | static int __write_tracing_file(const char *name, const char *val, bool append) |
d01f4e8d NK |
102 | { |
103 | char *file; | |
104 | int fd, ret = -1; | |
105 | ssize_t size = strlen(val); | |
a9af6be5 | 106 | int flags = O_WRONLY; |
e7bd9ba2 | 107 | char errbuf[512]; |
63cd02d8 | 108 | char *val_copy; |
d01f4e8d NK |
109 | |
110 | file = get_tracing_file(name); | |
111 | if (!file) { | |
112 | pr_debug("cannot get tracing file: %s\n", name); | |
113 | return -1; | |
114 | } | |
115 | ||
a9af6be5 NK |
116 | if (append) |
117 | flags |= O_APPEND; | |
118 | else | |
119 | flags |= O_TRUNC; | |
120 | ||
121 | fd = open(file, flags); | |
d01f4e8d | 122 | if (fd < 0) { |
e7bd9ba2 NK |
123 | pr_debug("cannot open tracing file: %s: %s\n", |
124 | name, str_error_r(errno, errbuf, sizeof(errbuf))); | |
d01f4e8d NK |
125 | goto out; |
126 | } | |
127 | ||
63cd02d8 CD |
128 | /* |
129 | * Copy the original value and append a '\n'. Without this, | |
130 | * the kernel can hide possible errors. | |
131 | */ | |
132 | val_copy = strdup(val); | |
133 | if (!val_copy) | |
134 | goto out_close; | |
135 | val_copy[size] = '\n'; | |
136 | ||
137 | if (write(fd, val_copy, size + 1) == size + 1) | |
d01f4e8d NK |
138 | ret = 0; |
139 | else | |
e7bd9ba2 NK |
140 | pr_debug("write '%s' to tracing/%s failed: %s\n", |
141 | val, name, str_error_r(errno, errbuf, sizeof(errbuf))); | |
d01f4e8d | 142 | |
63cd02d8 CD |
143 | free(val_copy); |
144 | out_close: | |
d01f4e8d NK |
145 | close(fd); |
146 | out: | |
147 | put_tracing_file(file); | |
148 | return ret; | |
149 | } | |
150 | ||
a9af6be5 NK |
151 | static int write_tracing_file(const char *name, const char *val) |
152 | { | |
153 | return __write_tracing_file(name, val, false); | |
154 | } | |
155 | ||
156 | static int append_tracing_file(const char *name, const char *val) | |
157 | { | |
158 | return __write_tracing_file(name, val, true); | |
159 | } | |
160 | ||
d6d81bfe CD |
161 | static int read_tracing_file_to_stdout(const char *name) |
162 | { | |
163 | char buf[4096]; | |
164 | char *file; | |
165 | int fd; | |
166 | int ret = -1; | |
167 | ||
168 | file = get_tracing_file(name); | |
169 | if (!file) { | |
170 | pr_debug("cannot get tracing file: %s\n", name); | |
171 | return -1; | |
172 | } | |
173 | ||
174 | fd = open(file, O_RDONLY); | |
175 | if (fd < 0) { | |
176 | pr_debug("cannot open tracing file: %s: %s\n", | |
177 | name, str_error_r(errno, buf, sizeof(buf))); | |
178 | goto out; | |
179 | } | |
180 | ||
181 | /* read contents to stdout */ | |
182 | while (true) { | |
183 | int n = read(fd, buf, sizeof(buf)); | |
184 | if (n == 0) | |
185 | break; | |
186 | else if (n < 0) | |
187 | goto out_close; | |
188 | ||
189 | if (fwrite(buf, n, 1, stdout) != 1) | |
190 | goto out_close; | |
191 | } | |
192 | ret = 0; | |
193 | ||
194 | out_close: | |
195 | close(fd); | |
196 | out: | |
197 | put_tracing_file(file); | |
198 | return ret; | |
199 | } | |
200 | ||
2ae05fe0 CD |
201 | static int read_tracing_file_by_line(const char *name, |
202 | void (*cb)(char *str, void *arg), | |
203 | void *cb_arg) | |
204 | { | |
205 | char *line = NULL; | |
206 | size_t len = 0; | |
207 | char *file; | |
208 | FILE *fp; | |
209 | ||
210 | file = get_tracing_file(name); | |
211 | if (!file) { | |
212 | pr_debug("cannot get tracing file: %s\n", name); | |
213 | return -1; | |
214 | } | |
215 | ||
216 | fp = fopen(file, "r"); | |
217 | if (fp == NULL) { | |
218 | pr_debug("cannot open tracing file: %s\n", name); | |
219 | put_tracing_file(file); | |
220 | return -1; | |
221 | } | |
222 | ||
223 | while (getline(&line, &len, fp) != -1) { | |
224 | cb(line, cb_arg); | |
225 | } | |
226 | ||
227 | if (line) | |
228 | free(line); | |
229 | ||
230 | fclose(fp); | |
231 | put_tracing_file(file); | |
232 | return 0; | |
233 | } | |
234 | ||
68faab0f CD |
235 | static int write_tracing_file_int(const char *name, int value) |
236 | { | |
237 | char buf[16]; | |
238 | ||
239 | snprintf(buf, sizeof(buf), "%d", value); | |
240 | if (write_tracing_file(name, buf) < 0) | |
241 | return -1; | |
242 | ||
243 | return 0; | |
244 | } | |
245 | ||
5b347472 CD |
246 | static int write_tracing_option_file(const char *name, const char *val) |
247 | { | |
248 | char *file; | |
249 | int ret; | |
250 | ||
251 | if (asprintf(&file, "options/%s", name) < 0) | |
252 | return -1; | |
253 | ||
254 | ret = __write_tracing_file(file, val, false); | |
255 | free(file); | |
256 | return ret; | |
257 | } | |
258 | ||
dc231032 | 259 | static int reset_tracing_cpu(void); |
78b83e8b | 260 | static void reset_tracing_filters(void); |
dc231032 | 261 | |
5b347472 CD |
262 | static void reset_tracing_options(struct perf_ftrace *ftrace __maybe_unused) |
263 | { | |
264 | write_tracing_option_file("function-fork", "0"); | |
b1d84af6 | 265 | write_tracing_option_file("func_stack_trace", "0"); |
38988f2e | 266 | write_tracing_option_file("sleep-time", "1"); |
d1bcf17c | 267 | write_tracing_option_file("funcgraph-irqs", "1"); |
59486fb0 CD |
268 | write_tracing_option_file("funcgraph-proc", "0"); |
269 | write_tracing_option_file("funcgraph-abstime", "0"); | |
c7780089 | 270 | write_tracing_option_file("funcgraph-tail", "0"); |
59486fb0 | 271 | write_tracing_option_file("latency-format", "0"); |
c81fc34e | 272 | write_tracing_option_file("irq-info", "0"); |
5b347472 CD |
273 | } |
274 | ||
d01f4e8d NK |
275 | static int reset_tracing_files(struct perf_ftrace *ftrace __maybe_unused) |
276 | { | |
277 | if (write_tracing_file("tracing_on", "0") < 0) | |
278 | return -1; | |
279 | ||
280 | if (write_tracing_file("current_tracer", "nop") < 0) | |
281 | return -1; | |
282 | ||
283 | if (write_tracing_file("set_ftrace_pid", " ") < 0) | |
284 | return -1; | |
285 | ||
dc231032 NK |
286 | if (reset_tracing_cpu() < 0) |
287 | return -1; | |
288 | ||
1096c35a NK |
289 | if (write_tracing_file("max_graph_depth", "0") < 0) |
290 | return -1; | |
291 | ||
00c85d5f CD |
292 | if (write_tracing_file("tracing_thresh", "0") < 0) |
293 | return -1; | |
294 | ||
78b83e8b | 295 | reset_tracing_filters(); |
5b347472 | 296 | reset_tracing_options(ftrace); |
d01f4e8d NK |
297 | return 0; |
298 | } | |
299 | ||
a9af6be5 NK |
300 | static int set_tracing_pid(struct perf_ftrace *ftrace) |
301 | { | |
302 | int i; | |
303 | char buf[16]; | |
304 | ||
305 | if (target__has_cpu(&ftrace->target)) | |
306 | return 0; | |
307 | ||
a2f354e3 | 308 | for (i = 0; i < perf_thread_map__nr(ftrace->evlist->core.threads); i++) { |
a9af6be5 | 309 | scnprintf(buf, sizeof(buf), "%d", |
671b60cb | 310 | perf_thread_map__pid(ftrace->evlist->core.threads, i)); |
a9af6be5 NK |
311 | if (append_tracing_file("set_ftrace_pid", buf) < 0) |
312 | return -1; | |
313 | } | |
314 | return 0; | |
315 | } | |
316 | ||
f854839b | 317 | static int set_tracing_cpumask(struct perf_cpu_map *cpumap) |
dc231032 NK |
318 | { |
319 | char *cpumask; | |
320 | size_t mask_size; | |
321 | int ret; | |
322 | int last_cpu; | |
323 | ||
44028699 | 324 | last_cpu = perf_cpu_map__cpu(cpumap, perf_cpu_map__nr(cpumap) - 1).cpu; |
cf30ae72 | 325 | mask_size = last_cpu / 4 + 2; /* one more byte for EOS */ |
dc231032 NK |
326 | mask_size += last_cpu / 32; /* ',' is needed for every 32th cpus */ |
327 | ||
328 | cpumask = malloc(mask_size); | |
329 | if (cpumask == NULL) { | |
330 | pr_debug("failed to allocate cpu mask\n"); | |
331 | return -1; | |
332 | } | |
333 | ||
334 | cpu_map__snprint_mask(cpumap, cpumask, mask_size); | |
335 | ||
336 | ret = write_tracing_file("tracing_cpumask", cpumask); | |
337 | ||
338 | free(cpumask); | |
339 | return ret; | |
340 | } | |
341 | ||
342 | static int set_tracing_cpu(struct perf_ftrace *ftrace) | |
343 | { | |
0df6ade7 | 344 | struct perf_cpu_map *cpumap = ftrace->evlist->core.user_requested_cpus; |
dc231032 NK |
345 | |
346 | if (!target__has_cpu(&ftrace->target)) | |
347 | return 0; | |
348 | ||
349 | return set_tracing_cpumask(cpumap); | |
350 | } | |
351 | ||
b1d84af6 CD |
352 | static int set_tracing_func_stack_trace(struct perf_ftrace *ftrace) |
353 | { | |
354 | if (!ftrace->func_stack_trace) | |
355 | return 0; | |
356 | ||
357 | if (write_tracing_option_file("func_stack_trace", "1") < 0) | |
358 | return -1; | |
359 | ||
360 | return 0; | |
361 | } | |
362 | ||
c81fc34e CD |
363 | static int set_tracing_func_irqinfo(struct perf_ftrace *ftrace) |
364 | { | |
365 | if (!ftrace->func_irq_info) | |
366 | return 0; | |
367 | ||
368 | if (write_tracing_option_file("irq-info", "1") < 0) | |
369 | return -1; | |
370 | ||
371 | return 0; | |
372 | } | |
373 | ||
dc231032 NK |
374 | static int reset_tracing_cpu(void) |
375 | { | |
effe957c | 376 | struct perf_cpu_map *cpumap = perf_cpu_map__new_online_cpus(); |
dc231032 NK |
377 | int ret; |
378 | ||
379 | ret = set_tracing_cpumask(cpumap); | |
38f01d8d | 380 | perf_cpu_map__put(cpumap); |
dc231032 NK |
381 | return ret; |
382 | } | |
383 | ||
78b83e8b NK |
384 | static int __set_tracing_filter(const char *filter_file, struct list_head *funcs) |
385 | { | |
386 | struct filter_entry *pos; | |
387 | ||
388 | list_for_each_entry(pos, funcs, list) { | |
389 | if (append_tracing_file(filter_file, pos->name) < 0) | |
390 | return -1; | |
391 | } | |
392 | ||
393 | return 0; | |
394 | } | |
395 | ||
396 | static int set_tracing_filters(struct perf_ftrace *ftrace) | |
397 | { | |
398 | int ret; | |
399 | ||
400 | ret = __set_tracing_filter("set_ftrace_filter", &ftrace->filters); | |
401 | if (ret < 0) | |
402 | return ret; | |
403 | ||
404 | ret = __set_tracing_filter("set_ftrace_notrace", &ftrace->notrace); | |
405 | if (ret < 0) | |
406 | return ret; | |
407 | ||
408 | ret = __set_tracing_filter("set_graph_function", &ftrace->graph_funcs); | |
409 | if (ret < 0) | |
410 | return ret; | |
411 | ||
412 | /* old kernels do not have this filter */ | |
413 | __set_tracing_filter("set_graph_notrace", &ftrace->nograph_funcs); | |
414 | ||
415 | return ret; | |
416 | } | |
417 | ||
418 | static void reset_tracing_filters(void) | |
419 | { | |
420 | write_tracing_file("set_ftrace_filter", " "); | |
421 | write_tracing_file("set_ftrace_notrace", " "); | |
422 | write_tracing_file("set_graph_function", " "); | |
423 | write_tracing_file("set_graph_notrace", " "); | |
424 | } | |
425 | ||
1096c35a NK |
426 | static int set_tracing_depth(struct perf_ftrace *ftrace) |
427 | { | |
1096c35a NK |
428 | if (ftrace->graph_depth == 0) |
429 | return 0; | |
430 | ||
431 | if (ftrace->graph_depth < 0) { | |
432 | pr_err("invalid graph depth: %d\n", ftrace->graph_depth); | |
433 | return -1; | |
434 | } | |
435 | ||
68faab0f | 436 | if (write_tracing_file_int("max_graph_depth", ftrace->graph_depth) < 0) |
1096c35a NK |
437 | return -1; |
438 | ||
439 | return 0; | |
440 | } | |
441 | ||
846e1939 CD |
442 | static int set_tracing_percpu_buffer_size(struct perf_ftrace *ftrace) |
443 | { | |
444 | int ret; | |
445 | ||
446 | if (ftrace->percpu_buffer_size == 0) | |
447 | return 0; | |
448 | ||
449 | ret = write_tracing_file_int("buffer_size_kb", | |
450 | ftrace->percpu_buffer_size / 1024); | |
451 | if (ret < 0) | |
452 | return ret; | |
453 | ||
454 | return 0; | |
455 | } | |
456 | ||
5b347472 CD |
457 | static int set_tracing_trace_inherit(struct perf_ftrace *ftrace) |
458 | { | |
459 | if (!ftrace->inherit) | |
460 | return 0; | |
461 | ||
462 | if (write_tracing_option_file("function-fork", "1") < 0) | |
463 | return -1; | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
38988f2e CD |
468 | static int set_tracing_sleep_time(struct perf_ftrace *ftrace) |
469 | { | |
470 | if (!ftrace->graph_nosleep_time) | |
471 | return 0; | |
472 | ||
473 | if (write_tracing_option_file("sleep-time", "0") < 0) | |
474 | return -1; | |
475 | ||
476 | return 0; | |
477 | } | |
478 | ||
d1bcf17c CD |
479 | static int set_tracing_funcgraph_irqs(struct perf_ftrace *ftrace) |
480 | { | |
481 | if (!ftrace->graph_noirqs) | |
482 | return 0; | |
483 | ||
484 | if (write_tracing_option_file("funcgraph-irqs", "0") < 0) | |
485 | return -1; | |
486 | ||
487 | return 0; | |
488 | } | |
489 | ||
59486fb0 CD |
490 | static int set_tracing_funcgraph_verbose(struct perf_ftrace *ftrace) |
491 | { | |
492 | if (!ftrace->graph_verbose) | |
493 | return 0; | |
494 | ||
495 | if (write_tracing_option_file("funcgraph-proc", "1") < 0) | |
496 | return -1; | |
497 | ||
498 | if (write_tracing_option_file("funcgraph-abstime", "1") < 0) | |
499 | return -1; | |
500 | ||
501 | if (write_tracing_option_file("latency-format", "1") < 0) | |
502 | return -1; | |
503 | ||
504 | return 0; | |
505 | } | |
506 | ||
c7780089 NK |
507 | static int set_tracing_funcgraph_tail(struct perf_ftrace *ftrace) |
508 | { | |
509 | if (!ftrace->graph_tail) | |
510 | return 0; | |
511 | ||
512 | if (write_tracing_option_file("funcgraph-tail", "1") < 0) | |
513 | return -1; | |
514 | ||
515 | return 0; | |
516 | } | |
517 | ||
00c85d5f CD |
518 | static int set_tracing_thresh(struct perf_ftrace *ftrace) |
519 | { | |
520 | int ret; | |
521 | ||
522 | if (ftrace->graph_thresh == 0) | |
523 | return 0; | |
524 | ||
525 | ret = write_tracing_file_int("tracing_thresh", ftrace->graph_thresh); | |
526 | if (ret < 0) | |
527 | return ret; | |
528 | ||
529 | return 0; | |
530 | } | |
531 | ||
3c4dc21b | 532 | static int set_tracing_options(struct perf_ftrace *ftrace) |
d01f4e8d | 533 | { |
a9af6be5 NK |
534 | if (set_tracing_pid(ftrace) < 0) { |
535 | pr_err("failed to set ftrace pid\n"); | |
3c4dc21b | 536 | return -1; |
d01f4e8d NK |
537 | } |
538 | ||
dc231032 NK |
539 | if (set_tracing_cpu(ftrace) < 0) { |
540 | pr_err("failed to set tracing cpumask\n"); | |
3c4dc21b | 541 | return -1; |
dc231032 NK |
542 | } |
543 | ||
b1d84af6 CD |
544 | if (set_tracing_func_stack_trace(ftrace) < 0) { |
545 | pr_err("failed to set tracing option func_stack_trace\n"); | |
3c4dc21b | 546 | return -1; |
b1d84af6 CD |
547 | } |
548 | ||
c81fc34e CD |
549 | if (set_tracing_func_irqinfo(ftrace) < 0) { |
550 | pr_err("failed to set tracing option irq-info\n"); | |
3c4dc21b | 551 | return -1; |
c81fc34e CD |
552 | } |
553 | ||
78b83e8b NK |
554 | if (set_tracing_filters(ftrace) < 0) { |
555 | pr_err("failed to set tracing filters\n"); | |
3c4dc21b | 556 | return -1; |
78b83e8b NK |
557 | } |
558 | ||
1096c35a NK |
559 | if (set_tracing_depth(ftrace) < 0) { |
560 | pr_err("failed to set graph depth\n"); | |
3c4dc21b | 561 | return -1; |
1096c35a NK |
562 | } |
563 | ||
846e1939 CD |
564 | if (set_tracing_percpu_buffer_size(ftrace) < 0) { |
565 | pr_err("failed to set tracing per-cpu buffer size\n"); | |
3c4dc21b | 566 | return -1; |
846e1939 CD |
567 | } |
568 | ||
5b347472 CD |
569 | if (set_tracing_trace_inherit(ftrace) < 0) { |
570 | pr_err("failed to set tracing option function-fork\n"); | |
3c4dc21b | 571 | return -1; |
5b347472 CD |
572 | } |
573 | ||
38988f2e CD |
574 | if (set_tracing_sleep_time(ftrace) < 0) { |
575 | pr_err("failed to set tracing option sleep-time\n"); | |
3c4dc21b | 576 | return -1; |
38988f2e CD |
577 | } |
578 | ||
d1bcf17c CD |
579 | if (set_tracing_funcgraph_irqs(ftrace) < 0) { |
580 | pr_err("failed to set tracing option funcgraph-irqs\n"); | |
3c4dc21b | 581 | return -1; |
d1bcf17c CD |
582 | } |
583 | ||
59486fb0 CD |
584 | if (set_tracing_funcgraph_verbose(ftrace) < 0) { |
585 | pr_err("failed to set tracing option funcgraph-proc/funcgraph-abstime\n"); | |
3c4dc21b | 586 | return -1; |
59486fb0 CD |
587 | } |
588 | ||
00c85d5f CD |
589 | if (set_tracing_thresh(ftrace) < 0) { |
590 | pr_err("failed to set tracing thresh\n"); | |
3c4dc21b CD |
591 | return -1; |
592 | } | |
593 | ||
c7780089 NK |
594 | if (set_tracing_funcgraph_tail(ftrace) < 0) { |
595 | pr_err("failed to set tracing option funcgraph-tail\n"); | |
596 | return -1; | |
597 | } | |
598 | ||
3c4dc21b CD |
599 | return 0; |
600 | } | |
601 | ||
a9b8ae8a NK |
602 | static void select_tracer(struct perf_ftrace *ftrace) |
603 | { | |
604 | bool graph = !list_empty(&ftrace->graph_funcs) || | |
605 | !list_empty(&ftrace->nograph_funcs); | |
606 | bool func = !list_empty(&ftrace->filters) || | |
607 | !list_empty(&ftrace->notrace); | |
608 | ||
609 | /* The function_graph has priority over function tracer. */ | |
610 | if (graph) | |
611 | ftrace->tracer = "function_graph"; | |
612 | else if (func) | |
613 | ftrace->tracer = "function"; | |
614 | /* Otherwise, the default tracer is used. */ | |
615 | ||
616 | pr_debug("%s tracer is used\n", ftrace->tracer); | |
617 | } | |
618 | ||
619 | static int __cmd_ftrace(struct perf_ftrace *ftrace) | |
3c4dc21b CD |
620 | { |
621 | char *trace_file; | |
622 | int trace_fd; | |
623 | char buf[4096]; | |
624 | struct pollfd pollfd = { | |
625 | .events = POLLIN, | |
626 | }; | |
627 | ||
a9b8ae8a | 628 | select_tracer(ftrace); |
3c4dc21b | 629 | |
3c4dc21b CD |
630 | if (reset_tracing_files(ftrace) < 0) { |
631 | pr_err("failed to reset ftrace\n"); | |
632 | goto out; | |
633 | } | |
634 | ||
635 | /* reset ftrace buffer */ | |
636 | if (write_tracing_file("trace", "0") < 0) | |
637 | goto out; | |
638 | ||
3c4dc21b CD |
639 | if (set_tracing_options(ftrace) < 0) |
640 | goto out_reset; | |
641 | ||
a9af6be5 NK |
642 | if (write_tracing_file("current_tracer", ftrace->tracer) < 0) { |
643 | pr_err("failed to set current_tracer to %s\n", ftrace->tracer); | |
644 | goto out_reset; | |
d01f4e8d NK |
645 | } |
646 | ||
29681bc5 NK |
647 | setup_pager(); |
648 | ||
d01f4e8d NK |
649 | trace_file = get_tracing_file("trace_pipe"); |
650 | if (!trace_file) { | |
651 | pr_err("failed to open trace_pipe\n"); | |
a9af6be5 | 652 | goto out_reset; |
d01f4e8d NK |
653 | } |
654 | ||
655 | trace_fd = open(trace_file, O_RDONLY); | |
656 | ||
657 | put_tracing_file(trace_file); | |
658 | ||
659 | if (trace_fd < 0) { | |
660 | pr_err("failed to open trace_pipe\n"); | |
a9af6be5 | 661 | goto out_reset; |
d01f4e8d NK |
662 | } |
663 | ||
664 | fcntl(trace_fd, F_SETFL, O_NONBLOCK); | |
665 | pollfd.fd = trace_fd; | |
666 | ||
81523c1e CD |
667 | /* display column headers */ |
668 | read_tracing_file_to_stdout("trace"); | |
669 | ||
f9f60efb | 670 | if (!ftrace->target.initial_delay) { |
6555c2f6 CD |
671 | if (write_tracing_file("tracing_on", "1") < 0) { |
672 | pr_err("can't enable tracing\n"); | |
673 | goto out_close_fd; | |
674 | } | |
d01f4e8d NK |
675 | } |
676 | ||
7b392ef0 | 677 | evlist__start_workload(ftrace->evlist); |
d01f4e8d | 678 | |
f9f60efb CD |
679 | if (ftrace->target.initial_delay > 0) { |
680 | usleep(ftrace->target.initial_delay * 1000); | |
6555c2f6 CD |
681 | if (write_tracing_file("tracing_on", "1") < 0) { |
682 | pr_err("can't enable tracing\n"); | |
683 | goto out_close_fd; | |
684 | } | |
685 | } | |
686 | ||
d01f4e8d NK |
687 | while (!done) { |
688 | if (poll(&pollfd, 1, -1) < 0) | |
689 | break; | |
690 | ||
691 | if (pollfd.revents & POLLIN) { | |
692 | int n = read(trace_fd, buf, sizeof(buf)); | |
693 | if (n < 0) | |
694 | break; | |
695 | if (fwrite(buf, n, 1, stdout) != 1) | |
696 | break; | |
40bf1cb0 CD |
697 | /* flush output since stdout is in full buffering mode due to pager */ |
698 | fflush(stdout); | |
d01f4e8d NK |
699 | } |
700 | } | |
701 | ||
702 | write_tracing_file("tracing_on", "0"); | |
703 | ||
51a09d8f CD |
704 | if (workload_exec_errno) { |
705 | const char *emsg = str_error_r(workload_exec_errno, buf, sizeof(buf)); | |
706 | /* flush stdout first so below error msg appears at the end. */ | |
707 | fflush(stdout); | |
708 | pr_err("workload failed: %s\n", emsg); | |
709 | goto out_close_fd; | |
710 | } | |
711 | ||
d01f4e8d NK |
712 | /* read remaining buffer contents */ |
713 | while (true) { | |
714 | int n = read(trace_fd, buf, sizeof(buf)); | |
715 | if (n <= 0) | |
716 | break; | |
717 | if (fwrite(buf, n, 1, stdout) != 1) | |
718 | break; | |
719 | } | |
720 | ||
721 | out_close_fd: | |
722 | close(trace_fd); | |
a9af6be5 | 723 | out_reset: |
d01f4e8d | 724 | reset_tracing_files(ftrace); |
a9af6be5 | 725 | out: |
51a09d8f | 726 | return (done && !workload_exec_errno) ? 0 : -1; |
d01f4e8d NK |
727 | } |
728 | ||
84005bb6 NK |
729 | static void make_histogram(int buckets[], char *buf, size_t len, char *linebuf, |
730 | bool use_nsec) | |
53be5028 NK |
731 | { |
732 | char *p, *q; | |
733 | char *unit; | |
734 | double num; | |
735 | int i; | |
736 | ||
737 | /* ensure NUL termination */ | |
738 | buf[len] = '\0'; | |
739 | ||
740 | /* handle data line by line */ | |
741 | for (p = buf; (q = strchr(p, '\n')) != NULL; p = q + 1) { | |
742 | *q = '\0'; | |
743 | /* move it to the line buffer */ | |
744 | strcat(linebuf, p); | |
745 | ||
746 | /* | |
747 | * parse trace output to get function duration like in | |
748 | * | |
749 | * # tracer: function_graph | |
750 | * # | |
751 | * # CPU DURATION FUNCTION CALLS | |
752 | * # | | | | | | | | |
753 | * 1) + 10.291 us | do_filp_open(); | |
754 | * 1) 4.889 us | do_filp_open(); | |
755 | * 1) 6.086 us | do_filp_open(); | |
756 | * | |
757 | */ | |
758 | if (linebuf[0] == '#') | |
759 | goto next; | |
760 | ||
761 | /* ignore CPU */ | |
762 | p = strchr(linebuf, ')'); | |
763 | if (p == NULL) | |
764 | p = linebuf; | |
765 | ||
766 | while (*p && !isdigit(*p) && (*p != '|')) | |
767 | p++; | |
768 | ||
769 | /* no duration */ | |
770 | if (*p == '\0' || *p == '|') | |
771 | goto next; | |
772 | ||
773 | num = strtod(p, &unit); | |
774 | if (!unit || strncmp(unit, " us", 3)) | |
775 | goto next; | |
776 | ||
84005bb6 NK |
777 | if (use_nsec) |
778 | num *= 1000; | |
779 | ||
53be5028 NK |
780 | i = log2(num); |
781 | if (i < 0) | |
782 | i = 0; | |
783 | if (i >= NUM_BUCKET) | |
784 | i = NUM_BUCKET - 1; | |
785 | ||
786 | buckets[i]++; | |
787 | ||
788 | next: | |
789 | /* empty the line buffer for the next output */ | |
790 | linebuf[0] = '\0'; | |
791 | } | |
792 | ||
793 | /* preserve any remaining output (before newline) */ | |
794 | strcat(linebuf, p); | |
795 | } | |
796 | ||
84005bb6 | 797 | static void display_histogram(int buckets[], bool use_nsec) |
53be5028 NK |
798 | { |
799 | int i; | |
800 | int total = 0; | |
801 | int bar_total = 46; /* to fit in 80 column */ | |
802 | char bar[] = "###############################################"; | |
803 | int bar_len; | |
804 | ||
805 | for (i = 0; i < NUM_BUCKET; i++) | |
806 | total += buckets[i]; | |
807 | ||
808 | if (total == 0) { | |
809 | printf("No data found\n"); | |
810 | return; | |
811 | } | |
812 | ||
813 | printf("# %14s | %10s | %-*s |\n", | |
814 | " DURATION ", "COUNT", bar_total, "GRAPH"); | |
815 | ||
816 | bar_len = buckets[0] * bar_total / total; | |
817 | printf(" %4d - %-4d %s | %10d | %.*s%*s |\n", | |
818 | 0, 1, "us", buckets[0], bar_len, bar, bar_total - bar_len, ""); | |
819 | ||
820 | for (i = 1; i < NUM_BUCKET - 1; i++) { | |
821 | int start = (1 << (i - 1)); | |
822 | int stop = 1 << i; | |
84005bb6 | 823 | const char *unit = use_nsec ? "ns" : "us"; |
53be5028 NK |
824 | |
825 | if (start >= 1024) { | |
826 | start >>= 10; | |
827 | stop >>= 10; | |
84005bb6 | 828 | unit = use_nsec ? "us" : "ms"; |
53be5028 NK |
829 | } |
830 | bar_len = buckets[i] * bar_total / total; | |
831 | printf(" %4d - %-4d %s | %10d | %.*s%*s |\n", | |
832 | start, stop, unit, buckets[i], bar_len, bar, | |
833 | bar_total - bar_len, ""); | |
834 | } | |
835 | ||
836 | bar_len = buckets[NUM_BUCKET - 1] * bar_total / total; | |
837 | printf(" %4d - %-4s %s | %10d | %.*s%*s |\n", | |
84005bb6 NK |
838 | 1, "...", use_nsec ? "ms" : " s", buckets[NUM_BUCKET - 1], |
839 | bar_len, bar, bar_total - bar_len, ""); | |
53be5028 NK |
840 | |
841 | } | |
842 | ||
177f4eac | 843 | static int prepare_func_latency(struct perf_ftrace *ftrace) |
53be5028 NK |
844 | { |
845 | char *trace_file; | |
177f4eac | 846 | int fd; |
53be5028 | 847 | |
177f4eac NK |
848 | if (ftrace->target.use_bpf) |
849 | return perf_ftrace__latency_prepare_bpf(ftrace); | |
53be5028 NK |
850 | |
851 | if (reset_tracing_files(ftrace) < 0) { | |
852 | pr_err("failed to reset ftrace\n"); | |
177f4eac | 853 | return -1; |
53be5028 NK |
854 | } |
855 | ||
856 | /* reset ftrace buffer */ | |
857 | if (write_tracing_file("trace", "0") < 0) | |
177f4eac | 858 | return -1; |
53be5028 NK |
859 | |
860 | if (set_tracing_options(ftrace) < 0) | |
177f4eac | 861 | return -1; |
53be5028 NK |
862 | |
863 | /* force to use the function_graph tracer to track duration */ | |
864 | if (write_tracing_file("current_tracer", "function_graph") < 0) { | |
865 | pr_err("failed to set current_tracer to function_graph\n"); | |
177f4eac | 866 | return -1; |
53be5028 NK |
867 | } |
868 | ||
869 | trace_file = get_tracing_file("trace_pipe"); | |
870 | if (!trace_file) { | |
871 | pr_err("failed to open trace_pipe\n"); | |
177f4eac | 872 | return -1; |
53be5028 NK |
873 | } |
874 | ||
177f4eac NK |
875 | fd = open(trace_file, O_RDONLY); |
876 | if (fd < 0) | |
877 | pr_err("failed to open trace_pipe\n"); | |
53be5028 NK |
878 | |
879 | put_tracing_file(trace_file); | |
177f4eac NK |
880 | return fd; |
881 | } | |
53be5028 | 882 | |
177f4eac NK |
883 | static int start_func_latency(struct perf_ftrace *ftrace) |
884 | { | |
885 | if (ftrace->target.use_bpf) | |
886 | return perf_ftrace__latency_start_bpf(ftrace); | |
887 | ||
888 | if (write_tracing_file("tracing_on", "1") < 0) { | |
889 | pr_err("can't enable tracing\n"); | |
890 | return -1; | |
53be5028 NK |
891 | } |
892 | ||
177f4eac NK |
893 | return 0; |
894 | } | |
895 | ||
896 | static int stop_func_latency(struct perf_ftrace *ftrace) | |
897 | { | |
898 | if (ftrace->target.use_bpf) | |
899 | return perf_ftrace__latency_stop_bpf(ftrace); | |
900 | ||
901 | write_tracing_file("tracing_on", "0"); | |
902 | return 0; | |
903 | } | |
904 | ||
905 | static int read_func_latency(struct perf_ftrace *ftrace, int buckets[]) | |
906 | { | |
907 | if (ftrace->target.use_bpf) | |
908 | return perf_ftrace__latency_read_bpf(ftrace, buckets); | |
909 | ||
910 | return 0; | |
911 | } | |
912 | ||
913 | static int cleanup_func_latency(struct perf_ftrace *ftrace) | |
914 | { | |
915 | if (ftrace->target.use_bpf) | |
916 | return perf_ftrace__latency_cleanup_bpf(ftrace); | |
917 | ||
918 | reset_tracing_files(ftrace); | |
919 | return 0; | |
920 | } | |
921 | ||
922 | static int __cmd_latency(struct perf_ftrace *ftrace) | |
923 | { | |
924 | int trace_fd; | |
925 | char buf[4096]; | |
926 | char line[256]; | |
927 | struct pollfd pollfd = { | |
928 | .events = POLLIN, | |
929 | }; | |
930 | int buckets[NUM_BUCKET] = { }; | |
931 | ||
177f4eac NK |
932 | trace_fd = prepare_func_latency(ftrace); |
933 | if (trace_fd < 0) | |
934 | goto out; | |
935 | ||
53be5028 NK |
936 | fcntl(trace_fd, F_SETFL, O_NONBLOCK); |
937 | pollfd.fd = trace_fd; | |
938 | ||
177f4eac NK |
939 | if (start_func_latency(ftrace) < 0) |
940 | goto out; | |
53be5028 NK |
941 | |
942 | evlist__start_workload(ftrace->evlist); | |
943 | ||
944 | line[0] = '\0'; | |
945 | while (!done) { | |
946 | if (poll(&pollfd, 1, -1) < 0) | |
947 | break; | |
948 | ||
949 | if (pollfd.revents & POLLIN) { | |
950 | int n = read(trace_fd, buf, sizeof(buf) - 1); | |
951 | if (n < 0) | |
952 | break; | |
953 | ||
84005bb6 | 954 | make_histogram(buckets, buf, n, line, ftrace->use_nsec); |
53be5028 NK |
955 | } |
956 | } | |
957 | ||
177f4eac | 958 | stop_func_latency(ftrace); |
53be5028 NK |
959 | |
960 | if (workload_exec_errno) { | |
961 | const char *emsg = str_error_r(workload_exec_errno, buf, sizeof(buf)); | |
962 | pr_err("workload failed: %s\n", emsg); | |
177f4eac | 963 | goto out; |
53be5028 NK |
964 | } |
965 | ||
966 | /* read remaining buffer contents */ | |
177f4eac | 967 | while (!ftrace->target.use_bpf) { |
53be5028 NK |
968 | int n = read(trace_fd, buf, sizeof(buf) - 1); |
969 | if (n <= 0) | |
970 | break; | |
84005bb6 | 971 | make_histogram(buckets, buf, n, line, ftrace->use_nsec); |
53be5028 NK |
972 | } |
973 | ||
177f4eac NK |
974 | read_func_latency(ftrace, buckets); |
975 | ||
84005bb6 | 976 | display_histogram(buckets, ftrace->use_nsec); |
53be5028 | 977 | |
53be5028 | 978 | out: |
177f4eac NK |
979 | close(trace_fd); |
980 | cleanup_func_latency(ftrace); | |
981 | ||
53be5028 NK |
982 | return (done && !workload_exec_errno) ? 0 : -1; |
983 | } | |
984 | ||
0f223813 NK |
985 | static size_t profile_hash(long func, void *ctx __maybe_unused) |
986 | { | |
987 | return str_hash((char *)func); | |
988 | } | |
989 | ||
990 | static bool profile_equal(long func1, long func2, void *ctx __maybe_unused) | |
991 | { | |
992 | return !strcmp((char *)func1, (char *)func2); | |
993 | } | |
994 | ||
995 | static int prepare_func_profile(struct perf_ftrace *ftrace) | |
996 | { | |
997 | ftrace->tracer = "function_graph"; | |
998 | ftrace->graph_tail = 1; | |
999 | ||
1000 | ftrace->profile_hash = hashmap__new(profile_hash, profile_equal, NULL); | |
1001 | if (ftrace->profile_hash == NULL) | |
1002 | return -ENOMEM; | |
1003 | ||
1004 | return 0; | |
1005 | } | |
1006 | ||
1007 | /* This is saved in a hashmap keyed by the function name */ | |
1008 | struct ftrace_profile_data { | |
1009 | struct stats st; | |
1010 | }; | |
1011 | ||
1012 | static int add_func_duration(struct perf_ftrace *ftrace, char *func, double time_ns) | |
1013 | { | |
1014 | struct ftrace_profile_data *prof = NULL; | |
1015 | ||
1016 | if (!hashmap__find(ftrace->profile_hash, func, &prof)) { | |
1017 | char *key = strdup(func); | |
1018 | ||
1019 | if (key == NULL) | |
1020 | return -ENOMEM; | |
1021 | ||
1022 | prof = zalloc(sizeof(*prof)); | |
1023 | if (prof == NULL) { | |
1024 | free(key); | |
1025 | return -ENOMEM; | |
1026 | } | |
1027 | ||
1028 | init_stats(&prof->st); | |
1029 | hashmap__add(ftrace->profile_hash, key, prof); | |
1030 | } | |
1031 | ||
1032 | update_stats(&prof->st, time_ns); | |
1033 | return 0; | |
1034 | } | |
1035 | ||
1036 | /* | |
1037 | * The ftrace function_graph text output normally looks like below: | |
1038 | * | |
1039 | * CPU DURATION FUNCTION | |
1040 | * | |
1041 | * 0) | syscall_trace_enter.isra.0() { | |
1042 | * 0) | __audit_syscall_entry() { | |
1043 | * 0) | auditd_test_task() { | |
1044 | * 0) 0.271 us | __rcu_read_lock(); | |
1045 | * 0) 0.275 us | __rcu_read_unlock(); | |
1046 | * 0) 1.254 us | } /\* auditd_test_task *\/ | |
1047 | * 0) 0.279 us | ktime_get_coarse_real_ts64(); | |
1048 | * 0) 2.227 us | } /\* __audit_syscall_entry *\/ | |
1049 | * 0) 2.713 us | } /\* syscall_trace_enter.isra.0 *\/ | |
1050 | * | |
1051 | * Parse the line and get the duration and function name. | |
1052 | */ | |
1053 | static int parse_func_duration(struct perf_ftrace *ftrace, char *line, size_t len) | |
1054 | { | |
1055 | char *p; | |
1056 | char *func; | |
1057 | double duration; | |
1058 | ||
1059 | /* skip CPU */ | |
1060 | p = strchr(line, ')'); | |
1061 | if (p == NULL) | |
1062 | return 0; | |
1063 | ||
1064 | /* get duration */ | |
1065 | p = skip_spaces(p + 1); | |
1066 | ||
1067 | /* no duration? */ | |
1068 | if (p == NULL || *p == '|') | |
1069 | return 0; | |
1070 | ||
1071 | /* skip markers like '*' or '!' for longer than ms */ | |
1072 | if (!isdigit(*p)) | |
1073 | p++; | |
1074 | ||
1075 | duration = strtod(p, &p); | |
1076 | ||
1077 | if (strncmp(p, " us", 3)) { | |
1078 | pr_debug("non-usec time found.. ignoring\n"); | |
1079 | return 0; | |
1080 | } | |
1081 | ||
1082 | /* | |
1083 | * profile stat keeps the max and min values as integer, | |
1084 | * convert to nsec time so that we can have accurate max. | |
1085 | */ | |
1086 | duration *= 1000; | |
1087 | ||
1088 | /* skip to the pipe */ | |
1089 | while (p < line + len && *p != '|') | |
1090 | p++; | |
1091 | ||
1092 | if (*p++ != '|') | |
1093 | return -EINVAL; | |
1094 | ||
1095 | /* get function name */ | |
1096 | func = skip_spaces(p); | |
1097 | ||
1098 | /* skip the closing bracket and the start of comment */ | |
1099 | if (*func == '}') | |
1100 | func += 5; | |
1101 | ||
1102 | /* remove semi-colon or end of comment at the end */ | |
1103 | p = line + len - 1; | |
1104 | while (!isalnum(*p) && *p != ']') { | |
1105 | *p = '\0'; | |
1106 | --p; | |
1107 | } | |
1108 | ||
1109 | return add_func_duration(ftrace, func, duration); | |
1110 | } | |
1111 | ||
74ae366c NK |
1112 | enum perf_ftrace_profile_sort_key { |
1113 | PFP_SORT_TOTAL = 0, | |
1114 | PFP_SORT_AVG, | |
1115 | PFP_SORT_MAX, | |
1116 | PFP_SORT_COUNT, | |
1117 | PFP_SORT_NAME, | |
1118 | }; | |
1119 | ||
1120 | static enum perf_ftrace_profile_sort_key profile_sort = PFP_SORT_TOTAL; | |
1121 | ||
0f223813 NK |
1122 | static int cmp_profile_data(const void *a, const void *b) |
1123 | { | |
1124 | const struct hashmap_entry *e1 = *(const struct hashmap_entry **)a; | |
1125 | const struct hashmap_entry *e2 = *(const struct hashmap_entry **)b; | |
1126 | struct ftrace_profile_data *p1 = e1->pvalue; | |
1127 | struct ftrace_profile_data *p2 = e2->pvalue; | |
74ae366c NK |
1128 | double v1, v2; |
1129 | ||
1130 | switch (profile_sort) { | |
1131 | case PFP_SORT_NAME: | |
1132 | return strcmp(e1->pkey, e2->pkey); | |
1133 | case PFP_SORT_AVG: | |
1134 | v1 = p1->st.mean; | |
1135 | v2 = p2->st.mean; | |
1136 | break; | |
1137 | case PFP_SORT_MAX: | |
1138 | v1 = p1->st.max; | |
1139 | v2 = p2->st.max; | |
1140 | break; | |
1141 | case PFP_SORT_COUNT: | |
1142 | v1 = p1->st.n; | |
1143 | v2 = p2->st.n; | |
1144 | break; | |
1145 | case PFP_SORT_TOTAL: | |
1146 | default: | |
1147 | v1 = p1->st.n * p1->st.mean; | |
1148 | v2 = p2->st.n * p2->st.mean; | |
1149 | break; | |
1150 | } | |
1151 | ||
1152 | if (v1 > v2) | |
0f223813 NK |
1153 | return -1; |
1154 | else | |
1155 | return 1; | |
1156 | } | |
1157 | ||
1158 | static void print_profile_result(struct perf_ftrace *ftrace) | |
1159 | { | |
1160 | struct hashmap_entry *entry, **profile; | |
1161 | size_t i, nr, bkt; | |
1162 | ||
1163 | nr = hashmap__size(ftrace->profile_hash); | |
1164 | if (nr == 0) | |
1165 | return; | |
1166 | ||
1167 | profile = calloc(nr, sizeof(*profile)); | |
1168 | if (profile == NULL) { | |
1169 | pr_err("failed to allocate memory for the result\n"); | |
1170 | return; | |
1171 | } | |
1172 | ||
1173 | i = 0; | |
1174 | hashmap__for_each_entry(ftrace->profile_hash, entry, bkt) | |
1175 | profile[i++] = entry; | |
1176 | ||
1177 | assert(i == nr); | |
1178 | ||
1179 | //cmp_profile_data(profile[0], profile[1]); | |
1180 | qsort(profile, nr, sizeof(*profile), cmp_profile_data); | |
1181 | ||
1182 | printf("# %10s %10s %10s %10s %s\n", | |
1183 | "Total (us)", "Avg (us)", "Max (us)", "Count", "Function"); | |
1184 | ||
1185 | for (i = 0; i < nr; i++) { | |
1186 | const char *name = profile[i]->pkey; | |
1187 | struct ftrace_profile_data *p = profile[i]->pvalue; | |
1188 | ||
1189 | printf("%12.3f %10.3f %6"PRIu64".%03"PRIu64" %10.0f %s\n", | |
1190 | p->st.n * p->st.mean / 1000, p->st.mean / 1000, | |
1191 | p->st.max / 1000, p->st.max % 1000, p->st.n, name); | |
1192 | } | |
1193 | ||
1194 | free(profile); | |
1195 | ||
1196 | hashmap__for_each_entry(ftrace->profile_hash, entry, bkt) { | |
1197 | free((char *)entry->pkey); | |
1198 | free(entry->pvalue); | |
1199 | } | |
1200 | ||
1201 | hashmap__free(ftrace->profile_hash); | |
1202 | ftrace->profile_hash = NULL; | |
1203 | } | |
1204 | ||
1205 | static int __cmd_profile(struct perf_ftrace *ftrace) | |
1206 | { | |
1207 | char *trace_file; | |
1208 | int trace_fd; | |
1209 | char buf[4096]; | |
1210 | struct io io; | |
1211 | char *line = NULL; | |
1212 | size_t line_len = 0; | |
1213 | ||
1214 | if (prepare_func_profile(ftrace) < 0) { | |
1215 | pr_err("failed to prepare func profiler\n"); | |
1216 | goto out; | |
1217 | } | |
1218 | ||
1219 | if (reset_tracing_files(ftrace) < 0) { | |
1220 | pr_err("failed to reset ftrace\n"); | |
1221 | goto out; | |
1222 | } | |
1223 | ||
1224 | /* reset ftrace buffer */ | |
1225 | if (write_tracing_file("trace", "0") < 0) | |
1226 | goto out; | |
1227 | ||
1228 | if (set_tracing_options(ftrace) < 0) | |
1229 | return -1; | |
1230 | ||
1231 | if (write_tracing_file("current_tracer", ftrace->tracer) < 0) { | |
1232 | pr_err("failed to set current_tracer to %s\n", ftrace->tracer); | |
1233 | goto out_reset; | |
1234 | } | |
1235 | ||
1236 | setup_pager(); | |
1237 | ||
1238 | trace_file = get_tracing_file("trace_pipe"); | |
1239 | if (!trace_file) { | |
1240 | pr_err("failed to open trace_pipe\n"); | |
1241 | goto out_reset; | |
1242 | } | |
1243 | ||
1244 | trace_fd = open(trace_file, O_RDONLY); | |
1245 | ||
1246 | put_tracing_file(trace_file); | |
1247 | ||
1248 | if (trace_fd < 0) { | |
1249 | pr_err("failed to open trace_pipe\n"); | |
1250 | goto out_reset; | |
1251 | } | |
1252 | ||
1253 | fcntl(trace_fd, F_SETFL, O_NONBLOCK); | |
1254 | ||
1255 | if (write_tracing_file("tracing_on", "1") < 0) { | |
1256 | pr_err("can't enable tracing\n"); | |
1257 | goto out_close_fd; | |
1258 | } | |
1259 | ||
1260 | evlist__start_workload(ftrace->evlist); | |
1261 | ||
1262 | io__init(&io, trace_fd, buf, sizeof(buf)); | |
1263 | io.timeout_ms = -1; | |
1264 | ||
1265 | while (!done && !io.eof) { | |
1266 | if (io__getline(&io, &line, &line_len) < 0) | |
1267 | break; | |
1268 | ||
1269 | if (parse_func_duration(ftrace, line, line_len) < 0) | |
1270 | break; | |
1271 | } | |
1272 | ||
1273 | write_tracing_file("tracing_on", "0"); | |
1274 | ||
1275 | if (workload_exec_errno) { | |
1276 | const char *emsg = str_error_r(workload_exec_errno, buf, sizeof(buf)); | |
1277 | /* flush stdout first so below error msg appears at the end. */ | |
1278 | fflush(stdout); | |
1279 | pr_err("workload failed: %s\n", emsg); | |
1280 | goto out_free_line; | |
1281 | } | |
1282 | ||
1283 | /* read remaining buffer contents */ | |
1284 | io.timeout_ms = 0; | |
1285 | while (!io.eof) { | |
1286 | if (io__getline(&io, &line, &line_len) < 0) | |
1287 | break; | |
1288 | ||
1289 | if (parse_func_duration(ftrace, line, line_len) < 0) | |
1290 | break; | |
1291 | } | |
1292 | ||
1293 | print_profile_result(ftrace); | |
1294 | ||
1295 | out_free_line: | |
1296 | free(line); | |
1297 | out_close_fd: | |
1298 | close(trace_fd); | |
1299 | out_reset: | |
1300 | reset_tracing_files(ftrace); | |
1301 | out: | |
1302 | return (done && !workload_exec_errno) ? 0 : -1; | |
1303 | } | |
1304 | ||
b05d1093 TS |
1305 | static int perf_ftrace_config(const char *var, const char *value, void *cb) |
1306 | { | |
1307 | struct perf_ftrace *ftrace = cb; | |
1308 | ||
8e99b6d4 | 1309 | if (!strstarts(var, "ftrace.")) |
b05d1093 TS |
1310 | return 0; |
1311 | ||
1312 | if (strcmp(var, "ftrace.tracer")) | |
1313 | return -1; | |
1314 | ||
1315 | if (!strcmp(value, "function_graph") || | |
1316 | !strcmp(value, "function")) { | |
1317 | ftrace->tracer = value; | |
1318 | return 0; | |
1319 | } | |
1320 | ||
1321 | pr_err("Please select \"function_graph\" (default) or \"function\"\n"); | |
1322 | return -1; | |
1323 | } | |
1324 | ||
2ae05fe0 CD |
1325 | static void list_function_cb(char *str, void *arg) |
1326 | { | |
1327 | struct strfilter *filter = (struct strfilter *)arg; | |
1328 | ||
1329 | if (strfilter__compare(filter, str)) | |
1330 | printf("%s", str); | |
1331 | } | |
1332 | ||
1333 | static int opt_list_avail_functions(const struct option *opt __maybe_unused, | |
1334 | const char *str, int unset) | |
1335 | { | |
1336 | struct strfilter *filter; | |
1337 | const char *err = NULL; | |
1338 | int ret; | |
1339 | ||
1340 | if (unset || !str) | |
1341 | return -1; | |
1342 | ||
1343 | filter = strfilter__new(str, &err); | |
1344 | if (!filter) | |
1345 | return err ? -EINVAL : -ENOMEM; | |
1346 | ||
1347 | ret = strfilter__or(filter, str, &err); | |
1348 | if (ret == -EINVAL) { | |
1349 | pr_err("Filter parse error at %td.\n", err - str + 1); | |
1350 | pr_err("Source: \"%s\"\n", str); | |
1351 | pr_err(" %*c\n", (int)(err - str + 1), '^'); | |
1352 | strfilter__delete(filter); | |
1353 | return ret; | |
1354 | } | |
1355 | ||
1356 | ret = read_tracing_file_by_line("available_filter_functions", | |
1357 | list_function_cb, filter); | |
1358 | strfilter__delete(filter); | |
1359 | if (ret < 0) | |
1360 | return ret; | |
1361 | ||
1362 | exit(0); | |
1363 | } | |
1364 | ||
78b83e8b NK |
1365 | static int parse_filter_func(const struct option *opt, const char *str, |
1366 | int unset __maybe_unused) | |
1367 | { | |
1368 | struct list_head *head = opt->value; | |
1369 | struct filter_entry *entry; | |
1370 | ||
1371 | entry = malloc(sizeof(*entry) + strlen(str) + 1); | |
1372 | if (entry == NULL) | |
1373 | return -ENOMEM; | |
1374 | ||
1375 | strcpy(entry->name, str); | |
1376 | list_add_tail(&entry->list, head); | |
1377 | ||
1378 | return 0; | |
1379 | } | |
1380 | ||
1381 | static void delete_filter_func(struct list_head *head) | |
1382 | { | |
1383 | struct filter_entry *pos, *tmp; | |
1384 | ||
1385 | list_for_each_entry_safe(pos, tmp, head, list) { | |
e56fbc9d | 1386 | list_del_init(&pos->list); |
78b83e8b NK |
1387 | free(pos); |
1388 | } | |
1389 | } | |
1390 | ||
846e1939 CD |
1391 | static int parse_buffer_size(const struct option *opt, |
1392 | const char *str, int unset) | |
1393 | { | |
1394 | unsigned long *s = (unsigned long *)opt->value; | |
1395 | static struct parse_tag tags_size[] = { | |
1396 | { .tag = 'B', .mult = 1 }, | |
1397 | { .tag = 'K', .mult = 1 << 10 }, | |
1398 | { .tag = 'M', .mult = 1 << 20 }, | |
1399 | { .tag = 'G', .mult = 1 << 30 }, | |
1400 | { .tag = 0 }, | |
1401 | }; | |
1402 | unsigned long val; | |
1403 | ||
1404 | if (unset) { | |
1405 | *s = 0; | |
1406 | return 0; | |
1407 | } | |
1408 | ||
1409 | val = parse_tag_value(str, tags_size); | |
1410 | if (val != (unsigned long) -1) { | |
1411 | if (val < 1024) { | |
1412 | pr_err("buffer size too small, must larger than 1KB."); | |
1413 | return -1; | |
1414 | } | |
1415 | *s = val; | |
1416 | return 0; | |
1417 | } | |
1418 | ||
1419 | return -1; | |
1420 | } | |
1421 | ||
b1d84af6 CD |
1422 | static int parse_func_tracer_opts(const struct option *opt, |
1423 | const char *str, int unset) | |
1424 | { | |
1425 | int ret; | |
1426 | struct perf_ftrace *ftrace = (struct perf_ftrace *) opt->value; | |
1427 | struct sublevel_option func_tracer_opts[] = { | |
1428 | { .name = "call-graph", .value_ptr = &ftrace->func_stack_trace }, | |
c81fc34e | 1429 | { .name = "irq-info", .value_ptr = &ftrace->func_irq_info }, |
b1d84af6 CD |
1430 | { .name = NULL, } |
1431 | }; | |
1432 | ||
1433 | if (unset) | |
1434 | return 0; | |
1435 | ||
1436 | ret = perf_parse_sublevel_options(str, func_tracer_opts); | |
1437 | if (ret) | |
1438 | return ret; | |
1439 | ||
1440 | return 0; | |
1441 | } | |
1442 | ||
38988f2e CD |
1443 | static int parse_graph_tracer_opts(const struct option *opt, |
1444 | const char *str, int unset) | |
1445 | { | |
1446 | int ret; | |
1447 | struct perf_ftrace *ftrace = (struct perf_ftrace *) opt->value; | |
1448 | struct sublevel_option graph_tracer_opts[] = { | |
1449 | { .name = "nosleep-time", .value_ptr = &ftrace->graph_nosleep_time }, | |
d1bcf17c | 1450 | { .name = "noirqs", .value_ptr = &ftrace->graph_noirqs }, |
59486fb0 | 1451 | { .name = "verbose", .value_ptr = &ftrace->graph_verbose }, |
00c85d5f | 1452 | { .name = "thresh", .value_ptr = &ftrace->graph_thresh }, |
a8f87a5c | 1453 | { .name = "depth", .value_ptr = &ftrace->graph_depth }, |
c7780089 | 1454 | { .name = "tail", .value_ptr = &ftrace->graph_tail }, |
38988f2e CD |
1455 | { .name = NULL, } |
1456 | }; | |
1457 | ||
1458 | if (unset) | |
1459 | return 0; | |
1460 | ||
1461 | ret = perf_parse_sublevel_options(str, graph_tracer_opts); | |
1462 | if (ret) | |
1463 | return ret; | |
1464 | ||
1465 | return 0; | |
1466 | } | |
1467 | ||
74ae366c NK |
1468 | static int parse_sort_key(const struct option *opt, const char *str, int unset) |
1469 | { | |
1470 | enum perf_ftrace_profile_sort_key *key = (void *)opt->value; | |
1471 | ||
1472 | if (unset) | |
1473 | return 0; | |
1474 | ||
1475 | if (!strcmp(str, "total")) | |
1476 | *key = PFP_SORT_TOTAL; | |
1477 | else if (!strcmp(str, "avg")) | |
1478 | *key = PFP_SORT_AVG; | |
1479 | else if (!strcmp(str, "max")) | |
1480 | *key = PFP_SORT_MAX; | |
1481 | else if (!strcmp(str, "count")) | |
1482 | *key = PFP_SORT_COUNT; | |
1483 | else if (!strcmp(str, "name")) | |
1484 | *key = PFP_SORT_NAME; | |
1485 | else { | |
1486 | pr_err("Unknown sort key: %s\n", str); | |
1487 | return -1; | |
1488 | } | |
1489 | return 0; | |
1490 | } | |
1491 | ||
53be5028 NK |
1492 | enum perf_ftrace_subcommand { |
1493 | PERF_FTRACE_NONE, | |
1494 | PERF_FTRACE_TRACE, | |
1495 | PERF_FTRACE_LATENCY, | |
0f223813 | 1496 | PERF_FTRACE_PROFILE, |
53be5028 NK |
1497 | }; |
1498 | ||
b0ad8ea6 | 1499 | int cmd_ftrace(int argc, const char **argv) |
d01f4e8d NK |
1500 | { |
1501 | int ret; | |
fceb6212 | 1502 | int (*cmd_func)(struct perf_ftrace *) = NULL; |
d01f4e8d | 1503 | struct perf_ftrace ftrace = { |
bf062bd2 | 1504 | .tracer = DEFAULT_TRACER, |
d01f4e8d NK |
1505 | .target = { .uid = UINT_MAX, }, |
1506 | }; | |
416e15ad | 1507 | const struct option common_options[] = { |
a9af6be5 | 1508 | OPT_STRING('p', "pid", &ftrace.target.pid, "pid", |
492e4edb | 1509 | "Trace on existing process id"), |
42145d71 CD |
1510 | /* TODO: Add short option -t after -t/--tracer can be removed. */ |
1511 | OPT_STRING(0, "tid", &ftrace.target.tid, "tid", | |
492e4edb | 1512 | "Trace on existing thread id (exclusive to --pid)"), |
d01f4e8d | 1513 | OPT_INCR('v', "verbose", &verbose, |
492e4edb | 1514 | "Be more verbose"), |
dc231032 | 1515 | OPT_BOOLEAN('a', "all-cpus", &ftrace.target.system_wide, |
492e4edb | 1516 | "System-wide collection from all CPUs"), |
dc231032 | 1517 | OPT_STRING('C', "cpu", &ftrace.target.cpu_list, "cpu", |
492e4edb | 1518 | "List of cpus to monitor"), |
416e15ad NK |
1519 | OPT_END() |
1520 | }; | |
1521 | const struct option ftrace_options[] = { | |
1522 | OPT_STRING('t', "tracer", &ftrace.tracer, "tracer", | |
1523 | "Tracer to use: function_graph(default) or function"), | |
1524 | OPT_CALLBACK_DEFAULT('F', "funcs", NULL, "[FILTER]", | |
1525 | "Show available functions to filter", | |
1526 | opt_list_avail_functions, "*"), | |
78b83e8b | 1527 | OPT_CALLBACK('T', "trace-funcs", &ftrace.filters, "func", |
492e4edb | 1528 | "Trace given functions using function tracer", |
eb6d31ae | 1529 | parse_filter_func), |
78b83e8b | 1530 | OPT_CALLBACK('N', "notrace-funcs", &ftrace.notrace, "func", |
492e4edb | 1531 | "Do not trace given functions", parse_filter_func), |
b1d84af6 | 1532 | OPT_CALLBACK(0, "func-opts", &ftrace, "options", |
492e4edb | 1533 | "Function tracer options, available options: call-graph,irq-info", |
b1d84af6 | 1534 | parse_func_tracer_opts), |
78b83e8b | 1535 | OPT_CALLBACK('G', "graph-funcs", &ftrace.graph_funcs, "func", |
492e4edb | 1536 | "Trace given functions using function_graph tracer", |
eb6d31ae | 1537 | parse_filter_func), |
78b83e8b NK |
1538 | OPT_CALLBACK('g', "nograph-funcs", &ftrace.nograph_funcs, "func", |
1539 | "Set nograph filter on given functions", parse_filter_func), | |
38988f2e | 1540 | OPT_CALLBACK(0, "graph-opts", &ftrace, "options", |
492e4edb | 1541 | "Graph tracer options, available options: nosleep-time,noirqs,verbose,thresh=<n>,depth=<n>", |
38988f2e | 1542 | parse_graph_tracer_opts), |
846e1939 | 1543 | OPT_CALLBACK('m', "buffer-size", &ftrace.percpu_buffer_size, "size", |
492e4edb | 1544 | "Size of per cpu buffer, needs to use a B, K, M or G suffix.", parse_buffer_size), |
5b347472 | 1545 | OPT_BOOLEAN(0, "inherit", &ftrace.inherit, |
492e4edb | 1546 | "Trace children processes"), |
f9f60efb CD |
1547 | OPT_INTEGER('D', "delay", &ftrace.target.initial_delay, |
1548 | "Number of milliseconds to wait before starting tracing after program start"), | |
416e15ad NK |
1549 | OPT_PARENT(common_options), |
1550 | }; | |
53be5028 NK |
1551 | const struct option latency_options[] = { |
1552 | OPT_CALLBACK('T', "trace-funcs", &ftrace.filters, "func", | |
1553 | "Show latency of given function", parse_filter_func), | |
177f4eac NK |
1554 | #ifdef HAVE_BPF_SKEL |
1555 | OPT_BOOLEAN('b', "use-bpf", &ftrace.target.use_bpf, | |
1556 | "Use BPF to measure function latency"), | |
1557 | #endif | |
8d73259e | 1558 | OPT_BOOLEAN('n', "use-nsec", &ftrace.use_nsec, |
84005bb6 | 1559 | "Use nano-second histogram"), |
53be5028 NK |
1560 | OPT_PARENT(common_options), |
1561 | }; | |
0f223813 NK |
1562 | const struct option profile_options[] = { |
1563 | OPT_CALLBACK('T', "trace-funcs", &ftrace.filters, "func", | |
1564 | "Trace given functions using function tracer", | |
1565 | parse_filter_func), | |
1566 | OPT_CALLBACK('N', "notrace-funcs", &ftrace.notrace, "func", | |
1567 | "Do not trace given functions", parse_filter_func), | |
1568 | OPT_CALLBACK('G', "graph-funcs", &ftrace.graph_funcs, "func", | |
1569 | "Trace given functions using function_graph tracer", | |
1570 | parse_filter_func), | |
1571 | OPT_CALLBACK('g', "nograph-funcs", &ftrace.nograph_funcs, "func", | |
1572 | "Set nograph filter on given functions", parse_filter_func), | |
1573 | OPT_CALLBACK('m', "buffer-size", &ftrace.percpu_buffer_size, "size", | |
1574 | "Size of per cpu buffer, needs to use a B, K, M or G suffix.", parse_buffer_size), | |
74ae366c NK |
1575 | OPT_CALLBACK('s', "sort", &profile_sort, "key", |
1576 | "Sort result by key: total (default), avg, max, count, name.", | |
1577 | parse_sort_key), | |
0f223813 NK |
1578 | OPT_PARENT(common_options), |
1579 | }; | |
53be5028 | 1580 | const struct option *options = ftrace_options; |
416e15ad NK |
1581 | |
1582 | const char * const ftrace_usage[] = { | |
1583 | "perf ftrace [<options>] [<command>]", | |
1584 | "perf ftrace [<options>] -- [<command>] [<options>]", | |
0f223813 NK |
1585 | "perf ftrace {trace|latency|profile} [<options>] [<command>]", |
1586 | "perf ftrace {trace|latency|profile} [<options>] -- [<command>] [<options>]", | |
416e15ad | 1587 | NULL |
d01f4e8d | 1588 | }; |
53be5028 | 1589 | enum perf_ftrace_subcommand subcmd = PERF_FTRACE_NONE; |
d01f4e8d | 1590 | |
78b83e8b NK |
1591 | INIT_LIST_HEAD(&ftrace.filters); |
1592 | INIT_LIST_HEAD(&ftrace.notrace); | |
1593 | INIT_LIST_HEAD(&ftrace.graph_funcs); | |
1594 | INIT_LIST_HEAD(&ftrace.nograph_funcs); | |
1595 | ||
a9b8ae8a NK |
1596 | signal(SIGINT, sig_handler); |
1597 | signal(SIGUSR1, sig_handler); | |
1598 | signal(SIGCHLD, sig_handler); | |
1599 | signal(SIGPIPE, sig_handler); | |
1600 | ||
e25ebda7 | 1601 | if (!check_ftrace_capable()) |
608585f4 NK |
1602 | return -1; |
1603 | ||
74298dd8 CD |
1604 | if (!is_ftrace_supported()) { |
1605 | pr_err("ftrace is not supported on this system\n"); | |
1606 | return -ENOTSUP; | |
1607 | } | |
1608 | ||
b05d1093 TS |
1609 | ret = perf_config(perf_ftrace_config, &ftrace); |
1610 | if (ret < 0) | |
1611 | return -1; | |
1612 | ||
53be5028 NK |
1613 | if (argc > 1) { |
1614 | if (!strcmp(argv[1], "trace")) { | |
1615 | subcmd = PERF_FTRACE_TRACE; | |
1616 | } else if (!strcmp(argv[1], "latency")) { | |
1617 | subcmd = PERF_FTRACE_LATENCY; | |
1618 | options = latency_options; | |
0f223813 NK |
1619 | } else if (!strcmp(argv[1], "profile")) { |
1620 | subcmd = PERF_FTRACE_PROFILE; | |
1621 | options = profile_options; | |
53be5028 NK |
1622 | } |
1623 | ||
1624 | if (subcmd != PERF_FTRACE_NONE) { | |
1625 | argc--; | |
1626 | argv++; | |
1627 | } | |
416e15ad | 1628 | } |
53be5028 NK |
1629 | /* for backward compatibility */ |
1630 | if (subcmd == PERF_FTRACE_NONE) | |
1631 | subcmd = PERF_FTRACE_TRACE; | |
416e15ad | 1632 | |
53be5028 | 1633 | argc = parse_options(argc, argv, options, ftrace_usage, |
d01f4e8d | 1634 | PARSE_OPT_STOP_AT_NON_OPTION); |
53be5028 NK |
1635 | if (argc < 0) { |
1636 | ret = -EINVAL; | |
1637 | goto out_delete_filters; | |
1638 | } | |
d01f4e8d | 1639 | |
ecd4960d YJ |
1640 | /* Make system wide (-a) the default target. */ |
1641 | if (!argc && target__none(&ftrace.target)) | |
1642 | ftrace.target.system_wide = true; | |
1643 | ||
fceb6212 CD |
1644 | switch (subcmd) { |
1645 | case PERF_FTRACE_TRACE: | |
fceb6212 CD |
1646 | cmd_func = __cmd_ftrace; |
1647 | break; | |
1648 | case PERF_FTRACE_LATENCY: | |
1649 | if (list_empty(&ftrace.filters)) { | |
1650 | pr_err("Should provide a function to measure\n"); | |
1651 | parse_options_usage(ftrace_usage, options, "T", 1); | |
1652 | ret = -EINVAL; | |
1653 | goto out_delete_filters; | |
1654 | } | |
1655 | cmd_func = __cmd_latency; | |
1656 | break; | |
0f223813 NK |
1657 | case PERF_FTRACE_PROFILE: |
1658 | cmd_func = __cmd_profile; | |
1659 | break; | |
fceb6212 CD |
1660 | case PERF_FTRACE_NONE: |
1661 | default: | |
1662 | pr_err("Invalid subcommand\n"); | |
1663 | ret = -EINVAL; | |
1664 | goto out_delete_filters; | |
1665 | } | |
1666 | ||
a9af6be5 NK |
1667 | ret = target__validate(&ftrace.target); |
1668 | if (ret) { | |
1669 | char errbuf[512]; | |
1670 | ||
1671 | target__strerror(&ftrace.target, ret, errbuf, 512); | |
1672 | pr_err("%s\n", errbuf); | |
78b83e8b | 1673 | goto out_delete_filters; |
a9af6be5 NK |
1674 | } |
1675 | ||
0f98b11c | 1676 | ftrace.evlist = evlist__new(); |
78b83e8b NK |
1677 | if (ftrace.evlist == NULL) { |
1678 | ret = -ENOMEM; | |
1679 | goto out_delete_filters; | |
1680 | } | |
d01f4e8d | 1681 | |
7748bb71 | 1682 | ret = evlist__create_maps(ftrace.evlist, &ftrace.target); |
d01f4e8d NK |
1683 | if (ret < 0) |
1684 | goto out_delete_evlist; | |
1685 | ||
a9b8ae8a NK |
1686 | if (argc) { |
1687 | ret = evlist__prepare_workload(ftrace.evlist, &ftrace.target, | |
1688 | argv, false, | |
1689 | ftrace__workload_exec_failed_signal); | |
1690 | if (ret < 0) | |
1691 | goto out_delete_evlist; | |
1692 | } | |
1693 | ||
fceb6212 | 1694 | ret = cmd_func(&ftrace); |
d01f4e8d NK |
1695 | |
1696 | out_delete_evlist: | |
c12995a5 | 1697 | evlist__delete(ftrace.evlist); |
d01f4e8d | 1698 | |
78b83e8b NK |
1699 | out_delete_filters: |
1700 | delete_filter_func(&ftrace.filters); | |
1701 | delete_filter_func(&ftrace.notrace); | |
1702 | delete_filter_func(&ftrace.graph_funcs); | |
1703 | delete_filter_func(&ftrace.nograph_funcs); | |
1704 | ||
d01f4e8d NK |
1705 | return ret; |
1706 | } |