diff options
author | Frederic Weisbecker <fweisbec@gmail.com> | 2009-07-02 11:58:21 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-07-02 14:47:15 -0400 |
commit | 4eb3e4788b8a5e220a0aeb590f88c28850726ebe (patch) | |
tree | 537392177078684b87197d358cf2104e90ec6d66 /tools/perf | |
parent | 5a4b181721375700124513cdd9f97056e1c66675 (diff) |
perf report: Add support for callchain graph output
Currently, the printing of callchains is done in a single
vertical level, this is the "flat" mode:
8.25% [k] copy_user_generic_string
4.19%
copy_user_generic_string
generic_file_aio_read
do_sync_read
vfs_read
sys_pread64
system_call_fastpath
pread64
This patch introduces a new "graph" mode which provides a
hierarchical output of factorized paths recursively sorted:
8.25% [k] copy_user_generic_string
|
|--4.31%-- generic_file_aio_read
| do_sync_read
| vfs_read
| |
| |--4.19%-- sys_pread64
| | system_call_fastpath
| | pread64
| |
| --0.12%-- sys_read
| system_call_fastpath
| __read
|
|--3.24%-- generic_file_buffered_write
| __generic_file_aio_write_nolock
| generic_file_aio_write
| do_sync_write
| reiserfs_file_write
| vfs_write
| |
| |--3.14%-- sys_pwrite64
| | system_call_fastpath
| | __pwrite64
| |
| --0.10%-- sys_write
[...]
The command line has then changed.
By providing the -c option, the callchain will output in the
flat mode by default.
But you can override it:
perf report -c graph
or
perf report -c flat
You can also pass the abreviated mode:
perf report -c g
or
perf report -c gra
will both make use of the graph mode.
Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Anton Blanchard <anton@samba.org>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
LKML-Reference: <1246550301-8954-3-git-send-email-fweisbec@gmail.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'tools/perf')
-rw-r--r-- | tools/perf/builtin-report.c | 141 | ||||
-rw-r--r-- | tools/perf/util/callchain.c | 51 | ||||
-rw-r--r-- | tools/perf/util/callchain.h | 11 |
3 files changed, 185 insertions, 18 deletions
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index b44476ca2398..0ca46386d936 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c | |||
@@ -59,6 +59,7 @@ static regex_t parent_regex; | |||
59 | 59 | ||
60 | static int exclude_other = 1; | 60 | static int exclude_other = 1; |
61 | static int callchain; | 61 | static int callchain; |
62 | static enum chain_mode callchain_mode; | ||
62 | 63 | ||
63 | static u64 sample_type; | 64 | static u64 sample_type; |
64 | 65 | ||
@@ -787,8 +788,103 @@ hist_entry__collapse(struct hist_entry *left, struct hist_entry *right) | |||
787 | return cmp; | 788 | return cmp; |
788 | } | 789 | } |
789 | 790 | ||
791 | static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask) | ||
792 | { | ||
793 | int i; | ||
794 | size_t ret = 0; | ||
795 | |||
796 | ret += fprintf(fp, "%s", " "); | ||
797 | |||
798 | for (i = 0; i < depth; i++) | ||
799 | if (depth_mask & (1 << i)) | ||
800 | ret += fprintf(fp, "| "); | ||
801 | else | ||
802 | ret += fprintf(fp, " "); | ||
803 | |||
804 | ret += fprintf(fp, "\n"); | ||
805 | |||
806 | return ret; | ||
807 | } | ||
790 | static size_t | 808 | static size_t |
791 | callchain__fprintf(FILE *fp, struct callchain_node *self, u64 total_samples) | 809 | ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, int depth, |
810 | int depth_mask, int count, u64 total_samples, | ||
811 | int hits) | ||
812 | { | ||
813 | int i; | ||
814 | size_t ret = 0; | ||
815 | |||
816 | ret += fprintf(fp, "%s", " "); | ||
817 | for (i = 0; i < depth; i++) { | ||
818 | if (depth_mask & (1 << i)) | ||
819 | ret += fprintf(fp, "|"); | ||
820 | else | ||
821 | ret += fprintf(fp, " "); | ||
822 | if (!count && i == depth - 1) { | ||
823 | double percent; | ||
824 | |||
825 | percent = hits * 100.0 / total_samples; | ||
826 | ret += fprintf(fp, "--%2.2f%%-- ", percent); | ||
827 | } else | ||
828 | ret += fprintf(fp, "%s", " "); | ||
829 | } | ||
830 | if (chain->sym) | ||
831 | ret += fprintf(fp, "%s\n", chain->sym->name); | ||
832 | else | ||
833 | ret += fprintf(fp, "%p\n", (void *)(long)chain->ip); | ||
834 | |||
835 | return ret; | ||
836 | } | ||
837 | |||
838 | static size_t | ||
839 | callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | ||
840 | u64 total_samples, int depth, int depth_mask) | ||
841 | { | ||
842 | struct rb_node *node, *next; | ||
843 | struct callchain_node *child; | ||
844 | struct callchain_list *chain; | ||
845 | int new_depth_mask = depth_mask; | ||
846 | size_t ret = 0; | ||
847 | int i; | ||
848 | |||
849 | node = rb_first(&self->rb_root); | ||
850 | while (node) { | ||
851 | child = rb_entry(node, struct callchain_node, rb_node); | ||
852 | |||
853 | /* | ||
854 | * The depth mask manages the output of pipes that show | ||
855 | * the depth. We don't want to keep the pipes of the current | ||
856 | * level for the last child of this depth | ||
857 | */ | ||
858 | next = rb_next(node); | ||
859 | if (!next) | ||
860 | new_depth_mask &= ~(1 << (depth - 1)); | ||
861 | |||
862 | /* | ||
863 | * But we keep the older depth mask for the line seperator | ||
864 | * to keep the level link until we reach the last child | ||
865 | */ | ||
866 | ret += ipchain__fprintf_graph_line(fp, depth, depth_mask); | ||
867 | i = 0; | ||
868 | list_for_each_entry(chain, &child->val, list) { | ||
869 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
870 | continue; | ||
871 | ret += ipchain__fprintf_graph(fp, chain, depth, | ||
872 | new_depth_mask, i++, | ||
873 | total_samples, | ||
874 | child->cumul_hit); | ||
875 | } | ||
876 | ret += callchain__fprintf_graph(fp, child, total_samples, | ||
877 | depth + 1, | ||
878 | new_depth_mask | (1 << depth)); | ||
879 | node = next; | ||
880 | } | ||
881 | |||
882 | return ret; | ||
883 | } | ||
884 | |||
885 | static size_t | ||
886 | callchain__fprintf_flat(FILE *fp, struct callchain_node *self, | ||
887 | u64 total_samples) | ||
792 | { | 888 | { |
793 | struct callchain_list *chain; | 889 | struct callchain_list *chain; |
794 | size_t ret = 0; | 890 | size_t ret = 0; |
@@ -796,7 +892,7 @@ callchain__fprintf(FILE *fp, struct callchain_node *self, u64 total_samples) | |||
796 | if (!self) | 892 | if (!self) |
797 | return 0; | 893 | return 0; |
798 | 894 | ||
799 | ret += callchain__fprintf(fp, self->parent, total_samples); | 895 | ret += callchain__fprintf_flat(fp, self->parent, total_samples); |
800 | 896 | ||
801 | 897 | ||
802 | list_for_each_entry(chain, &self->val, list) { | 898 | list_for_each_entry(chain, &self->val, list) { |
@@ -826,8 +922,13 @@ hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, | |||
826 | 922 | ||
827 | chain = rb_entry(rb_node, struct callchain_node, rb_node); | 923 | chain = rb_entry(rb_node, struct callchain_node, rb_node); |
828 | percent = chain->hit * 100.0 / total_samples; | 924 | percent = chain->hit * 100.0 / total_samples; |
829 | ret += fprintf(fp, " %6.2f%%\n", percent); | 925 | if (callchain_mode == FLAT) { |
830 | ret += callchain__fprintf(fp, chain, total_samples); | 926 | ret += fprintf(fp, " %6.2f%%\n", percent); |
927 | ret += callchain__fprintf_flat(fp, chain, total_samples); | ||
928 | } else if (callchain_mode == GRAPH) { | ||
929 | ret += callchain__fprintf_graph(fp, chain, | ||
930 | total_samples, 1, 1); | ||
931 | } | ||
831 | ret += fprintf(fp, "\n"); | 932 | ret += fprintf(fp, "\n"); |
832 | rb_node = rb_next(rb_node); | 933 | rb_node = rb_next(rb_node); |
833 | } | 934 | } |
@@ -1129,8 +1230,12 @@ static void output__insert_entry(struct hist_entry *he) | |||
1129 | struct rb_node *parent = NULL; | 1230 | struct rb_node *parent = NULL; |
1130 | struct hist_entry *iter; | 1231 | struct hist_entry *iter; |
1131 | 1232 | ||
1132 | if (callchain) | 1233 | if (callchain) { |
1133 | sort_chain_to_rbtree(&he->sorted_chain, &he->callchain); | 1234 | if (callchain_mode == FLAT) |
1235 | sort_chain_flat(&he->sorted_chain, &he->callchain); | ||
1236 | else if (callchain_mode == GRAPH) | ||
1237 | sort_chain_graph(&he->sorted_chain, &he->callchain); | ||
1238 | } | ||
1134 | 1239 | ||
1135 | while (*p != NULL) { | 1240 | while (*p != NULL) { |
1136 | parent = *p; | 1241 | parent = *p; |
@@ -1702,6 +1807,26 @@ done: | |||
1702 | return rc; | 1807 | return rc; |
1703 | } | 1808 | } |
1704 | 1809 | ||
1810 | static int | ||
1811 | parse_callchain_opt(const struct option *opt __used, const char *arg, | ||
1812 | int unset __used) | ||
1813 | { | ||
1814 | callchain = 1; | ||
1815 | |||
1816 | if (!arg) | ||
1817 | return 0; | ||
1818 | |||
1819 | if (!strncmp(arg, "graph", strlen(arg))) | ||
1820 | callchain_mode = GRAPH; | ||
1821 | |||
1822 | else if (!strncmp(arg, "flat", strlen(arg))) | ||
1823 | callchain_mode = FLAT; | ||
1824 | else | ||
1825 | return -1; | ||
1826 | |||
1827 | return 0; | ||
1828 | } | ||
1829 | |||
1705 | static const char * const report_usage[] = { | 1830 | static const char * const report_usage[] = { |
1706 | "perf report [<options>] <command>", | 1831 | "perf report [<options>] <command>", |
1707 | NULL | 1832 | NULL |
@@ -1725,7 +1850,9 @@ static const struct option options[] = { | |||
1725 | "regex filter to identify parent, see: '--sort parent'"), | 1850 | "regex filter to identify parent, see: '--sort parent'"), |
1726 | OPT_BOOLEAN('x', "exclude-other", &exclude_other, | 1851 | OPT_BOOLEAN('x', "exclude-other", &exclude_other, |
1727 | "Only display entries with parent-match"), | 1852 | "Only display entries with parent-match"), |
1728 | OPT_BOOLEAN('c', "callchain", &callchain, "Display callchains"), | 1853 | OPT_CALLBACK_DEFAULT('c', "callchain", NULL, "output_type", |
1854 | "Display callchains with output_type: flat, graph. " | ||
1855 | "Default to flat", &parse_callchain_opt, "flat"), | ||
1729 | OPT_STRING('d', "dsos", &dso_list_str, "dso[,dso...]", | 1856 | OPT_STRING('d', "dsos", &dso_list_str, "dso[,dso...]", |
1730 | "only consider symbols in these dsos"), | 1857 | "only consider symbols in these dsos"), |
1731 | OPT_STRING('C', "comms", &comm_list_str, "comm[,comm...]", | 1858 | OPT_STRING('C', "comms", &comm_list_str, "comm[,comm...]", |
diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 3c4a91fea622..a9873aafcd92 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c | |||
@@ -19,9 +19,9 @@ | |||
19 | #define chain_for_each_child(child, parent) \ | 19 | #define chain_for_each_child(child, parent) \ |
20 | list_for_each_entry(child, &parent->children, brothers) | 20 | list_for_each_entry(child, &parent->children, brothers) |
21 | 21 | ||
22 | |||
23 | static void | 22 | static void |
24 | rb_insert_callchain(struct rb_root *root, struct callchain_node *chain) | 23 | rb_insert_callchain(struct rb_root *root, struct callchain_node *chain, |
24 | enum chain_mode mode) | ||
25 | { | 25 | { |
26 | struct rb_node **p = &root->rb_node; | 26 | struct rb_node **p = &root->rb_node; |
27 | struct rb_node *parent = NULL; | 27 | struct rb_node *parent = NULL; |
@@ -31,10 +31,22 @@ rb_insert_callchain(struct rb_root *root, struct callchain_node *chain) | |||
31 | parent = *p; | 31 | parent = *p; |
32 | rnode = rb_entry(parent, struct callchain_node, rb_node); | 32 | rnode = rb_entry(parent, struct callchain_node, rb_node); |
33 | 33 | ||
34 | if (rnode->hit < chain->hit) | 34 | switch (mode) { |
35 | p = &(*p)->rb_left; | 35 | case FLAT: |
36 | else | 36 | if (rnode->hit < chain->hit) |
37 | p = &(*p)->rb_right; | 37 | p = &(*p)->rb_left; |
38 | else | ||
39 | p = &(*p)->rb_right; | ||
40 | break; | ||
41 | case GRAPH: | ||
42 | if (rnode->cumul_hit < chain->cumul_hit) | ||
43 | p = &(*p)->rb_left; | ||
44 | else | ||
45 | p = &(*p)->rb_right; | ||
46 | break; | ||
47 | default: | ||
48 | break; | ||
49 | } | ||
38 | } | 50 | } |
39 | 51 | ||
40 | rb_link_node(&chain->rb_node, parent, p); | 52 | rb_link_node(&chain->rb_node, parent, p); |
@@ -45,15 +57,36 @@ rb_insert_callchain(struct rb_root *root, struct callchain_node *chain) | |||
45 | * Once we get every callchains from the stream, we can now | 57 | * Once we get every callchains from the stream, we can now |
46 | * sort them by hit | 58 | * sort them by hit |
47 | */ | 59 | */ |
48 | void sort_chain_to_rbtree(struct rb_root *rb_root, struct callchain_node *node) | 60 | void sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node) |
49 | { | 61 | { |
50 | struct callchain_node *child; | 62 | struct callchain_node *child; |
51 | 63 | ||
52 | chain_for_each_child(child, node) | 64 | chain_for_each_child(child, node) |
53 | sort_chain_to_rbtree(rb_root, child); | 65 | sort_chain_flat(rb_root, child); |
54 | 66 | ||
55 | if (node->hit) | 67 | if (node->hit) |
56 | rb_insert_callchain(rb_root, node); | 68 | rb_insert_callchain(rb_root, node, FLAT); |
69 | } | ||
70 | |||
71 | static void __sort_chain_graph(struct callchain_node *node) | ||
72 | { | ||
73 | struct callchain_node *child; | ||
74 | |||
75 | node->rb_root = RB_ROOT; | ||
76 | node->cumul_hit = node->hit; | ||
77 | |||
78 | chain_for_each_child(child, node) { | ||
79 | __sort_chain_graph(child); | ||
80 | rb_insert_callchain(&node->rb_root, child, GRAPH); | ||
81 | node->cumul_hit += child->cumul_hit; | ||
82 | } | ||
83 | } | ||
84 | |||
85 | void | ||
86 | sort_chain_graph(struct rb_root *rb_root, struct callchain_node *chain_root) | ||
87 | { | ||
88 | __sort_chain_graph(chain_root); | ||
89 | rb_root->rb_node = chain_root->rb_root.rb_node; | ||
57 | } | 90 | } |
58 | 91 | ||
59 | /* | 92 | /* |
diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index e9bd5e882f38..dfa56008d9ad 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h | |||
@@ -6,15 +6,21 @@ | |||
6 | #include <linux/rbtree.h> | 6 | #include <linux/rbtree.h> |
7 | #include "symbol.h" | 7 | #include "symbol.h" |
8 | 8 | ||
9 | enum chain_mode { | ||
10 | FLAT, | ||
11 | GRAPH | ||
12 | }; | ||
9 | 13 | ||
10 | struct callchain_node { | 14 | struct callchain_node { |
11 | struct callchain_node *parent; | 15 | struct callchain_node *parent; |
12 | struct list_head brothers; | 16 | struct list_head brothers; |
13 | struct list_head children; | 17 | struct list_head children; |
14 | struct list_head val; | 18 | struct list_head val; |
15 | struct rb_node rb_node; | 19 | struct rb_node rb_node; /* to sort nodes in an rbtree */ |
20 | struct rb_root rb_root; /* sorted tree of children */ | ||
16 | unsigned int val_nr; | 21 | unsigned int val_nr; |
17 | u64 hit; | 22 | u64 hit; |
23 | u64 cumul_hit; /* hit + hits of children */ | ||
18 | }; | 24 | }; |
19 | 25 | ||
20 | struct callchain_list { | 26 | struct callchain_list { |
@@ -32,5 +38,6 @@ static inline void callchain_init(struct callchain_node *node) | |||
32 | 38 | ||
33 | void append_chain(struct callchain_node *root, struct ip_callchain *chain, | 39 | void append_chain(struct callchain_node *root, struct ip_callchain *chain, |
34 | struct symbol **syms); | 40 | struct symbol **syms); |
35 | void sort_chain_to_rbtree(struct rb_root *rb_root, struct callchain_node *node); | 41 | void sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node); |
42 | void sort_chain_graph(struct rb_root *rb_root, struct callchain_node *node); | ||
36 | #endif | 43 | #endif |