2013-07-01 14:28:54

by Greg Price

[permalink] [raw]
Subject: [PATCH v2] perf report/top: Add option to collapse undesired parts of call graph

For example, in an application with an expensive function
implemented with deeply nested recursive calls, the default
call-graph presentation is dominated by the different callchains
within that function. By ignoring these callees, we can collect
the callchains leading into the function and compactly identify
what to blame for expensive calls.

For example, in this report the callers of garbage_collect() are
scattered across the tree:
$ perf report -d ruby 2>- | grep -m10 ^[^#]*[a-z]
22.03% ruby [.] gc_mark
--- gc_mark
|--59.40%-- mark_keyvalue
| st_foreach
| gc_mark_children
| |--99.75%-- rb_gc_mark
| | rb_vm_mark
| | gc_mark_children
| | gc_marks
| | |--99.00%-- garbage_collect

If we ignore the callees of garbage_collect(), its callers are coalesced:
$ perf report --ignore-callees garbage_collect -d ruby 2>- | grep -m10 ^[^#]*[a-z]
72.92% ruby [.] garbage_collect
--- garbage_collect
vm_xmalloc
|--47.08%-- ruby_xmalloc
| st_insert2
| rb_hash_aset
| |--98.45%-- features_index_add
| | rb_provide_feature
| | rb_require_safe
| | vm_call_method

Link: http://lkml.kernel.org/r/[email protected]
Cc: Arnaldo Carvalho de Melo <[email protected]>
Cc: Namhyung Kim <[email protected]>
Cc: Jiri Olsa <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Paul Mackerras <[email protected]>
Cc: David Ahern <[email protected]>
Signed-off-by: Greg Price <[email protected]>
---

Now on top of v3.10. Option renamed, added to top, comment and doc added.

tools/perf/Documentation/perf-report.txt | 5 +++++
tools/perf/Documentation/perf-top.txt | 5 +++++
tools/perf/builtin-report.c | 27 ++++++++++++++++++++++++---
tools/perf/builtin-top.c | 6 ++++--
tools/perf/util/machine.c | 24 +++++++++++++++---------
tools/perf/util/machine.h | 4 +++-
tools/perf/util/session.c | 3 +--
tools/perf/util/sort.c | 2 ++
tools/perf/util/sort.h | 4 ++++
9 files changed, 63 insertions(+), 17 deletions(-)

diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt
index 7d5f4f3..57f2137 100644
--- a/tools/perf/Documentation/perf-report.txt
+++ b/tools/perf/Documentation/perf-report.txt
@@ -135,6 +135,11 @@ OPTIONS
--inverted::
alias for inverted caller based call graph.

+--ignore-callees=<regex>::
+ Ignore callees of the function(s) matching the given regex.
+ This has the effect of collecting the callers of each such
+ function into one place in the call-graph tree.
+
--pretty=<key>::
Pretty printing style. key: normal, raw

diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt
index 9f1a2fe..be66778 100644
--- a/tools/perf/Documentation/perf-top.txt
+++ b/tools/perf/Documentation/perf-top.txt
@@ -155,6 +155,11 @@ Default is to monitor all CPUS.

Default: fractal,0.5,callee.

+--ignore-callees=<regex>::
+ Ignore callees of the function(s) matching the given regex.
+ This has the effect of collecting the callers of each such
+ function into one place in the call-graph tree.
+
INTERACTIVE PROMPTING KEYS
--------------------------

diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index bd0ca81..842575f 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -83,7 +83,7 @@ static int perf_report__add_mem_hist_entry(struct perf_tool *tool,
if ((sort__has_parent || symbol_conf.use_callchain) &&
sample->callchain) {
err = machine__resolve_callchain(machine, evsel, al->thread,
- sample, &parent);
+ sample, &parent, al);
if (err)
return err;
}
@@ -174,7 +174,7 @@ static int perf_report__add_branch_hist_entry(struct perf_tool *tool,
if ((sort__has_parent || symbol_conf.use_callchain)
&& sample->callchain) {
err = machine__resolve_callchain(machine, evsel, al->thread,
- sample, &parent);
+ sample, &parent, al);
if (err)
return err;
}
@@ -245,7 +245,7 @@ static int perf_evsel__add_hist_entry(struct perf_evsel *evsel,

if ((sort__has_parent || symbol_conf.use_callchain) && sample->callchain) {
err = machine__resolve_callchain(machine, evsel, al->thread,
- sample, &parent);
+ sample, &parent, al);
if (err)
return err;
}
@@ -687,6 +687,24 @@ setup:
return 0;
}

+int
+report_parse_ignore_callees_opt(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ if (arg) {
+ int err = regcomp(&ignore_callees_regex, arg, REG_EXTENDED);
+ if (err) {
+ char buf[BUFSIZ];
+ regerror(err, &ignore_callees_regex, buf, sizeof(buf));
+ pr_err("Invalid --ignore-callees regex: %s\n%s", arg, buf);
+ return -1;
+ }
+ have_ignore_callees = 1;
+ }
+
+ return 0;
+}
+
static int
parse_branch_mode(const struct option *opt __maybe_unused,
const char *str __maybe_unused, int unset)
@@ -764,6 +782,9 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
"Default: fractal,0.5,callee", &parse_callchain_opt, callchain_default_opt),
OPT_BOOLEAN('G', "inverted", &report.inverted_callchain,
"alias for inverted call graph"),
+ OPT_CALLBACK(0, "ignore-callees", NULL, "regex",
+ "ignore callees of these functions in call graphs",
+ report_parse_ignore_callees_opt),
OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
"only consider symbols in these dsos"),
OPT_STRING('c', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index 67bdb9f..ef4da38 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -775,8 +775,7 @@ static void perf_event__process_sample(struct perf_tool *tool,
sample->callchain) {
err = machine__resolve_callchain(machine, evsel,
al.thread, sample,
- &parent);
-
+ &parent, &al);
if (err)
return;
}
@@ -1095,6 +1094,9 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused)
OPT_CALLBACK_DEFAULT('G', "call-graph", &top.record_opts,
"mode[,dump_size]", record_callchain_help,
&parse_callchain_opt, "fp"),
+ OPT_CALLBACK(0, "ignore-callees", NULL, "regex",
+ "ignore callees of these functions in call graphs",
+ report_parse_ignore_callees_opt),
OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period,
"Show a column with the sum of periods"),
OPT_STRING(0, "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index b2ecad6..6ab6112 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1058,11 +1058,10 @@ int machine__process_event(struct machine *machine, union perf_event *event)
return ret;
}

-static bool symbol__match_parent_regex(struct symbol *sym)
+static bool symbol__match_regex(struct symbol *sym, regex_t *regex)
{
- if (sym->name && !regexec(&parent_regex, sym->name, 0, NULL, 0))
+ if (sym->name && !regexec(regex, sym->name, 0, NULL, 0))
return 1;
-
return 0;
}

@@ -1159,8 +1158,8 @@ struct branch_info *machine__resolve_bstack(struct machine *machine,
static int machine__resolve_callchain_sample(struct machine *machine,
struct thread *thread,
struct ip_callchain *chain,
- struct symbol **parent)
-
+ struct symbol **parent,
+ struct addr_location *root_al)
{
u8 cpumode = PERF_RECORD_MISC_USER;
unsigned int i;
@@ -1211,8 +1210,15 @@ static int machine__resolve_callchain_sample(struct machine *machine,
MAP__FUNCTION, ip, &al, NULL);
if (al.sym != NULL) {
if (sort__has_parent && !*parent &&
- symbol__match_parent_regex(al.sym))
+ symbol__match_regex(al.sym, &parent_regex))
*parent = al.sym;
+ else if (have_ignore_callees && root_al &&
+ symbol__match_regex(al.sym, &ignore_callees_regex)) {
+ /* Treat this symbol as the root,
+ forgetting its callees. */
+ *root_al = al;
+ callchain_cursor_reset(&callchain_cursor);
+ }
if (!symbol_conf.use_callchain)
break;
}
@@ -1237,15 +1243,15 @@ int machine__resolve_callchain(struct machine *machine,
struct perf_evsel *evsel,
struct thread *thread,
struct perf_sample *sample,
- struct symbol **parent)
-
+ struct symbol **parent,
+ struct addr_location *root_al)
{
int ret;

callchain_cursor_reset(&callchain_cursor);

ret = machine__resolve_callchain_sample(machine, thread,
- sample->callchain, parent);
+ sample->callchain, parent, root_al);
if (ret)
return ret;

diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h
index 7794068..9ce97a5 100644
--- a/tools/perf/util/machine.h
+++ b/tools/perf/util/machine.h
@@ -5,6 +5,7 @@
#include <linux/rbtree.h>
#include "map.h"

+struct addr_location;
struct branch_stack;
struct perf_evsel;
struct perf_sample;
@@ -83,7 +84,8 @@ int machine__resolve_callchain(struct machine *machine,
struct perf_evsel *evsel,
struct thread *thread,
struct perf_sample *sample,
- struct symbol **parent);
+ struct symbol **parent,
+ struct addr_location *root_al);

/*
* Default guest kernel is defined by parameter --guestkallsyms
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index cf1fe01..7024950 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -1397,9 +1397,8 @@ void perf_evsel__print_ip(struct perf_evsel *evsel, union perf_event *event,

if (symbol_conf.use_callchain && sample->callchain) {

-
if (machine__resolve_callchain(machine, evsel, al.thread,
- sample, NULL) != 0) {
+ sample, NULL, NULL) != 0) {
if (verbose)
error("Failed to resolve callchain. Skipping\n");
return;
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
index 5f52d49..295eef8 100644
--- a/tools/perf/util/sort.c
+++ b/tools/perf/util/sort.c
@@ -6,6 +6,8 @@ const char default_parent_pattern[] = "^sys_|^do_page_fault";
const char *parent_pattern = default_parent_pattern;
const char default_sort_order[] = "comm,dso,symbol";
const char *sort_order = default_sort_order;
+regex_t ignore_callees_regex;
+int have_ignore_callees = 0;
int sort__need_collapse = 0;
int sort__has_parent = 0;
int sort__has_sym = 0;
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h
index f24bdf6..3275f6b 100644
--- a/tools/perf/util/sort.h
+++ b/tools/perf/util/sort.h
@@ -29,6 +29,8 @@ extern const char *sort_order;
extern const char default_parent_pattern[];
extern const char *parent_pattern;
extern const char default_sort_order[];
+extern regex_t ignore_callees_regex;
+extern int have_ignore_callees;
extern int sort__need_collapse;
extern int sort__has_parent;
extern int sort__has_sym;
@@ -175,4 +177,6 @@ extern int sort_dimension__add(const char *);
void sort_entry__setup_elide(struct sort_entry *self, struct strlist *list,
const char *list_name, FILE *fp);

+int report_parse_ignore_callees_opt(const struct option *opt, const char *arg, int unset);
+
#endif /* __PERF_SORT_H */
--
1.8.2


2013-07-07 13:14:00

by Jiri Olsa

[permalink] [raw]
Subject: Re: [PATCH v2] perf report/top: Add option to collapse undesired parts of call graph

On Mon, Jul 01, 2013 at 10:28:45AM -0400, Greg Price wrote:
> For example, in an application with an expensive function
> implemented with deeply nested recursive calls, the default
> call-graph presentation is dominated by the different callchains
> within that function. By ignoring these callees, we can collect
> the callchains leading into the function and compactly identify
> what to blame for expensive calls.

hi,
what's this one based on? I cannot get it applied on acme's perf/core

thanks,
jirka

2013-07-08 11:57:57

by Greg Price

[permalink] [raw]
Subject: Re: [PATCH v2] perf report/top: Add option to collapse undesired parts of call graph

On Sun, Jul 07, 2013 at 03:13:40PM +0200, Jiri Olsa wrote:
> On Mon, Jul 01, 2013 at 10:28:45AM -0400, Greg Price wrote:
> > For example, in an application with an expensive function
> > implemented with deeply nested recursive calls, the default
> > call-graph presentation is dominated by the different callchains
> > within that function. By ignoring these callees, we can collect
> > the callchains leading into the function and compactly identify
> > what to blame for expensive calls.
>
> hi,
> what's this one based on? I cannot get it applied on acme's perf/core

That was on v3.10. Here it is on 6d895ece5, which is currently acme's
perf/core.

Cheers,
Greg


-- >8 --
Date: Thu, 6 Dec 2012 21:48:05 -0800

For example, in an application with an expensive function
implemented with deeply nested recursive calls, the default
call-graph presentation is dominated by the different callchains
within that function. By ignoring these callees, we can collect
the callchains leading into the function and compactly identify
what to blame for expensive calls.

For example, in this report the callers of garbage_collect() are
scattered across the tree:
$ perf report -d ruby 2>- | grep -m10 ^[^#]*[a-z]
22.03% ruby [.] gc_mark
--- gc_mark
|--59.40%-- mark_keyvalue
| st_foreach
| gc_mark_children
| |--99.75%-- rb_gc_mark
| | rb_vm_mark
| | gc_mark_children
| | gc_marks
| | |--99.00%-- garbage_collect

If we ignore the callees of garbage_collect(), its callers are coalesced:
$ perf report --ignore-callees garbage_collect -d ruby 2>- | grep -m10 ^[^#]*[a-z]
72.92% ruby [.] garbage_collect
--- garbage_collect
vm_xmalloc
|--47.08%-- ruby_xmalloc
| st_insert2
| rb_hash_aset
| |--98.45%-- features_index_add
| | rb_provide_feature
| | rb_require_safe
| | vm_call_method

Link: http://lkml.kernel.org/r/[email protected]
Cc: Arnaldo Carvalho de Melo <[email protected]>
Cc: Namhyung Kim <[email protected]>
Cc: Jiri Olsa <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Paul Mackerras <[email protected]>
Cc: David Ahern <[email protected]>
Signed-off-by: Greg Price <[email protected]>
---
tools/perf/Documentation/perf-report.txt | 5 +++++
tools/perf/Documentation/perf-top.txt | 5 +++++
tools/perf/builtin-report.c | 27 ++++++++++++++++++++++++---
tools/perf/builtin-top.c | 6 ++++--
tools/perf/util/machine.c | 24 +++++++++++++++---------
tools/perf/util/machine.h | 4 +++-
tools/perf/util/session.c | 3 +--
tools/perf/util/sort.c | 2 ++
tools/perf/util/sort.h | 4 ++++
9 files changed, 63 insertions(+), 17 deletions(-)

diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt
index 66dab74..747ff50 100644
--- a/tools/perf/Documentation/perf-report.txt
+++ b/tools/perf/Documentation/perf-report.txt
@@ -135,6 +135,11 @@ OPTIONS
--inverted::
alias for inverted caller based call graph.

+--ignore-callees=<regex>::
+ Ignore callees of the function(s) matching the given regex.
+ This has the effect of collecting the callers of each such
+ function into one place in the call-graph tree.
+
--pretty=<key>::
Pretty printing style. key: normal, raw

diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt
index 7fdd190..58d6598 100644
--- a/tools/perf/Documentation/perf-top.txt
+++ b/tools/perf/Documentation/perf-top.txt
@@ -155,6 +155,11 @@ Default is to monitor all CPUS.

Default: fractal,0.5,callee.

+--ignore-callees=<regex>::
+ Ignore callees of the function(s) matching the given regex.
+ This has the effect of collecting the callers of each such
+ function into one place in the call-graph tree.
+
--percent-limit::
Do not show entries which have an overhead under that percent.
(Default: 0).
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index ee2ca3e..d1bd252 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -89,7 +89,7 @@ static int perf_report__add_mem_hist_entry(struct perf_tool *tool,
if ((sort__has_parent || symbol_conf.use_callchain) &&
sample->callchain) {
err = machine__resolve_callchain(machine, evsel, al->thread,
- sample, &parent);
+ sample, &parent, al);
if (err)
return err;
}
@@ -180,7 +180,7 @@ static int perf_report__add_branch_hist_entry(struct perf_tool *tool,
if ((sort__has_parent || symbol_conf.use_callchain)
&& sample->callchain) {
err = machine__resolve_callchain(machine, evsel, al->thread,
- sample, &parent);
+ sample, &parent, al);
if (err)
return err;
}
@@ -254,7 +254,7 @@ static int perf_evsel__add_hist_entry(struct perf_evsel *evsel,

if ((sort__has_parent || symbol_conf.use_callchain) && sample->callchain) {
err = machine__resolve_callchain(machine, evsel, al->thread,
- sample, &parent);
+ sample, &parent, al);
if (err)
return err;
}
@@ -681,6 +681,24 @@ setup:
return 0;
}

+int
+report_parse_ignore_callees_opt(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ if (arg) {
+ int err = regcomp(&ignore_callees_regex, arg, REG_EXTENDED);
+ if (err) {
+ char buf[BUFSIZ];
+ regerror(err, &ignore_callees_regex, buf, sizeof(buf));
+ pr_err("Invalid --ignore-callees regex: %s\n%s", arg, buf);
+ return -1;
+ }
+ have_ignore_callees = 1;
+ }
+
+ return 0;
+}
+
static int
parse_branch_mode(const struct option *opt __maybe_unused,
const char *str __maybe_unused, int unset)
@@ -771,6 +789,9 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
"Default: fractal,0.5,callee", &parse_callchain_opt, callchain_default_opt),
OPT_BOOLEAN('G', "inverted", &report.inverted_callchain,
"alias for inverted call graph"),
+ OPT_CALLBACK(0, "ignore-callees", NULL, "regex",
+ "ignore callees of these functions in call graphs",
+ report_parse_ignore_callees_opt),
OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
"only consider symbols in these dsos"),
OPT_STRING('c', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index a237059..bbf4635 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -773,8 +773,7 @@ static void perf_event__process_sample(struct perf_tool *tool,
sample->callchain) {
err = machine__resolve_callchain(machine, evsel,
al.thread, sample,
- &parent);
-
+ &parent, &al);
if (err)
return;
}
@@ -1109,6 +1108,9 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused)
OPT_CALLBACK_DEFAULT('G', "call-graph", &top.record_opts,
"mode[,dump_size]", record_callchain_help,
&parse_callchain_opt, "fp"),
+ OPT_CALLBACK(0, "ignore-callees", NULL, "regex",
+ "ignore callees of these functions in call graphs",
+ report_parse_ignore_callees_opt),
OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period,
"Show a column with the sum of periods"),
OPT_STRING(0, "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index 5dd5026..f3dacdc 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1058,11 +1058,10 @@ int machine__process_event(struct machine *machine, union perf_event *event)
return ret;
}

-static bool symbol__match_parent_regex(struct symbol *sym)
+static bool symbol__match_regex(struct symbol *sym, regex_t *regex)
{
- if (sym->name && !regexec(&parent_regex, sym->name, 0, NULL, 0))
+ if (sym->name && !regexec(regex, sym->name, 0, NULL, 0))
return 1;
-
return 0;
}

@@ -1159,8 +1158,8 @@ struct branch_info *machine__resolve_bstack(struct machine *machine,
static int machine__resolve_callchain_sample(struct machine *machine,
struct thread *thread,
struct ip_callchain *chain,
- struct symbol **parent)
-
+ struct symbol **parent,
+ struct addr_location *root_al)
{
u8 cpumode = PERF_RECORD_MISC_USER;
unsigned int i;
@@ -1211,8 +1210,15 @@ static int machine__resolve_callchain_sample(struct machine *machine,
MAP__FUNCTION, ip, &al, NULL);
if (al.sym != NULL) {
if (sort__has_parent && !*parent &&
- symbol__match_parent_regex(al.sym))
+ symbol__match_regex(al.sym, &parent_regex))
*parent = al.sym;
+ else if (have_ignore_callees && root_al &&
+ symbol__match_regex(al.sym, &ignore_callees_regex)) {
+ /* Treat this symbol as the root,
+ forgetting its callees. */
+ *root_al = al;
+ callchain_cursor_reset(&callchain_cursor);
+ }
if (!symbol_conf.use_callchain)
break;
}
@@ -1237,13 +1243,13 @@ int machine__resolve_callchain(struct machine *machine,
struct perf_evsel *evsel,
struct thread *thread,
struct perf_sample *sample,
- struct symbol **parent)
-
+ struct symbol **parent,
+ struct addr_location *root_al)
{
int ret;

ret = machine__resolve_callchain_sample(machine, thread,
- sample->callchain, parent);
+ sample->callchain, parent, root_al);
if (ret)
return ret;

diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h
index e49ba01..5bb6244 100644
--- a/tools/perf/util/machine.h
+++ b/tools/perf/util/machine.h
@@ -5,6 +5,7 @@
#include <linux/rbtree.h>
#include "map.h"

+struct addr_location;
struct branch_stack;
struct perf_evsel;
struct perf_sample;
@@ -83,7 +84,8 @@ int machine__resolve_callchain(struct machine *machine,
struct perf_evsel *evsel,
struct thread *thread,
struct perf_sample *sample,
- struct symbol **parent);
+ struct symbol **parent,
+ struct addr_location *root_al);

/*
* Default guest kernel is defined by parameter --guestkallsyms
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 951a1cf..1eb58ee 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -1406,9 +1406,8 @@ void perf_evsel__print_ip(struct perf_evsel *evsel, union perf_event *event,

if (symbol_conf.use_callchain && sample->callchain) {

-
if (machine__resolve_callchain(machine, evsel, al.thread,
- sample, NULL) != 0) {
+ sample, NULL, NULL) != 0) {
if (verbose)
error("Failed to resolve callchain. Skipping\n");
return;
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
index 8deee19..cb2b108 100644
--- a/tools/perf/util/sort.c
+++ b/tools/perf/util/sort.c
@@ -7,6 +7,8 @@ const char default_parent_pattern[] = "^sys_|^do_page_fault";
const char *parent_pattern = default_parent_pattern;
const char default_sort_order[] = "comm,dso,symbol";
const char *sort_order = default_sort_order;
+regex_t ignore_callees_regex;
+int have_ignore_callees = 0;
int sort__need_collapse = 0;
int sort__has_parent = 0;
int sort__has_sym = 0;
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h
index 45ac84c..a4a6d0b 100644
--- a/tools/perf/util/sort.h
+++ b/tools/perf/util/sort.h
@@ -29,6 +29,8 @@ extern const char *sort_order;
extern const char default_parent_pattern[];
extern const char *parent_pattern;
extern const char default_sort_order[];
+extern regex_t ignore_callees_regex;
+extern int have_ignore_callees;
extern int sort__need_collapse;
extern int sort__has_parent;
extern int sort__has_sym;
@@ -183,4 +185,6 @@ int setup_sorting(void);
extern int sort_dimension__add(const char *);
void sort__setup_elide(FILE *fp);

+int report_parse_ignore_callees_opt(const struct option *opt, const char *arg, int unset);
+
#endif /* __PERF_SORT_H */
--
1.8.2

2013-07-08 16:25:14

by Jiri Olsa

[permalink] [raw]
Subject: Re: [PATCH v2] perf report/top: Add option to collapse undesired parts of call graph

On Mon, Jul 08, 2013 at 07:57:46AM -0400, Greg Price wrote:
> On Sun, Jul 07, 2013 at 03:13:40PM +0200, Jiri Olsa wrote:
> > On Mon, Jul 01, 2013 at 10:28:45AM -0400, Greg Price wrote:
> > > For example, in an application with an expensive function
> > > implemented with deeply nested recursive calls, the default
> > > call-graph presentation is dominated by the different callchains
> > > within that function. By ignoring these callees, we can collect
> > > the callchains leading into the function and compactly identify
> > > what to blame for expensive calls.
> >
> > hi,
> > what's this one based on? I cannot get it applied on acme's perf/core
>
> That was on v3.10. Here it is on 6d895ece5, which is currently acme's
> perf/core.
>
> Cheers,
> Greg
>
>
> -- >8 --
> Date: Thu, 6 Dec 2012 21:48:05 -0800
>
> For example, in an application with an expensive function
> implemented with deeply nested recursive calls, the default
> call-graph presentation is dominated by the different callchains
> within that function. By ignoring these callees, we can collect
> the callchains leading into the function and compactly identify
> what to blame for expensive calls.

change looks ok, but I'm not that confident in this part, so at least:

Tested-by: Jiri Olsa <[email protected]>

thanks,
jirka

2013-07-08 16:47:35

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH v2] perf report/top: Add option to collapse undesired parts of call graph

Em Mon, Jul 08, 2013 at 06:24:52PM +0200, Jiri Olsa escreveu:
> On Mon, Jul 08, 2013 at 07:57:46AM -0400, Greg Price wrote:
> > For example, in an application with an expensive function
> > implemented with deeply nested recursive calls, the default
> > call-graph presentation is dominated by the different callchains
> > within that function. By ignoring these callees, we can collect the
> > callchains leading into the function and compactly identify what to
> > blame for expensive calls.

> change looks ok, but I'm not that confident in this part, so at least:

> Tested-by: Jiri Olsa <[email protected]>

Thanks, applied.

- Arnaldo

Subject: [tip:perf/core] perf report/top: Add option to collapse undesired parts of call graph

Commit-ID: b21484f1a1f300d422cfe5d4f8f50015e22cea24
Gitweb: http://git.kernel.org/tip/b21484f1a1f300d422cfe5d4f8f50015e22cea24
Author: Greg Price <[email protected]>
AuthorDate: Thu, 6 Dec 2012 21:48:05 -0800
Committer: Arnaldo Carvalho de Melo <[email protected]>
CommitDate: Fri, 12 Jul 2013 13:53:55 -0300

perf report/top: Add option to collapse undesired parts of call graph

For example, in an application with an expensive function implemented
with deeply nested recursive calls, the default call-graph presentation
is dominated by the different callchains within that function. By
ignoring these callees, we can collect the callchains leading into the
function and compactly identify what to blame for expensive calls.

For example, in this report the callers of garbage_collect() are
scattered across the tree:

$ perf report -d ruby 2>- | grep -m10 ^[^#]*[a-z]
22.03% ruby [.] gc_mark
--- gc_mark
|--59.40%-- mark_keyvalue
| st_foreach
| gc_mark_children
| |--99.75%-- rb_gc_mark
| | rb_vm_mark
| | gc_mark_children
| | gc_marks
| | |--99.00%-- garbage_collect

If we ignore the callees of garbage_collect(), its callers are coalesced:

$ perf report --ignore-callees garbage_collect -d ruby 2>- | grep -m10 ^[^#]*[a-z]
72.92% ruby [.] garbage_collect
--- garbage_collect
vm_xmalloc
|--47.08%-- ruby_xmalloc
| st_insert2
| rb_hash_aset
| |--98.45%-- features_index_add
| | rb_provide_feature
| | rb_require_safe
| | vm_call_method

Signed-off-by: Greg Price <[email protected]>
Tested-by: Jiri Olsa <[email protected]>
Cc: David Ahern <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: Jiri Olsa <[email protected]>
Cc: Namhyung Kim <[email protected]>
Cc: Paul Mackerras <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Link: http://lkml.kernel.org/r/[email protected]
Link: http://lkml.kernel.org/r/[email protected]
Cc: Fengguang Wu <[email protected]>
[ remove spaces at beginning of line, reported by Fengguang Wu ]
Signed-off-by: Arnaldo Carvalho de Melo <[email protected]>
---
tools/perf/Documentation/perf-report.txt | 5 +++++
tools/perf/Documentation/perf-top.txt | 5 +++++
tools/perf/builtin-report.c | 27 ++++++++++++++++++++++++---
tools/perf/builtin-top.c | 6 ++++--
tools/perf/util/machine.c | 24 +++++++++++++++---------
tools/perf/util/machine.h | 4 +++-
tools/perf/util/session.c | 3 +--
tools/perf/util/sort.c | 2 ++
tools/perf/util/sort.h | 4 ++++
9 files changed, 63 insertions(+), 17 deletions(-)

diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt
index 66dab74..747ff50 100644
--- a/tools/perf/Documentation/perf-report.txt
+++ b/tools/perf/Documentation/perf-report.txt
@@ -135,6 +135,11 @@ OPTIONS
--inverted::
alias for inverted caller based call graph.

+--ignore-callees=<regex>::
+ Ignore callees of the function(s) matching the given regex.
+ This has the effect of collecting the callers of each such
+ function into one place in the call-graph tree.
+
--pretty=<key>::
Pretty printing style. key: normal, raw

diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt
index 7fdd190..58d6598 100644
--- a/tools/perf/Documentation/perf-top.txt
+++ b/tools/perf/Documentation/perf-top.txt
@@ -155,6 +155,11 @@ Default is to monitor all CPUS.

Default: fractal,0.5,callee.

+--ignore-callees=<regex>::
+ Ignore callees of the function(s) matching the given regex.
+ This has the effect of collecting the callers of each such
+ function into one place in the call-graph tree.
+
--percent-limit::
Do not show entries which have an overhead under that percent.
(Default: 0).
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index ee2ca3e..9a7e54d 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -89,7 +89,7 @@ static int perf_report__add_mem_hist_entry(struct perf_tool *tool,
if ((sort__has_parent || symbol_conf.use_callchain) &&
sample->callchain) {
err = machine__resolve_callchain(machine, evsel, al->thread,
- sample, &parent);
+ sample, &parent, al);
if (err)
return err;
}
@@ -180,7 +180,7 @@ static int perf_report__add_branch_hist_entry(struct perf_tool *tool,
if ((sort__has_parent || symbol_conf.use_callchain)
&& sample->callchain) {
err = machine__resolve_callchain(machine, evsel, al->thread,
- sample, &parent);
+ sample, &parent, al);
if (err)
return err;
}
@@ -254,7 +254,7 @@ static int perf_evsel__add_hist_entry(struct perf_evsel *evsel,

if ((sort__has_parent || symbol_conf.use_callchain) && sample->callchain) {
err = machine__resolve_callchain(machine, evsel, al->thread,
- sample, &parent);
+ sample, &parent, al);
if (err)
return err;
}
@@ -681,6 +681,24 @@ setup:
return 0;
}

+int
+report_parse_ignore_callees_opt(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ if (arg) {
+ int err = regcomp(&ignore_callees_regex, arg, REG_EXTENDED);
+ if (err) {
+ char buf[BUFSIZ];
+ regerror(err, &ignore_callees_regex, buf, sizeof(buf));
+ pr_err("Invalid --ignore-callees regex: %s\n%s", arg, buf);
+ return -1;
+ }
+ have_ignore_callees = 1;
+ }
+
+ return 0;
+}
+
static int
parse_branch_mode(const struct option *opt __maybe_unused,
const char *str __maybe_unused, int unset)
@@ -771,6 +789,9 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
"Default: fractal,0.5,callee", &parse_callchain_opt, callchain_default_opt),
OPT_BOOLEAN('G', "inverted", &report.inverted_callchain,
"alias for inverted call graph"),
+ OPT_CALLBACK(0, "ignore-callees", NULL, "regex",
+ "ignore callees of these functions in call graphs",
+ report_parse_ignore_callees_opt),
OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
"only consider symbols in these dsos"),
OPT_STRING('c', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index a237059..bbf4635 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -773,8 +773,7 @@ static void perf_event__process_sample(struct perf_tool *tool,
sample->callchain) {
err = machine__resolve_callchain(machine, evsel,
al.thread, sample,
- &parent);
-
+ &parent, &al);
if (err)
return;
}
@@ -1109,6 +1108,9 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused)
OPT_CALLBACK_DEFAULT('G', "call-graph", &top.record_opts,
"mode[,dump_size]", record_callchain_help,
&parse_callchain_opt, "fp"),
+ OPT_CALLBACK(0, "ignore-callees", NULL, "regex",
+ "ignore callees of these functions in call graphs",
+ report_parse_ignore_callees_opt),
OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period,
"Show a column with the sum of periods"),
OPT_STRING(0, "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index 5dd5026..f9f9d63 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1058,11 +1058,10 @@ int machine__process_event(struct machine *machine, union perf_event *event)
return ret;
}

-static bool symbol__match_parent_regex(struct symbol *sym)
+static bool symbol__match_regex(struct symbol *sym, regex_t *regex)
{
- if (sym->name && !regexec(&parent_regex, sym->name, 0, NULL, 0))
+ if (sym->name && !regexec(regex, sym->name, 0, NULL, 0))
return 1;
-
return 0;
}

@@ -1159,8 +1158,8 @@ struct branch_info *machine__resolve_bstack(struct machine *machine,
static int machine__resolve_callchain_sample(struct machine *machine,
struct thread *thread,
struct ip_callchain *chain,
- struct symbol **parent)
-
+ struct symbol **parent,
+ struct addr_location *root_al)
{
u8 cpumode = PERF_RECORD_MISC_USER;
unsigned int i;
@@ -1211,8 +1210,15 @@ static int machine__resolve_callchain_sample(struct machine *machine,
MAP__FUNCTION, ip, &al, NULL);
if (al.sym != NULL) {
if (sort__has_parent && !*parent &&
- symbol__match_parent_regex(al.sym))
+ symbol__match_regex(al.sym, &parent_regex))
*parent = al.sym;
+ else if (have_ignore_callees && root_al &&
+ symbol__match_regex(al.sym, &ignore_callees_regex)) {
+ /* Treat this symbol as the root,
+ forgetting its callees. */
+ *root_al = al;
+ callchain_cursor_reset(&callchain_cursor);
+ }
if (!symbol_conf.use_callchain)
break;
}
@@ -1237,13 +1243,13 @@ int machine__resolve_callchain(struct machine *machine,
struct perf_evsel *evsel,
struct thread *thread,
struct perf_sample *sample,
- struct symbol **parent)
-
+ struct symbol **parent,
+ struct addr_location *root_al)
{
int ret;

ret = machine__resolve_callchain_sample(machine, thread,
- sample->callchain, parent);
+ sample->callchain, parent, root_al);
if (ret)
return ret;

diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h
index e49ba01..5bb6244 100644
--- a/tools/perf/util/machine.h
+++ b/tools/perf/util/machine.h
@@ -5,6 +5,7 @@
#include <linux/rbtree.h>
#include "map.h"

+struct addr_location;
struct branch_stack;
struct perf_evsel;
struct perf_sample;
@@ -83,7 +84,8 @@ int machine__resolve_callchain(struct machine *machine,
struct perf_evsel *evsel,
struct thread *thread,
struct perf_sample *sample,
- struct symbol **parent);
+ struct symbol **parent,
+ struct addr_location *root_al);

/*
* Default guest kernel is defined by parameter --guestkallsyms
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 951a1cf..1eb58ee 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -1406,9 +1406,8 @@ void perf_evsel__print_ip(struct perf_evsel *evsel, union perf_event *event,

if (symbol_conf.use_callchain && sample->callchain) {

-
if (machine__resolve_callchain(machine, evsel, al.thread,
- sample, NULL) != 0) {
+ sample, NULL, NULL) != 0) {
if (verbose)
error("Failed to resolve callchain. Skipping\n");
return;
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
index 8deee19..cb2b108 100644
--- a/tools/perf/util/sort.c
+++ b/tools/perf/util/sort.c
@@ -7,6 +7,8 @@ const char default_parent_pattern[] = "^sys_|^do_page_fault";
const char *parent_pattern = default_parent_pattern;
const char default_sort_order[] = "comm,dso,symbol";
const char *sort_order = default_sort_order;
+regex_t ignore_callees_regex;
+int have_ignore_callees = 0;
int sort__need_collapse = 0;
int sort__has_parent = 0;
int sort__has_sym = 0;
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h
index 45ac84c..a4a6d0b 100644
--- a/tools/perf/util/sort.h
+++ b/tools/perf/util/sort.h
@@ -29,6 +29,8 @@ extern const char *sort_order;
extern const char default_parent_pattern[];
extern const char *parent_pattern;
extern const char default_sort_order[];
+extern regex_t ignore_callees_regex;
+extern int have_ignore_callees;
extern int sort__need_collapse;
extern int sort__has_parent;
extern int sort__has_sym;
@@ -183,4 +185,6 @@ int setup_sorting(void);
extern int sort_dimension__add(const char *);
void sort__setup_elide(FILE *fp);

+int report_parse_ignore_callees_opt(const struct option *opt, const char *arg, int unset);
+
#endif /* __PERF_SORT_H */