Skip to contentSkip to conversation

perf, tools: Add support for prepending LBRs to the callstack

PATCH In review Andi Kleen 3 participants 9 messages Jan 10, 2014 – Jan 11, 2014 perf events

lore
Share & tools
lore

Andi Kleen

author
#0 lore raw

PATCH 1/4 perf, tools: Add support for prepending LBRs to the callstack

From: Andi Kleen <[email protected]>

I never found the default LBR display mode which generates histograms of individual branches particularly useful.

This implements an alternative mode that creates histograms over complete branch traces, instead of individual branches, similar to how normal callgraphs are handled. This is done by putting it in
front of the normal callgraph and then using the normal callgraph histogram infrastructure to unify them.

This way in complex functions we can understand the control flow that lead to a particular sample.

The default output is unchanged.

This is only implemented in perf report, no change to record or anywhere else.

This adds the basic code to report:
- add a new "branch" option to the -g option parser to enable this mode
- when the flag is set include the LBR into the callstack in machine.c. The rest of the history code is unchanged and doesn't know the difference between LBR entry and normal call entry.

Current limitations:
- There is no attempt to cut off the LBR at the beginning of the function, so there may be small overlaps between the callstack and the LBR.
- The LBR flags (mispredict etc.) are not shown in the history

---
 tools/perf/builtin-report.c |  15 ++++--
 tools/perf/util/callchain.h |   1 +
 tools/perf/util/machine.c   | 113 ++++++++++++++++++++++++++++++++++++--------
 tools/perf/util/symbol.h    |   3 +-
 4 files changed, 106 insertions(+), 26 deletions(-)
tools/perf/builtin-report.c +11 -4
index 8cf8e66..c2e6e43 100644
@@ -706,7 +706,7 @@ parse_callchain_opt(const struct option *opt, const char *arg, int unset)
 		callchain_param.order = ORDER_CALLER;
 	else if (!strncmp(tok2, "callee", strlen("callee")))
 		callchain_param.order = ORDER_CALLEE;
-	else
+	else if (tok2[0] != 0)
 		return -1;
 
 	/* Get the sort key */
@@ -717,8 +717,15 @@ parse_callchain_opt(const struct option *opt, const char *arg, int unset)
 		callchain_param.key = CCKEY_FUNCTION;
 	else if (!strncmp(tok2, "address", strlen("address")))
 		callchain_param.key = CCKEY_ADDRESS;
-	else
+	else if (tok2[0] != 0)
 		return -1;
+
+	tok2 = strtok(NULL, ",");
+	if (!tok2)
+		goto setup;
+	if (!strncmp(tok2, "branch", 6))
+		callchain_param.branch_callstack = 1;
+
 setup:
 	if (callchain_register_param(&callchain_param) < 0) {
 		fprintf(stderr, "Can't register callchain params\n");
@@ -831,8 +838,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
 		   "regex filter to identify parent, see: '--sort parent'"),
 	OPT_BOOLEAN('x', "exclude-other", &symbol_conf.exclude_other,
 		    "Only display entries with parent-match"),
-	OPT_CALLBACK_DEFAULT('g', "call-graph", &report, "output_type,min_percent[,print_limit],call_order",
-		     "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit, callchain order, key (function or address). "
+	OPT_CALLBACK_DEFAULT('g', "call-graph", &report, "output_type,min_percent[,print_limit],call_order[,branch]",
+		     "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit, callchain order, key (function or address), add branches. "
 		     "Default: fractal,0.5,callee,function", &parse_callchain_opt, callchain_default_opt),
 	OPT_INTEGER(0, "max-stack", &report.max_stack,
 		    "Set the maximum stack depth when parsing the callchain, "
tools/perf/util/callchain.h +1 -0
index 4f7f989..3d799f2 100644
@@ -53,6 +53,7 @@ struct callchain_param {
 	sort_chain_func_t	sort;
 	enum chain_order	order;
 	enum chain_key		key;
+	bool			branch_callstack;
 };
 
 struct callchain_list {
tools/perf/util/machine.c +92 -21

147 lines not on this page · raw message

tools/perf/util/symbol.h +2 -1
index 07de8fe..a21436e 100644
@@ -99,7 +99,8 @@ struct symbol_conf {
 			annotate_asm_raw,
 			annotate_src,
 			event_group,
-			demangle;
+			demangle,
+			branch_callstack;
 	const char	*vmlinux_name,
 			*kallsyms_name,
 			*source_prefix,
Signature · 1 line
1.8.3.1
author
#1 lore raw

PATCH 2/4 perf, tools: Add --branch-call-stack option to report

From: Andi Kleen <[email protected]>

Add a --branch-call-stack option toperf report that changes all the settings necessary for using the branches in callstacks.

This is just a short cut to make this nicer to use.

---
 tools/perf/Documentation/perf-report.txt |  5 +++++
 tools/perf/builtin-report.c              | 25 ++++++++++++++++++++++---
 2 files changed, 27 insertions(+), 3 deletions(-)
tools/perf/Documentation/perf-report.txt +5 -0
index 10a2798..77ec0b9 100644
@@ -223,6 +223,11 @@ OPTIONS
 	branch stacks and it will automatically switch to the branch view mode,
 	unless --no-branch-stack is used.
 
+--branch-call-stack::
+	Add the addresses of sampled taken branches to the callstack.
+	This allows to examine the path the program took to each sample.
+	The data collection must have used -b or -j.	
+
 --objdump=<path>::
         Path to objdump binary.
 
tools/perf/builtin-report.c +22 -3
index c2e6e43..c39d1ac 100644
@@ -763,6 +763,16 @@ parse_branch_mode(const struct option *opt __maybe_unused,
 }
 
 static int
+parse_branch_call_mode(const struct option *opt __maybe_unused,
+		  const char *str __maybe_unused, int unset)
+{
+	int *branch_mode = opt->value;
+
+	*branch_mode = !unset;
+	return 0;
+}
+
+static int
 parse_percent_limit(const struct option *opt, const char *str,
 		    int unset __maybe_unused)
 {
@@ -777,7 +787,7 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
 	struct perf_session *session;
 	struct stat st;
 	bool has_br_stack = false;
-	int branch_mode = -1;
+	int branch_mode = -1, branch_call_mode = -1;
 	int ret = -1;
 	char callchain_default_opt[] = "fractal,0.5,callee";
 	const char * const report_usage[] = {
@@ -883,7 +893,10 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
 	OPT_BOOLEAN(0, "group", &symbol_conf.event_group,
 		    "Show event group information together"),
 	OPT_CALLBACK_NOOPT('b', "branch-stack", &branch_mode, "",
-		    "use branch records for histogram filling", parse_branch_mode),
+		    "use branch records for per branch histogram filling", parse_branch_mode),
+	OPT_CALLBACK_NOOPT(0, "branch-call-stack", &branch_call_mode, "",
+		    "add last branch records to call stack",
+		    parse_branch_call_mode),
 	OPT_STRING(0, "objdump", &objdump_path, "path",
 		   "objdump binary to use for disassembly and annotations"),
 	OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle,
@@ -931,8 +944,14 @@ repeat:
 	has_br_stack = perf_header__has_feat(&session->header,
 					     HEADER_BRANCH_STACK);
 
-	if (branch_mode == -1 && has_br_stack)
+	if (branch_mode == -1 && has_br_stack && branch_call_mode == -1)
 		sort__mode = SORT_MODE__BRANCH;
+	if (branch_call_mode != -1) {
+		callchain_param.branch_callstack = 1;
+		callchain_param.key = CCKEY_ADDRESS;
+		symbol_conf.use_callchain = true;
+		callchain_register_param(&callchain_param);
+	}
 
 	/* sort__mode could be NORMAL if --no-branch-stack */
 	if (sort__mode == SORT_MODE__BRANCH) {
Signature · 1 line
1.8.3.1
author
#2 lore raw

PATCH 3/4 perf, tools: Filter out small loops from LBR-as-call-stack

In reply to Andi Kleen

From: Andi Kleen <[email protected]>

Small loops can cause unnecessary duplication in the LBR-as-callstack, because the loop body appears multiple times. Filter out duplications from the LBR before unifying it into the histories. This way the same loop body only appears once.

This uses a simple hash based cycle detector. It takes some short cuts (not handling hash collisions) so in rare cases duplicates may be missed.

---
 tools/perf/util/machine.c | 73 ++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 62 insertions(+), 11 deletions(-)
tools/perf/util/machine.c +62 -11
index a7e538b..0fb4e9a 100644
@@ -10,6 +10,7 @@
 #include "thread.h"
 #include <stdbool.h>
 #include "unwind.h"
+#include "linux/hash.h"
 
 int machine__init(struct machine *machine, const char *root_dir, pid_t pid)
 {
@@ -1302,6 +1303,46 @@ static int add_callchain_ip(struct machine *machine,
 	return callchain_cursor_append(&callchain_cursor, ip, al.map, al.sym);
 }
 
+#define CHASHSZ 127
+#define CHASHBITS 7
+#define NO_ENTRY 0xff
+
+#define PERF_MAX_BRANCH_DEPTH 127
+
+/* Remove loops. */
+static int remove_loops(struct branch_entry *l, int nr)
+{
+	int i, j, off;
+	unsigned char chash[CHASHSZ];
+	memset(chash, -1, sizeof(chash));
+
+	BUG_ON(nr >= 256);
+	for (i = 0; i < nr; i++) {
+		int h = hash_64(l[i].from, CHASHBITS) % CHASHSZ;
+
+		/* no collision handling for now */
+		if (chash[h] == NO_ENTRY) {
+			chash[h] = i;
+		} else if (l[chash[h]].from == l[i].from) {
+			bool is_loop = true;
+			/* check if it is a real loop */
+			off = 0;
+			for (j = chash[h]; j < i && i + off < nr; j++, off++)
+				if (l[j].from != l[i + off].from) {
+					is_loop = false;
+					break;
+				}
+			if (is_loop) {
+				memmove(l + i, l + i + off, 
+					(nr - (i + off))
+					* sizeof(struct branch_entry));
+				nr -= off;
+			}
+		}
+	}
+	return nr;
+}
+
 static int machine__resolve_callchain_sample(struct machine *machine,
 					     struct thread *thread,
 					     struct ip_callchain *chain,
@@ -1328,29 +1369,39 @@ static int machine__resolve_callchain_sample(struct machine *machine,
 	 * - No extra filters
 	 * - No annotations (should annotate somehow)
 	 * - When the sample is near the beginning of the function
- 	 *   we may overlap with the real callstack. Could handle this
-	 *   case later, by checking against the last ip.
+ 	 *   we may overlap with the real callstack. 
 	 */
 
+	if (branch->nr > PERF_MAX_BRANCH_DEPTH) {
+		pr_warning("corrupted branch chain. skipping...\n");
+		return 0;
+	}
+
 	if (callchain_param.branch_callstack) {
-		for (i = 0; i < branch->nr; i++) { 
-			struct branch_entry *b; 
+		int nr = branch->nr;
+		struct branch_entry be[nr];
 
+		for (i = 0; i < nr; i++) { 
 			if (callchain_param.order == ORDER_CALLEE)
-				b = &branch->entries[i];
+				be[i] = branch->entries[i];
 			else
-				b = &branch->entries[branch->nr - i - 1];
+				be[i] = branch->entries[branch->nr - i - 1];
+		}
 
-			err = add_callchain_ip(machine, thread, parent, root_al,
-					       -1, b->to);
+		nr = remove_loops(be, nr);
+
+		for (i = 0; i < nr; i++) {
+			err = add_callchain_ip(machine, thread, parent, 
+					       root_al,
+					       -1, be[i].to);
 			if (!err)
-				err = add_callchain_ip(machine, thread, parent, root_al,
-					       -1, b->from);
+				err = add_callchain_ip(machine, thread, 
+						       parent, root_al,
+						       -1, be[i].from);
 			if (err == -EINVAL)
 				break;
 			if (err)
 				return err;
-
 		}
 	}
 
Signature · 1 line
1.8.3.1
author
#3 lore raw

PATCH 4/4 perf, tools: Enable printing the srcline in the history

In reply to Andi Kleen

From: Andi Kleen <[email protected]>

For lbr-as-callgraph we need to see the line number in the history, because many LBR entries can be in a single function, and just showing the same function name many times is not useful.

When the history code is configured to sort by address, also try to resolve the address to a file:srcline and display this in the browser. If that doesn't work still display the address.

This can be also useful without LBRs for understanding which call in a large function (or in which inlined function) called something else.

Contains fixes from Namhyung Kim

---
 tools/perf/ui/browsers/hists.c | 15 ++++++++++++---
 tools/perf/ui/stdio/hist.c     | 16 +++++++++++++---
 tools/perf/util/callchain.h    |  1 +
 tools/perf/util/machine.c      |  2 +-
 tools/perf/util/srcline.c      |  8 +++++++-
 5 files changed, 34 insertions(+), 8 deletions(-)
tools/perf/ui/browsers/hists.c +12 -3
index a440e03..5e0688b 100644
@@ -399,9 +399,18 @@ static char *callchain_list__sym_name(struct callchain_list *cl,
 {
 	int printed;
 
-	if (cl->ms.sym)
-		printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
-	else
+	if (cl->ms.sym) {
+		if (callchain_param.key == CCKEY_ADDRESS && 
+		    cl->ms.map && !cl->srcline)
+			cl->srcline = get_srcline(cl->ms.map->dso,
+						  map__rip_2objdump(cl->ms.map,
+								    cl->ip));
+		if (cl->srcline)
+			printed = scnprintf(bf, bfsize, "%s %s", 
+					cl->ms.sym->name, cl->srcline);
+		else
+			printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
+	} else
 		printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip);
 
 	if (show_dso)
tools/perf/ui/stdio/hist.c +13 -3
index c244cb5..eea2af2 100644
@@ -56,9 +56,19 @@ static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain,
 		} else
 			ret += fprintf(fp, "%s", "          ");
 	}
-	if (chain->ms.sym)
-		ret += fprintf(fp, "%s\n", chain->ms.sym->name);
-	else
+	if (chain->ms.sym) {
+		if (callchain_param.key == CCKEY_ADDRESS && 
+		    chain->ms.map)
+			chain->srcline = get_srcline(chain->ms.map->dso,
+						  map__rip_2objdump(
+							  chain->ms.map,
+							  chain->ip));
+		if (chain->srcline)
+			ret += fprintf(fp, "%s %s\n", 
+				       chain->ms.sym->name, chain->srcline);
+		else
+			ret += fprintf(fp, "%s\n", chain->ms.sym->name);
+	} else
 		ret += fprintf(fp, "0x%0" PRIx64 "\n", chain->ip);
 
 	return ret;
tools/perf/util/callchain.h +1 -0
index 3d799f2..70bb29b 100644
@@ -59,6 +59,7 @@ struct callchain_param {
 struct callchain_list {
 	u64			ip;
 	struct map_symbol	ms;
+	char 		       *srcline;
 	struct list_head	list;
 };
 
tools/perf/util/machine.c +1 -1
index 0fb4e9a..14437af 100644
@@ -1300,7 +1300,7 @@ static int add_callchain_ip(struct machine *machine,
 			return -EINVAL;
 	}
 
-	return callchain_cursor_append(&callchain_cursor, ip, al.map, al.sym);
+	return callchain_cursor_append(&callchain_cursor, al.addr, al.map, al.sym);
 }
 
 #define CHASHSZ 127
tools/perf/util/srcline.c +7 -1
index d11aefb..65c402d 100644
@@ -230,7 +230,7 @@ char *get_srcline(struct dso *dso, unsigned long addr)
 	size_t size;
 
 	if (!dso->has_srcline)
-		return SRCLINE_UNKNOWN;
+		goto out;
 
 	if (dso_name[0] == '[')
 		goto out;
@@ -255,6 +255,12 @@ char *get_srcline(struct dso *dso, unsigned long addr)
 
 out:
 	dso->has_srcline = 0;
+	size = snprintf(NULL, 0, "%s[%lx]", dso->short_name, addr) + 1;
+	srcline = malloc(size);
+	if (srcline) { 
+		snprintf(srcline, size, "%s[%lx]", dso->short_name, addr);
+		return srcline;
+	}
 	return SRCLINE_UNKNOWN;
 }
 
Signature · 1 line
1.8.3.1
#4 lore raw
In reply to Andi Kleen
Quoted 16 lines · Andi Kleen

From: Andi Kleen <[email protected]>

I never found the default LBR display mode which generates histograms of individual branches particularly useful.

This implements an alternative mode that creates histograms over complete branch traces, instead of individual branches, similar to how normal callgraphs are handled. This is done by putting it in
front of the normal callgraph and then using the normal callgraph histogram infrastructure to unify them.

This way in complex functions we can understand the control flow that lead to a particular sample.

The default output is unchanged.

Quoted 8 lines

This is only implemented in perf report, no change to record or anywhere else.

This adds the basic code to report:
- add a new "branch" option to the -g option parser to enable this mode
- when the flag is set include the LBR into the callstack in machine.c. The rest of the history code is unchanged and doesn't know the difference between LBR entry and normal call entry.

sounds like nice idea, but I could not get the patchset applied on acme's perf/core

jirka

author
#5 lore raw
Quoted 20 lines · Jiri Olsa
Quoted 19 lines · Andi Kleen

From: Andi Kleen <[email protected]>

I never found the default LBR display mode which generates histograms of individual branches particularly useful.

This implements an alternative mode that creates histograms over complete branch traces, instead of individual branches, similar to how normal callgraphs are handled. This is done by putting it in
front of the normal callgraph and then using the normal callgraph histogram infrastructure to unify them.

This way in complex functions we can understand the control flow that lead to a particular sample.

The default output is unchanged.

This is only implemented in perf report, no change to record or anywhere else.

Quoted 8 lines
Quoted 5 lines

This adds the basic code to report:
- add a new "branch" option to the -g option parser to enable this mode
- when the flag is set include the LBR into the callstack in machine.c. The rest of the history code is unchanged and doesn't know the difference between LBR entry and normal call entry.

sounds like nice idea, but I could not get the patchset applied on acme's perf/core

It was on Linus master.

I tried to rebase on perf/core, but it seems to be totally broken by itself. All the config tests fail on my opensuse system.

Arnaldo?

Auto-detecting system features:
... backtrace: [ OFF ]
... dwarf: [ OFF ]
... fortify-source: [ OFF ]
... glibc: [ OFF ]
... gtk2: [ OFF ]
... gtk2-infobar: [ OFF ]
... libaudit: [ OFF ]
... libbfd: [ OFF ]
... libelf: [ OFF ]
... libelf-getphdrnum: [ OFF ]
... libelf-mmap: [ OFF ]
... libnuma: [ OFF ]
... libperl: [ OFF ]
... libpython: [ OFF ]
... libpython-version: [ OFF ]
... libslang: [ OFF ]
... libunwind: [ OFF ]
... on-exit: [ OFF ]
... stackprotector-all: [ OFF ]
... timerfd: [ OFF ]

config/Makefile:282: *** No gnu/libc-version.h found, please install glibc-dev[el]/glibc-static. Stop.
make: *** [all] Error 2

-Andi

#6 lore raw

Em Sat, Jan 11, 2014 at 06:58:16PM +0100, Andi Kleen escreveu:

Quoted 30 lines · Jiri Olsa
Quoted 20 lines · Andi Kleen
Quoted 19 lines

From: Andi Kleen <[email protected]>

I never found the default LBR display mode which generates histograms of individual branches particularly useful.

This implements an alternative mode that creates histograms over complete branch traces, instead of individual branches, similar to how normal callgraphs are handled. This is done by putting it in
front of the normal callgraph and then using the normal callgraph histogram infrastructure to unify them.

This way in complex functions we can understand the control flow that lead to a particular sample.

The default output is unchanged.

This is only implemented in perf report, no change to record or anywhere else.

Quoted 8 lines
Quoted 5 lines

This adds the basic code to report:
- add a new "branch" option to the -g option parser to enable this mode
- when the flag is set include the LBR into the callstack in machine.c. The rest of the history code is unchanged and doesn't know the difference between LBR entry and normal call entry.

sounds like nice idea, but I could not get the patchset applied on acme's perf/core

Quoted 6 lines

It was on Linus master.

I tried to rebase on perf/core, but it seems to be totally broken by itself. All the config tests fail on my opensuse system.

Arnaldo?

Oops, checking on some systems...

Quoted 27 lines

Auto-detecting system features:
... backtrace: [ OFF ]
... dwarf: [ OFF ]
... fortify-source: [ OFF ]
... glibc: [ OFF ]
... gtk2: [ OFF ]
... gtk2-infobar: [ OFF ]
... libaudit: [ OFF ]
... libbfd: [ OFF ]
... libelf: [ OFF ]
... libelf-getphdrnum: [ OFF ]
... libelf-mmap: [ OFF ]
... libnuma: [ OFF ]
... libperl: [ OFF ]
... libpython: [ OFF ]
... libpython-version: [ OFF ]
... libslang: [ OFF ]
... libunwind: [ OFF ]
... on-exit: [ OFF ]
... stackprotector-all: [ OFF ]
... timerfd: [ OFF ]

config/Makefile:282: *** No gnu/libc-version.h found, please install glibc-dev[el]/glibc-static. Stop.
make: *** [all] Error 2

-Andi

#7 lore raw

Em Sat, Jan 11, 2014 at 04:16:57PM -0300, Arnaldo Carvalho de Melo escreveu:

Quoted 31 lines

Em Sat, Jan 11, 2014 at 06:58:16PM +0100, Andi Kleen escreveu:

Quoted 30 lines · Jiri Olsa
Quoted 28 lines · Andi Kleen
Quoted 16 lines

From: Andi Kleen <[email protected]>

I never found the default LBR display mode which generates histograms of individual branches particularly useful.

This implements an alternative mode that creates histograms over complete branch traces, instead of individual branches, similar to how normal callgraphs are handled. This is done by putting it in
front of the normal callgraph and then using the normal callgraph histogram infrastructure to unify them.

This way in complex functions we can understand the control flow that lead to a particular sample.

The default output is unchanged.

Quoted 8 lines

This is only implemented in perf report, no change to record or anywhere else.

This adds the basic code to report:
- add a new "branch" option to the -g option parser to enable this mode
- when the flag is set include the LBR into the callstack in machine.c. The rest of the history code is unchanged and doesn't know the difference between LBR entry and normal call entry.

sounds like nice idea, but I could not get the patchset applied on acme's perf/core

Quoted 8 lines
Quoted 6 lines

It was on Linus master.

I tried to rebase on perf/core, but it seems to be totally broken by itself. All the config tests fail on my opensuse system.

Arnaldo?

Oops, checking on some systems...

What was your build command line?

Here, on a f18 system it works with these:

$ make -C tools/perf O=/tmp/build/perf install
$ cd tools/perf ; make

Trying on another system...

- Arnaldo

Quoted 27 lines
Quoted 27 lines

Auto-detecting system features:
... backtrace: [ OFF ]
... dwarf: [ OFF ]
... fortify-source: [ OFF ]
... glibc: [ OFF ]
... gtk2: [ OFF ]
... gtk2-infobar: [ OFF ]
... libaudit: [ OFF ]
... libbfd: [ OFF ]
... libelf: [ OFF ]
... libelf-getphdrnum: [ OFF ]
... libelf-mmap: [ OFF ]
... libnuma: [ OFF ]
... libperl: [ OFF ]
... libpython: [ OFF ]
... libpython-version: [ OFF ]
... libslang: [ OFF ]
... libunwind: [ OFF ]
... on-exit: [ OFF ]
... stackprotector-all: [ OFF ]
... timerfd: [ OFF ]

config/Makefile:282: *** No gnu/libc-version.h found, please install glibc-dev[el]/glibc-static. Stop.
make: *** [all] Error 2

-Andi

author
#8 lore raw
Quoted 9 lines

What was your build command line?

Here, on a f18 system it works with these:

$ make -C tools/perf O=/tmp/build/perf install
$ cd tools/perf ; make

Trying on another system...

Sorry for the false alarm. It looks like it was a problem on my side. Works now.

-andi