diff options
| -rw-r--r-- | tools/perf/Documentation/perf-report.txt | 4 | ||||
| -rw-r--r-- | tools/perf/builtin-report.c | 136 |
2 files changed, 138 insertions, 2 deletions
diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index a7d11ef2fe25..856c3c7e94fa 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt | |||
| @@ -461,6 +461,10 @@ include::itrace.txt[] | |||
| 461 | Display overall events statistics without any further processing. | 461 | Display overall events statistics without any further processing. |
| 462 | (like the one at the end of the perf report -D command) | 462 | (like the one at the end of the perf report -D command) |
| 463 | 463 | ||
| 464 | --tasks:: | ||
| 465 | Display monitored tasks stored in perf data. Displaying pid/tid/ppid | ||
| 466 | plus the command string aligned to distinguish parent and child tasks. | ||
| 467 | |||
| 464 | include::callchain-overhead-calculation.txt[] | 468 | include::callchain-overhead-calculation.txt[] |
| 465 | 469 | ||
| 466 | SEE ALSO | 470 | SEE ALSO |
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 8e67a8c25ab1..2c7bd85651dc 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" |
| @@ -63,6 +64,7 @@ struct report { | |||
| 63 | bool inverted_callchain; | 64 | bool inverted_callchain; |
| 64 | bool mem_mode; | 65 | bool mem_mode; |
| 65 | bool stats_mode; | 66 | bool stats_mode; |
| 67 | bool tasks_mode; | ||
| 66 | bool header; | 68 | bool header; |
| 67 | bool header_only; | 69 | bool header_only; |
| 68 | bool nonany_branch_mode; | 70 | bool nonany_branch_mode; |
| @@ -603,6 +605,124 @@ static int stats_print(struct report *rep) | |||
| 603 | return 0; | 605 | return 0; |
| 604 | } | 606 | } |
| 605 | 607 | ||
| 608 | static void tasks_setup(struct report *rep) | ||
| 609 | { | ||
| 610 | memset(&rep->tool, 0, sizeof(rep->tool)); | ||
| 611 | rep->tool.comm = perf_event__process_comm; | ||
| 612 | rep->tool.exit = perf_event__process_exit; | ||
| 613 | rep->tool.fork = perf_event__process_fork; | ||
| 614 | rep->tool.no_warn = true; | ||
| 615 | } | ||
| 616 | |||
| 617 | struct task { | ||
| 618 | struct thread *thread; | ||
| 619 | struct list_head list; | ||
| 620 | struct list_head children; | ||
| 621 | }; | ||
| 622 | |||
| 623 | static struct task *tasks_list(struct task *task, struct machine *machine) | ||
| 624 | { | ||
| 625 | struct thread *parent_thread, *thread = task->thread; | ||
| 626 | struct task *parent_task; | ||
| 627 | |||
| 628 | /* Already listed. */ | ||
| 629 | if (!list_empty(&task->list)) | ||
| 630 | return NULL; | ||
| 631 | |||
| 632 | /* Last one in the chain. */ | ||
| 633 | if (thread->ppid == -1) | ||
| 634 | return task; | ||
| 635 | |||
| 636 | parent_thread = machine__find_thread(machine, -1, thread->ppid); | ||
| 637 | if (!parent_thread) | ||
| 638 | return ERR_PTR(-ENOENT); | ||
| 639 | |||
| 640 | parent_task = thread__priv(parent_thread); | ||
| 641 | list_add_tail(&task->list, &parent_task->children); | ||
| 642 | return tasks_list(parent_task, machine); | ||
| 643 | } | ||
| 644 | |||
| 645 | static void task__print_level(struct task *task, FILE *fp, int level) | ||
| 646 | { | ||
| 647 | struct thread *thread = task->thread; | ||
| 648 | struct task *child; | ||
| 649 | |||
| 650 | fprintf(fp, " %8d %8d %8d |%*s%s\n", | ||
| 651 | thread->pid_, thread->tid, thread->ppid, | ||
| 652 | level, "", thread__comm_str(thread)); | ||
| 653 | |||
| 654 | if (!list_empty(&task->children)) { | ||
| 655 | list_for_each_entry(child, &task->children, list) | ||
| 656 | task__print_level(child, fp, level + 1); | ||
| 657 | } | ||
| 658 | } | ||
| 659 | |||
| 660 | static int tasks_print(struct report *rep, FILE *fp) | ||
| 661 | { | ||
| 662 | struct perf_session *session = rep->session; | ||
| 663 | struct machine *machine = &session->machines.host; | ||
| 664 | struct task *tasks, *task; | ||
| 665 | unsigned int nr = 0, itask = 0, i; | ||
| 666 | struct rb_node *nd; | ||
| 667 | LIST_HEAD(list); | ||
| 668 | |||
| 669 | /* | ||
| 670 | * No locking needed while accessing machine->threads, | ||
| 671 | * because --tasks is single threaded command. | ||
| 672 | */ | ||
| 673 | |||
| 674 | /* Count all the threads. */ | ||
| 675 | for (i = 0; i < THREADS__TABLE_SIZE; i++) | ||
| 676 | nr += machine->threads[i].nr; | ||
| 677 | |||
| 678 | tasks = malloc(sizeof(*tasks) * nr); | ||
| 679 | if (!tasks) | ||
| 680 | return -ENOMEM; | ||
| 681 | |||
| 682 | for (i = 0; i < THREADS__TABLE_SIZE; i++) { | ||
| 683 | struct threads *threads = &machine->threads[i]; | ||
| 684 | |||
| 685 | for (nd = rb_first(&threads->entries); nd; nd = rb_next(nd)) { | ||
| 686 | task = tasks + itask++; | ||
| 687 | |||
| 688 | task->thread = rb_entry(nd, struct thread, rb_node); | ||
| 689 | INIT_LIST_HEAD(&task->children); | ||
| 690 | INIT_LIST_HEAD(&task->list); | ||
| 691 | thread__set_priv(task->thread, task); | ||
| 692 | } | ||
| 693 | } | ||
| 694 | |||
| 695 | /* | ||
| 696 | * Iterate every task down to the unprocessed parent | ||
| 697 | * and link all in task children list. Task with no | ||
| 698 | * parent is added into 'list'. | ||
| 699 | */ | ||
| 700 | for (itask = 0; itask < nr; itask++) { | ||
| 701 | task = tasks + itask; | ||
| 702 | |||
| 703 | if (!list_empty(&task->list)) | ||
| 704 | continue; | ||
| 705 | |||
| 706 | task = tasks_list(task, machine); | ||
| 707 | if (IS_ERR(task)) { | ||
| 708 | pr_err("Error: failed to process tasks\n"); | ||
| 709 | free(tasks); | ||
| 710 | return PTR_ERR(task); | ||
| 711 | } | ||
| 712 | |||
| 713 | if (task) | ||
| 714 | list_add_tail(&task->list, &list); | ||
| 715 | } | ||
| 716 | |||
| 717 | fprintf(fp, "# %8s %8s %8s %s\n", "pid", "tid", "ppid", "comm"); | ||
| 718 | |||
| 719 | list_for_each_entry(task, &list, list) | ||
| 720 | task__print_level(task, fp, 0); | ||
| 721 | |||
| 722 | free(tasks); | ||
| 723 | return 0; | ||
| 724 | } | ||
| 725 | |||
| 606 | static int __cmd_report(struct report *rep) | 726 | static int __cmd_report(struct report *rep) |
| 607 | { | 727 | { |
| 608 | int ret; | 728 | int ret; |
| @@ -637,6 +757,9 @@ static int __cmd_report(struct report *rep) | |||
| 637 | if (rep->stats_mode) | 757 | if (rep->stats_mode) |
| 638 | stats_setup(rep); | 758 | stats_setup(rep); |
| 639 | 759 | ||
| 760 | if (rep->tasks_mode) | ||
| 761 | tasks_setup(rep); | ||
| 762 | |||
| 640 | ret = perf_session__process_events(session); | 763 | ret = perf_session__process_events(session); |
| 641 | if (ret) { | 764 | if (ret) { |
| 642 | ui__error("failed to process sample\n"); | 765 | ui__error("failed to process sample\n"); |
| @@ -646,6 +769,9 @@ static int __cmd_report(struct report *rep) | |||
| 646 | if (rep->stats_mode) | 769 | if (rep->stats_mode) |
| 647 | return stats_print(rep); | 770 | return stats_print(rep); |
| 648 | 771 | ||
| 772 | if (rep->tasks_mode) | ||
| 773 | return tasks_print(rep, stdout); | ||
| 774 | |||
| 649 | report__warn_kptr_restrict(rep); | 775 | report__warn_kptr_restrict(rep); |
| 650 | 776 | ||
| 651 | evlist__for_each_entry(session->evlist, pos) | 777 | evlist__for_each_entry(session->evlist, pos) |
| @@ -803,6 +929,7 @@ int cmd_report(int argc, const char **argv) | |||
| 803 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, | 929 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, |
| 804 | "dump raw trace in ASCII"), | 930 | "dump raw trace in ASCII"), |
| 805 | OPT_BOOLEAN(0, "stats", &report.stats_mode, "Display event stats"), | 931 | OPT_BOOLEAN(0, "stats", &report.stats_mode, "Display event stats"), |
| 932 | OPT_BOOLEAN(0, "tasks", &report.tasks_mode, "Display recorded tasks"), | ||
| 806 | OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, | 933 | OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, |
| 807 | "file", "vmlinux pathname"), | 934 | "file", "vmlinux pathname"), |
| 808 | OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, | 935 | OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, |
| @@ -1064,8 +1191,12 @@ repeat: | |||
| 1064 | report.tool.show_feat_hdr = SHOW_FEAT_HEADER; | 1191 | report.tool.show_feat_hdr = SHOW_FEAT_HEADER; |
| 1065 | if (report.show_full_info) | 1192 | if (report.show_full_info) |
| 1066 | report.tool.show_feat_hdr = SHOW_FEAT_HEADER_FULL_INFO; | 1193 | report.tool.show_feat_hdr = SHOW_FEAT_HEADER_FULL_INFO; |
| 1067 | if (report.stats_mode) | 1194 | if (report.stats_mode || report.tasks_mode) |
| 1068 | use_browser = 0; | 1195 | use_browser = 0; |
| 1196 | if (report.stats_mode && report.tasks_mode) { | ||
| 1197 | pr_err("Error: --tasks and --stats options cannot be used together\n"); | ||
| 1198 | goto error; | ||
| 1199 | } | ||
| 1069 | 1200 | ||
| 1070 | if (strcmp(input_name, "-") != 0) | 1201 | if (strcmp(input_name, "-") != 0) |
| 1071 | setup_browser(true); | 1202 | setup_browser(true); |
| @@ -1088,7 +1219,8 @@ repeat: | |||
| 1088 | ret = 0; | 1219 | ret = 0; |
| 1089 | goto error; | 1220 | goto error; |
| 1090 | } | 1221 | } |
| 1091 | } else if (use_browser == 0 && !quiet && !report.stats_mode) { | 1222 | } else if (use_browser == 0 && !quiet && |
| 1223 | !report.stats_mode && !report.tasks_mode) { | ||
| 1092 | fputs("# To display the perf.data header info, please use --header/--header-only options.\n#\n", | 1224 | fputs("# To display the perf.data header info, please use --header/--header-only options.\n#\n", |
| 1093 | stdout); | 1225 | stdout); |
| 1094 | } | 1226 | } |
