diff options
Diffstat (limited to 'tools/perf/builtin-report.c')
-rw-r--r-- | tools/perf/builtin-report.c | 283 |
1 files changed, 267 insertions, 16 deletions
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index af5dd038195e..42a52dcc41cd 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c | |||
@@ -15,6 +15,7 @@ | |||
15 | #include "util/color.h" | 15 | #include "util/color.h" |
16 | #include <linux/list.h> | 16 | #include <linux/list.h> |
17 | #include <linux/rbtree.h> | 17 | #include <linux/rbtree.h> |
18 | #include <linux/err.h> | ||
18 | #include "util/symbol.h" | 19 | #include "util/symbol.h" |
19 | #include "util/callchain.h" | 20 | #include "util/callchain.h" |
20 | #include "util/values.h" | 21 | #include "util/values.h" |
@@ -51,6 +52,7 @@ | |||
51 | #include <sys/types.h> | 52 | #include <sys/types.h> |
52 | #include <sys/stat.h> | 53 | #include <sys/stat.h> |
53 | #include <unistd.h> | 54 | #include <unistd.h> |
55 | #include <linux/mman.h> | ||
54 | 56 | ||
55 | struct report { | 57 | struct report { |
56 | struct perf_tool tool; | 58 | struct perf_tool tool; |
@@ -60,6 +62,9 @@ struct report { | |||
60 | bool show_threads; | 62 | bool show_threads; |
61 | bool inverted_callchain; | 63 | bool inverted_callchain; |
62 | bool mem_mode; | 64 | bool mem_mode; |
65 | bool stats_mode; | ||
66 | bool tasks_mode; | ||
67 | bool mmaps_mode; | ||
63 | bool header; | 68 | bool header; |
64 | bool header_only; | 69 | bool header_only; |
65 | bool nonany_branch_mode; | 70 | bool nonany_branch_mode; |
@@ -69,7 +74,9 @@ struct report { | |||
69 | const char *cpu_list; | 74 | const char *cpu_list; |
70 | const char *symbol_filter_str; | 75 | const char *symbol_filter_str; |
71 | const char *time_str; | 76 | const char *time_str; |
72 | struct perf_time_interval ptime; | 77 | struct perf_time_interval *ptime_range; |
78 | int range_size; | ||
79 | int range_num; | ||
73 | float min_percent; | 80 | float min_percent; |
74 | u64 nr_entries; | 81 | u64 nr_entries; |
75 | u64 queue_size; | 82 | u64 queue_size; |
@@ -162,12 +169,28 @@ static int hist_iter__branch_callback(struct hist_entry_iter *iter, | |||
162 | struct hist_entry *he = iter->he; | 169 | struct hist_entry *he = iter->he; |
163 | struct report *rep = arg; | 170 | struct report *rep = arg; |
164 | struct branch_info *bi; | 171 | struct branch_info *bi; |
172 | struct perf_sample *sample = iter->sample; | ||
173 | struct perf_evsel *evsel = iter->evsel; | ||
174 | int err; | ||
175 | |||
176 | if (!ui__has_annotation()) | ||
177 | return 0; | ||
178 | |||
179 | hist__account_cycles(sample->branch_stack, al, sample, | ||
180 | rep->nonany_branch_mode); | ||
165 | 181 | ||
166 | bi = he->branch_info; | 182 | bi = he->branch_info; |
183 | err = addr_map_symbol__inc_samples(&bi->from, sample, evsel->idx); | ||
184 | if (err) | ||
185 | goto out; | ||
186 | |||
187 | err = addr_map_symbol__inc_samples(&bi->to, sample, evsel->idx); | ||
188 | |||
167 | branch_type_count(&rep->brtype_stat, &bi->flags, | 189 | branch_type_count(&rep->brtype_stat, &bi->flags, |
168 | bi->from.addr, bi->to.addr); | 190 | bi->from.addr, bi->to.addr); |
169 | 191 | ||
170 | return 0; | 192 | out: |
193 | return err; | ||
171 | } | 194 | } |
172 | 195 | ||
173 | static int process_sample_event(struct perf_tool *tool, | 196 | static int process_sample_event(struct perf_tool *tool, |
@@ -186,8 +209,10 @@ static int process_sample_event(struct perf_tool *tool, | |||
186 | }; | 209 | }; |
187 | int ret = 0; | 210 | int ret = 0; |
188 | 211 | ||
189 | if (perf_time__skip_sample(&rep->ptime, sample->time)) | 212 | if (perf_time__ranges_skip_sample(rep->ptime_range, rep->range_num, |
213 | sample->time)) { | ||
190 | return 0; | 214 | return 0; |
215 | } | ||
191 | 216 | ||
192 | if (machine__resolve(machine, &al, sample) < 0) { | 217 | if (machine__resolve(machine, &al, sample) < 0) { |
193 | pr_debug("problem processing %d event, skipping it.\n", | 218 | pr_debug("problem processing %d event, skipping it.\n", |
@@ -312,9 +337,10 @@ static int report__setup_sample_type(struct report *rep) | |||
312 | 337 | ||
313 | if (symbol_conf.use_callchain || symbol_conf.cumulate_callchain) { | 338 | if (symbol_conf.use_callchain || symbol_conf.cumulate_callchain) { |
314 | if ((sample_type & PERF_SAMPLE_REGS_USER) && | 339 | if ((sample_type & PERF_SAMPLE_REGS_USER) && |
315 | (sample_type & PERF_SAMPLE_STACK_USER)) | 340 | (sample_type & PERF_SAMPLE_STACK_USER)) { |
316 | callchain_param.record_mode = CALLCHAIN_DWARF; | 341 | callchain_param.record_mode = CALLCHAIN_DWARF; |
317 | else if (sample_type & PERF_SAMPLE_BRANCH_STACK) | 342 | dwarf_callchain_users = true; |
343 | } else if (sample_type & PERF_SAMPLE_BRANCH_STACK) | ||
318 | callchain_param.record_mode = CALLCHAIN_LBR; | 344 | callchain_param.record_mode = CALLCHAIN_LBR; |
319 | else | 345 | else |
320 | callchain_param.record_mode = CALLCHAIN_FP; | 346 | callchain_param.record_mode = CALLCHAIN_FP; |
@@ -377,6 +403,9 @@ static size_t hists__fprintf_nr_sample_events(struct hists *hists, struct report | |||
377 | if (evname != NULL) | 403 | if (evname != NULL) |
378 | ret += fprintf(fp, " of event '%s'", evname); | 404 | ret += fprintf(fp, " of event '%s'", evname); |
379 | 405 | ||
406 | if (rep->time_str) | ||
407 | ret += fprintf(fp, " (time slices: %s)", rep->time_str); | ||
408 | |||
380 | if (symbol_conf.show_ref_callgraph && | 409 | if (symbol_conf.show_ref_callgraph && |
381 | strstr(evname, "call-graph=no")) { | 410 | strstr(evname, "call-graph=no")) { |
382 | ret += fprintf(fp, ", show reference callgraph"); | 411 | ret += fprintf(fp, ", show reference callgraph"); |
@@ -567,6 +596,174 @@ static void report__output_resort(struct report *rep) | |||
567 | ui_progress__finish(); | 596 | ui_progress__finish(); |
568 | } | 597 | } |
569 | 598 | ||
599 | static void stats_setup(struct report *rep) | ||
600 | { | ||
601 | memset(&rep->tool, 0, sizeof(rep->tool)); | ||
602 | rep->tool.no_warn = true; | ||
603 | } | ||
604 | |||
605 | static int stats_print(struct report *rep) | ||
606 | { | ||
607 | struct perf_session *session = rep->session; | ||
608 | |||
609 | perf_session__fprintf_nr_events(session, stdout); | ||
610 | return 0; | ||
611 | } | ||
612 | |||
613 | static void tasks_setup(struct report *rep) | ||
614 | { | ||
615 | memset(&rep->tool, 0, sizeof(rep->tool)); | ||
616 | if (rep->mmaps_mode) { | ||
617 | rep->tool.mmap = perf_event__process_mmap; | ||
618 | rep->tool.mmap2 = perf_event__process_mmap2; | ||
619 | } | ||
620 | rep->tool.comm = perf_event__process_comm; | ||
621 | rep->tool.exit = perf_event__process_exit; | ||
622 | rep->tool.fork = perf_event__process_fork; | ||
623 | rep->tool.no_warn = true; | ||
624 | } | ||
625 | |||
626 | struct task { | ||
627 | struct thread *thread; | ||
628 | struct list_head list; | ||
629 | struct list_head children; | ||
630 | }; | ||
631 | |||
632 | static struct task *tasks_list(struct task *task, struct machine *machine) | ||
633 | { | ||
634 | struct thread *parent_thread, *thread = task->thread; | ||
635 | struct task *parent_task; | ||
636 | |||
637 | /* Already listed. */ | ||
638 | if (!list_empty(&task->list)) | ||
639 | return NULL; | ||
640 | |||
641 | /* Last one in the chain. */ | ||
642 | if (thread->ppid == -1) | ||
643 | return task; | ||
644 | |||
645 | parent_thread = machine__find_thread(machine, -1, thread->ppid); | ||
646 | if (!parent_thread) | ||
647 | return ERR_PTR(-ENOENT); | ||
648 | |||
649 | parent_task = thread__priv(parent_thread); | ||
650 | list_add_tail(&task->list, &parent_task->children); | ||
651 | return tasks_list(parent_task, machine); | ||
652 | } | ||
653 | |||
654 | static size_t maps__fprintf_task(struct maps *maps, int indent, FILE *fp) | ||
655 | { | ||
656 | size_t printed = 0; | ||
657 | struct rb_node *nd; | ||
658 | |||
659 | for (nd = rb_first(&maps->entries); nd; nd = rb_next(nd)) { | ||
660 | struct map *map = rb_entry(nd, struct map, rb_node); | ||
661 | |||
662 | printed += fprintf(fp, "%*s %" PRIx64 "-%" PRIx64 " %c%c%c%c %08" PRIx64 " %" PRIu64 " %s\n", | ||
663 | indent, "", map->start, map->end, | ||
664 | map->prot & PROT_READ ? 'r' : '-', | ||
665 | map->prot & PROT_WRITE ? 'w' : '-', | ||
666 | map->prot & PROT_EXEC ? 'x' : '-', | ||
667 | map->flags & MAP_SHARED ? 's' : 'p', | ||
668 | map->pgoff, | ||
669 | map->ino, map->dso->name); | ||
670 | } | ||
671 | |||
672 | return printed; | ||
673 | } | ||
674 | |||
675 | static int map_groups__fprintf_task(struct map_groups *mg, int indent, FILE *fp) | ||
676 | { | ||
677 | int printed = 0, i; | ||
678 | for (i = 0; i < MAP__NR_TYPES; ++i) | ||
679 | printed += maps__fprintf_task(&mg->maps[i], indent, fp); | ||
680 | return printed; | ||
681 | } | ||
682 | |||
683 | static void task__print_level(struct task *task, FILE *fp, int level) | ||
684 | { | ||
685 | struct thread *thread = task->thread; | ||
686 | struct task *child; | ||
687 | int comm_indent = fprintf(fp, " %8d %8d %8d |%*s", | ||
688 | thread->pid_, thread->tid, thread->ppid, | ||
689 | level, ""); | ||
690 | |||
691 | fprintf(fp, "%s\n", thread__comm_str(thread)); | ||
692 | |||
693 | map_groups__fprintf_task(thread->mg, comm_indent, fp); | ||
694 | |||
695 | if (!list_empty(&task->children)) { | ||
696 | list_for_each_entry(child, &task->children, list) | ||
697 | task__print_level(child, fp, level + 1); | ||
698 | } | ||
699 | } | ||
700 | |||
701 | static int tasks_print(struct report *rep, FILE *fp) | ||
702 | { | ||
703 | struct perf_session *session = rep->session; | ||
704 | struct machine *machine = &session->machines.host; | ||
705 | struct task *tasks, *task; | ||
706 | unsigned int nr = 0, itask = 0, i; | ||
707 | struct rb_node *nd; | ||
708 | LIST_HEAD(list); | ||
709 | |||
710 | /* | ||
711 | * No locking needed while accessing machine->threads, | ||
712 | * because --tasks is single threaded command. | ||
713 | */ | ||
714 | |||
715 | /* Count all the threads. */ | ||
716 | for (i = 0; i < THREADS__TABLE_SIZE; i++) | ||
717 | nr += machine->threads[i].nr; | ||
718 | |||
719 | tasks = malloc(sizeof(*tasks) * nr); | ||
720 | if (!tasks) | ||
721 | return -ENOMEM; | ||
722 | |||
723 | for (i = 0; i < THREADS__TABLE_SIZE; i++) { | ||
724 | struct threads *threads = &machine->threads[i]; | ||
725 | |||
726 | for (nd = rb_first(&threads->entries); nd; nd = rb_next(nd)) { | ||
727 | task = tasks + itask++; | ||
728 | |||
729 | task->thread = rb_entry(nd, struct thread, rb_node); | ||
730 | INIT_LIST_HEAD(&task->children); | ||
731 | INIT_LIST_HEAD(&task->list); | ||
732 | thread__set_priv(task->thread, task); | ||
733 | } | ||
734 | } | ||
735 | |||
736 | /* | ||
737 | * Iterate every task down to the unprocessed parent | ||
738 | * and link all in task children list. Task with no | ||
739 | * parent is added into 'list'. | ||
740 | */ | ||
741 | for (itask = 0; itask < nr; itask++) { | ||
742 | task = tasks + itask; | ||
743 | |||
744 | if (!list_empty(&task->list)) | ||
745 | continue; | ||
746 | |||
747 | task = tasks_list(task, machine); | ||
748 | if (IS_ERR(task)) { | ||
749 | pr_err("Error: failed to process tasks\n"); | ||
750 | free(tasks); | ||
751 | return PTR_ERR(task); | ||
752 | } | ||
753 | |||
754 | if (task) | ||
755 | list_add_tail(&task->list, &list); | ||
756 | } | ||
757 | |||
758 | fprintf(fp, "# %8s %8s %8s %s\n", "pid", "tid", "ppid", "comm"); | ||
759 | |||
760 | list_for_each_entry(task, &list, list) | ||
761 | task__print_level(task, fp, 0); | ||
762 | |||
763 | free(tasks); | ||
764 | return 0; | ||
765 | } | ||
766 | |||
570 | static int __cmd_report(struct report *rep) | 767 | static int __cmd_report(struct report *rep) |
571 | { | 768 | { |
572 | int ret; | 769 | int ret; |
@@ -598,12 +795,24 @@ static int __cmd_report(struct report *rep) | |||
598 | return ret; | 795 | return ret; |
599 | } | 796 | } |
600 | 797 | ||
798 | if (rep->stats_mode) | ||
799 | stats_setup(rep); | ||
800 | |||
801 | if (rep->tasks_mode) | ||
802 | tasks_setup(rep); | ||
803 | |||
601 | ret = perf_session__process_events(session); | 804 | ret = perf_session__process_events(session); |
602 | if (ret) { | 805 | if (ret) { |
603 | ui__error("failed to process sample\n"); | 806 | ui__error("failed to process sample\n"); |
604 | return ret; | 807 | return ret; |
605 | } | 808 | } |
606 | 809 | ||
810 | if (rep->stats_mode) | ||
811 | return stats_print(rep); | ||
812 | |||
813 | if (rep->tasks_mode) | ||
814 | return tasks_print(rep, stdout); | ||
815 | |||
607 | report__warn_kptr_restrict(rep); | 816 | report__warn_kptr_restrict(rep); |
608 | 817 | ||
609 | evlist__for_each_entry(session->evlist, pos) | 818 | evlist__for_each_entry(session->evlist, pos) |
@@ -760,6 +969,9 @@ int cmd_report(int argc, const char **argv) | |||
760 | OPT_BOOLEAN('q', "quiet", &quiet, "Do not show any message"), | 969 | OPT_BOOLEAN('q', "quiet", &quiet, "Do not show any message"), |
761 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, | 970 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, |
762 | "dump raw trace in ASCII"), | 971 | "dump raw trace in ASCII"), |
972 | OPT_BOOLEAN(0, "stats", &report.stats_mode, "Display event stats"), | ||
973 | OPT_BOOLEAN(0, "tasks", &report.tasks_mode, "Display recorded tasks"), | ||
974 | OPT_BOOLEAN(0, "mmaps", &report.mmaps_mode, "Display recorded tasks memory maps"), | ||
763 | OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, | 975 | OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, |
764 | "file", "vmlinux pathname"), | 976 | "file", "vmlinux pathname"), |
765 | OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, | 977 | OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, |
@@ -907,6 +1119,9 @@ int cmd_report(int argc, const char **argv) | |||
907 | report.symbol_filter_str = argv[0]; | 1119 | report.symbol_filter_str = argv[0]; |
908 | } | 1120 | } |
909 | 1121 | ||
1122 | if (report.mmaps_mode) | ||
1123 | report.tasks_mode = true; | ||
1124 | |||
910 | if (quiet) | 1125 | if (quiet) |
911 | perf_quiet_option(); | 1126 | perf_quiet_option(); |
912 | 1127 | ||
@@ -921,13 +1136,6 @@ int cmd_report(int argc, const char **argv) | |||
921 | return -EINVAL; | 1136 | return -EINVAL; |
922 | } | 1137 | } |
923 | 1138 | ||
924 | if (report.use_stdio) | ||
925 | use_browser = 0; | ||
926 | else if (report.use_tui) | ||
927 | use_browser = 1; | ||
928 | else if (report.use_gtk) | ||
929 | use_browser = 2; | ||
930 | |||
931 | if (report.inverted_callchain) | 1139 | if (report.inverted_callchain) |
932 | callchain_param.order = ORDER_CALLER; | 1140 | callchain_param.order = ORDER_CALLER; |
933 | if (symbol_conf.cumulate_callchain && !callchain_param.order_set) | 1141 | if (symbol_conf.cumulate_callchain && !callchain_param.order_set) |
@@ -1014,6 +1222,13 @@ repeat: | |||
1014 | perf_hpp_list.need_collapse = true; | 1222 | perf_hpp_list.need_collapse = true; |
1015 | } | 1223 | } |
1016 | 1224 | ||
1225 | if (report.use_stdio) | ||
1226 | use_browser = 0; | ||
1227 | else if (report.use_tui) | ||
1228 | use_browser = 1; | ||
1229 | else if (report.use_gtk) | ||
1230 | use_browser = 2; | ||
1231 | |||
1017 | /* Force tty output for header output and per-thread stat. */ | 1232 | /* Force tty output for header output and per-thread stat. */ |
1018 | if (report.header || report.header_only || report.show_threads) | 1233 | if (report.header || report.header_only || report.show_threads) |
1019 | use_browser = 0; | 1234 | use_browser = 0; |
@@ -1021,6 +1236,12 @@ repeat: | |||
1021 | report.tool.show_feat_hdr = SHOW_FEAT_HEADER; | 1236 | report.tool.show_feat_hdr = SHOW_FEAT_HEADER; |
1022 | if (report.show_full_info) | 1237 | if (report.show_full_info) |
1023 | report.tool.show_feat_hdr = SHOW_FEAT_HEADER_FULL_INFO; | 1238 | report.tool.show_feat_hdr = SHOW_FEAT_HEADER_FULL_INFO; |
1239 | if (report.stats_mode || report.tasks_mode) | ||
1240 | use_browser = 0; | ||
1241 | if (report.stats_mode && report.tasks_mode) { | ||
1242 | pr_err("Error: --tasks and --mmaps can't be used together with --stats\n"); | ||
1243 | goto error; | ||
1244 | } | ||
1024 | 1245 | ||
1025 | if (strcmp(input_name, "-") != 0) | 1246 | if (strcmp(input_name, "-") != 0) |
1026 | setup_browser(true); | 1247 | setup_browser(true); |
@@ -1043,7 +1264,8 @@ repeat: | |||
1043 | ret = 0; | 1264 | ret = 0; |
1044 | goto error; | 1265 | goto error; |
1045 | } | 1266 | } |
1046 | } else if (use_browser == 0 && !quiet) { | 1267 | } else if (use_browser == 0 && !quiet && |
1268 | !report.stats_mode && !report.tasks_mode) { | ||
1047 | fputs("# To display the perf.data header info, please use --header/--header-only options.\n#\n", | 1269 | fputs("# To display the perf.data header info, please use --header/--header-only options.\n#\n", |
1048 | stdout); | 1270 | stdout); |
1049 | } | 1271 | } |
@@ -1077,9 +1299,36 @@ repeat: | |||
1077 | if (symbol__init(&session->header.env) < 0) | 1299 | if (symbol__init(&session->header.env) < 0) |
1078 | goto error; | 1300 | goto error; |
1079 | 1301 | ||
1080 | if (perf_time__parse_str(&report.ptime, report.time_str) != 0) { | 1302 | report.ptime_range = perf_time__range_alloc(report.time_str, |
1081 | pr_err("Invalid time string\n"); | 1303 | &report.range_size); |
1082 | return -EINVAL; | 1304 | if (!report.ptime_range) { |
1305 | ret = -ENOMEM; | ||
1306 | goto error; | ||
1307 | } | ||
1308 | |||
1309 | if (perf_time__parse_str(report.ptime_range, report.time_str) != 0) { | ||
1310 | if (session->evlist->first_sample_time == 0 && | ||
1311 | session->evlist->last_sample_time == 0) { | ||
1312 | pr_err("HINT: no first/last sample time found in perf data.\n" | ||
1313 | "Please use latest perf binary to execute 'perf record'\n" | ||
1314 | "(if '--buildid-all' is enabled, please set '--timestamp-boundary').\n"); | ||
1315 | ret = -EINVAL; | ||
1316 | goto error; | ||
1317 | } | ||
1318 | |||
1319 | report.range_num = perf_time__percent_parse_str( | ||
1320 | report.ptime_range, report.range_size, | ||
1321 | report.time_str, | ||
1322 | session->evlist->first_sample_time, | ||
1323 | session->evlist->last_sample_time); | ||
1324 | |||
1325 | if (report.range_num < 0) { | ||
1326 | pr_err("Invalid time string\n"); | ||
1327 | ret = -EINVAL; | ||
1328 | goto error; | ||
1329 | } | ||
1330 | } else { | ||
1331 | report.range_num = 1; | ||
1083 | } | 1332 | } |
1084 | 1333 | ||
1085 | sort__setup_elide(stdout); | 1334 | sort__setup_elide(stdout); |
@@ -1092,6 +1341,8 @@ repeat: | |||
1092 | ret = 0; | 1341 | ret = 0; |
1093 | 1342 | ||
1094 | error: | 1343 | error: |
1344 | zfree(&report.ptime_range); | ||
1345 | |||
1095 | perf_session__delete(session); | 1346 | perf_session__delete(session); |
1096 | return ret; | 1347 | return ret; |
1097 | } | 1348 | } |