perf annotate: Add branch stack / basic block

I wanted to know the hottest path through a function and figured the
branch-stack (LBR) information should be able to help out with that.

The below uses the branch-stack to create basic blocks and generate
statistics from them.

        from    to              branch_i
        * ----> *
                |
                | block
                v
                * ----> *
                from    to      branch_i+1

The blocks are broken down into non-overlapping ranges, while tracking
if the start of each range is an entry point and/or the end of a range
is a branch.

Each block iterates all ranges it covers (while splitting where required
to exactly match the block) and increments the 'coverage' count.

For the range including the branch we increment the taken counter, as
well as the pred counter if flags.predicted.

Using these number we can find if an instruction:

 - had coverage; given by:

        br->coverage / br->sym->max_coverage

   This metric ensures each symbol has a 100% spot, which reflects the
   observation that each symbol must have a most covered/hottest
   block.

 - is a branch target: br->is_target && br->start == add

 - for targets, how much of a branch's coverages comes from it:

	target->entry / branch->coverage

 - is a branch: br->is_branch && br->end == addr

 - for branches, how often it was taken:

        br->taken / br->coverage

   after all, all execution that didn't take the branch would have
   incremented the coverage and continued onward to a later branch.

 - for branches, how often it was predicted:

        br->pred / br->taken

The coverage percentage is used to color the address and asm sections;
for low (<1%) coverage we use NORMAL (uncolored), indicating that these
instructions are not 'important'. For high coverage (>75%) we color the
address RED.

For each branch, we add an asm comment after the instruction with
information on how often it was taken and predicted.

Output looks like (sans color, which does loose a lot of the
information :/)

$ perf record --branch-filter u,any -e cycles:p ./branches 27
$ perf annotate branches

 Percent |	Source code & Disassembly of branches for cycles:pu (217 samples)
---------------------------------------------------------------------------------
         :	branches():
    0.00 :	  40057a:       push   %rbp
    0.00 :	  40057b:       mov    %rsp,%rbp
    0.00 :	  40057e:       sub    $0x20,%rsp
    0.00 :	  400582:       mov    %rdi,-0x18(%rbp)
    0.00 :	  400586:       mov    %rsi,-0x20(%rbp)
    0.00 :	  40058a:       mov    -0x18(%rbp),%rax
    0.00 :	  40058e:       mov    %rax,-0x10(%rbp)
    0.00 :	  400592:       movq   $0x0,-0x8(%rbp)
    0.00 :	  40059a:       jmpq   400656 <branches+0xdc>
    1.84 :	  40059f:       mov    -0x10(%rbp),%rax	# +100.00%
    3.23 :	  4005a3:       and    $0x1,%eax
    1.84 :	  4005a6:       test   %rax,%rax
    0.00 :	  4005a9:       je     4005bf <branches+0x45>	# -54.50% (p:42.00%)
    0.46 :	  4005ab:       mov    0x200bbe(%rip),%rax        # 601170 <acc>
   12.90 :	  4005b2:       add    $0x1,%rax
    2.30 :	  4005b6:       mov    %rax,0x200bb3(%rip)        # 601170 <acc>
    0.46 :	  4005bd:       jmp    4005d1 <branches+0x57>	# -100.00% (p:100.00%)
    0.92 :	  4005bf:       mov    0x200baa(%rip),%rax        # 601170 <acc>	# +49.54%
   13.82 :	  4005c6:       sub    $0x1,%rax
    0.46 :	  4005ca:       mov    %rax,0x200b9f(%rip)        # 601170 <acc>
    2.30 :	  4005d1:       mov    -0x10(%rbp),%rax	# +50.46%
    0.46 :	  4005d5:       mov    %rax,%rdi
    0.46 :	  4005d8:       callq  400526 <lfsr>	# -100.00% (p:100.00%)
    0.00 :	  4005dd:       mov    %rax,-0x10(%rbp)	# +100.00%
    0.92 :	  4005e1:       mov    -0x18(%rbp),%rax
    0.00 :	  4005e5:       and    $0x1,%eax
    0.00 :	  4005e8:       test   %rax,%rax
    0.00 :	  4005eb:       je     4005ff <branches+0x85>	# -100.00% (p:100.00%)
    0.00 :	  4005ed:       mov    0x200b7c(%rip),%rax        # 601170 <acc>
    0.00 :	  4005f4:       shr    $0x2,%rax
    0.00 :	  4005f8:       mov    %rax,0x200b71(%rip)        # 601170 <acc>
    0.00 :	  4005ff:       mov    -0x10(%rbp),%rax	# +100.00%
    7.37 :	  400603:       and    $0x1,%eax
    3.69 :	  400606:       test   %rax,%rax
    0.00 :	  400609:       jne    400612 <branches+0x98>	# -59.25% (p:42.99%)
    1.84 :	  40060b:       mov    $0x1,%eax
   14.29 :	  400610:       jmp    400617 <branches+0x9d>	# -100.00% (p:100.00%)
    1.38 :	  400612:       mov    $0x0,%eax	# +57.65%
   10.14 :	  400617:       test   %al,%al	# +42.35%
    0.00 :	  400619:       je     40062f <branches+0xb5>	# -57.65% (p:100.00%)
    0.46 :	  40061b:       mov    0x200b4e(%rip),%rax        # 601170 <acc>
    2.76 :	  400622:       sub    $0x1,%rax
    0.00 :	  400626:       mov    %rax,0x200b43(%rip)        # 601170 <acc>
    0.46 :	  40062d:       jmp    400641 <branches+0xc7>	# -100.00% (p:100.00%)
    0.92 :	  40062f:       mov    0x200b3a(%rip),%rax        # 601170 <acc>	# +56.13%
    2.30 :	  400636:       add    $0x1,%rax
    0.92 :	  40063a:       mov    %rax,0x200b2f(%rip)        # 601170 <acc>
    0.92 :	  400641:       mov    -0x10(%rbp),%rax	# +43.87%
    2.30 :	  400645:       mov    %rax,%rdi
    0.00 :	  400648:       callq  400526 <lfsr>	# -100.00% (p:100.00%)
    0.00 :	  40064d:       mov    %rax,-0x10(%rbp)	# +100.00%
    1.84 :	  400651:       addq   $0x1,-0x8(%rbp)
    0.92 :	  400656:       mov    -0x8(%rbp),%rax
    5.07 :	  40065a:       cmp    -0x20(%rbp),%rax
    0.00 :	  40065e:       jb     40059f <branches+0x25>	# -100.00% (p:100.00%)
    0.00 :	  400664:       nop
    0.00 :	  400665:       leaveq
    0.00 :	  400666:       retq

(Note: the --branch-filter u,any was used to avoid spurious target and
branch points due to interrupts/faults, they show up as very small -/+
annotations on 'weird' locations)

Committer note:

Please take a look at:

  http://vger.kernel.org/~acme/perf/annotate_basic_blocks.png

To see the colors.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Andi Kleen <andi@firstfloor.org>
Cc: Anshuman Khandual <khandual@linux.vnet.ibm.com>
Cc: David Carrillo-Cisneros <davidcc@google.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Kan Liang <kan.liang@intel.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Stephane Eranian <eranian@google.com>
[ Moved sym->max_coverage to 'struct annotate', aka symbol__annotate(sym) ]
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Peter Zijlstra 2016-09-05 16:08:12 -03:00 committed by Arnaldo Carvalho de Melo
parent d7e404af11
commit 70fbe05745
6 changed files with 596 additions and 2 deletions

View File

@ -30,6 +30,7 @@
#include "util/tool.h"
#include "util/data.h"
#include "arch/common.h"
#include "util/block-range.h"
#include <dlfcn.h>
#include <linux/bitmap.h>
@ -46,6 +47,103 @@ struct perf_annotate {
DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);
};
/*
* Given one basic block:
*
* from to branch_i
* * ----> *
* |
* | block
* v
* * ----> *
* from to branch_i+1
*
* where the horizontal are the branches and the vertical is the executed
* block of instructions.
*
* We count, for each 'instruction', the number of blocks that covered it as
* well as count the ratio each branch is taken.
*
* We can do this without knowing the actual instruction stream by keeping
* track of the address ranges. We break down ranges such that there is no
* overlap and iterate from the start until the end.
*
* @acme: once we parse the objdump output _before_ processing the samples,
* we can easily fold the branch.cycles IPC bits in.
*/
static void process_basic_block(struct addr_map_symbol *start,
struct addr_map_symbol *end,
struct branch_flags *flags)
{
struct symbol *sym = start->sym;
struct annotation *notes = sym ? symbol__annotation(sym) : NULL;
struct block_range_iter iter;
struct block_range *entry;
/*
* Sanity; NULL isn't executable and the CPU cannot execute backwards
*/
if (!start->addr || start->addr > end->addr)
return;
iter = block_range__create(start->addr, end->addr);
if (!block_range_iter__valid(&iter))
return;
/*
* First block in range is a branch target.
*/
entry = block_range_iter(&iter);
assert(entry->is_target);
entry->entry++;
do {
entry = block_range_iter(&iter);
entry->coverage++;
entry->sym = sym;
if (notes)
notes->max_coverage = max(notes->max_coverage, entry->coverage);
} while (block_range_iter__next(&iter));
/*
* Last block in rage is a branch.
*/
entry = block_range_iter(&iter);
assert(entry->is_branch);
entry->taken++;
if (flags->predicted)
entry->pred++;
}
static void process_branch_stack(struct branch_stack *bs, struct addr_location *al,
struct perf_sample *sample)
{
struct addr_map_symbol *prev = NULL;
struct branch_info *bi;
int i;
if (!bs || !bs->nr)
return;
bi = sample__resolve_bstack(sample, al);
if (!bi)
return;
for (i = bs->nr - 1; i >= 0; i--) {
/*
* XXX filter against symbol
*/
if (prev)
process_basic_block(prev, &bi[i].from, &bi[i].flags);
prev = &bi[i].to;
}
free(bi);
}
static int perf_evsel__add_sample(struct perf_evsel *evsel,
struct perf_sample *sample,
struct addr_location *al,
@ -72,6 +170,12 @@ static int perf_evsel__add_sample(struct perf_evsel *evsel,
return 0;
}
/*
* XXX filtered samples can still have branch entires pointing into our
* symbol and are missed.
*/
process_branch_stack(sample->branch_stack, al, sample);
sample->period = 1;
sample->weight = 1;

View File

@ -1,5 +1,6 @@
libperf-y += alias.o
libperf-y += annotate.o
libperf-y += block-range.o
libperf-y += build-id.o
libperf-y += config.o
libperf-y += ctype.o

View File

@ -17,6 +17,7 @@
#include "debug.h"
#include "annotate.h"
#include "evsel.h"
#include "block-range.h"
#include <regex.h>
#include <pthread.h>
#include <linux/bitops.h>
@ -859,6 +860,89 @@ double disasm__calc_percent(struct annotation *notes, int evidx, s64 offset,
return percent;
}
static const char *annotate__address_color(struct block_range *br)
{
double cov = block_range__coverage(br);
if (cov >= 0) {
/* mark red for >75% coverage */
if (cov > 0.75)
return PERF_COLOR_RED;
/* mark dull for <1% coverage */
if (cov < 0.01)
return PERF_COLOR_NORMAL;
}
return PERF_COLOR_MAGENTA;
}
static const char *annotate__asm_color(struct block_range *br)
{
double cov = block_range__coverage(br);
if (cov >= 0) {
/* mark dull for <1% coverage */
if (cov < 0.01)
return PERF_COLOR_NORMAL;
}
return PERF_COLOR_BLUE;
}
static void annotate__branch_printf(struct block_range *br, u64 addr)
{
bool emit_comment = true;
if (!br)
return;
#if 1
if (br->is_target && br->start == addr) {
struct block_range *branch = br;
double p;
/*
* Find matching branch to our target.
*/
while (!branch->is_branch)
branch = block_range__next(branch);
p = 100 *(double)br->entry / branch->coverage;
if (p > 0.1) {
if (emit_comment) {
emit_comment = false;
printf("\t#");
}
/*
* The percentage of coverage joined at this target in relation
* to the next branch.
*/
printf(" +%.2f%%", p);
}
}
#endif
if (br->is_branch && br->end == addr) {
double p = 100*(double)br->taken / br->coverage;
if (p > 0.1) {
if (emit_comment) {
emit_comment = false;
printf("\t#");
}
/*
* The percentage of coverage leaving at this branch, and
* its prediction ratio.
*/
printf(" -%.2f%% (p:%.2f%%)", p, 100*(double)br->pred / br->taken);
}
}
}
static int disasm_line__print(struct disasm_line *dl, struct symbol *sym, u64 start,
struct perf_evsel *evsel, u64 len, int min_pcnt, int printed,
int max_lines, struct disasm_line *queue)
@ -878,6 +962,7 @@ static int disasm_line__print(struct disasm_line *dl, struct symbol *sym, u64 st
s64 offset = dl->offset;
const u64 addr = start + offset;
struct disasm_line *next;
struct block_range *br;
next = disasm__get_next_ip_line(&notes->src->source, dl);
@ -947,8 +1032,12 @@ static int disasm_line__print(struct disasm_line *dl, struct symbol *sym, u64 st
}
printf(" : ");
color_fprintf(stdout, PERF_COLOR_MAGENTA, " %" PRIx64 ":", addr);
color_fprintf(stdout, PERF_COLOR_BLUE, "%s\n", dl->line);
br = block_range__find(addr);
color_fprintf(stdout, annotate__address_color(br), " %" PRIx64 ":", addr);
color_fprintf(stdout, annotate__asm_color(br), "%s", dl->line);
annotate__branch_printf(br, addr);
printf("\n");
if (ppercents != &percent)
free(ppercents);

View File

@ -130,6 +130,7 @@ struct annotated_source {
struct annotation {
pthread_mutex_t lock;
u64 max_coverage;
struct annotated_source *src;
};

View File

@ -0,0 +1,328 @@
#include "block-range.h"
#include "annotate.h"
struct {
struct rb_root root;
u64 blocks;
} block_ranges;
static void block_range__debug(void)
{
/*
* XXX still paranoid for now; see if we can make this depend on
* DEBUG=1 builds.
*/
#if 1
struct rb_node *rb;
u64 old = 0; /* NULL isn't executable */
for (rb = rb_first(&block_ranges.root); rb; rb = rb_next(rb)) {
struct block_range *entry = rb_entry(rb, struct block_range, node);
assert(old < entry->start);
assert(entry->start <= entry->end); /* single instruction block; jump to a jump */
old = entry->end;
}
#endif
}
struct block_range *block_range__find(u64 addr)
{
struct rb_node **p = &block_ranges.root.rb_node;
struct rb_node *parent = NULL;
struct block_range *entry;
while (*p != NULL) {
parent = *p;
entry = rb_entry(parent, struct block_range, node);
if (addr < entry->start)
p = &parent->rb_left;
else if (addr > entry->end)
p = &parent->rb_right;
else
return entry;
}
return NULL;
}
static inline void rb_link_left_of_node(struct rb_node *left, struct rb_node *node)
{
struct rb_node **p = &node->rb_left;
while (*p) {
node = *p;
p = &node->rb_right;
}
rb_link_node(left, node, p);
}
static inline void rb_link_right_of_node(struct rb_node *right, struct rb_node *node)
{
struct rb_node **p = &node->rb_right;
while (*p) {
node = *p;
p = &node->rb_left;
}
rb_link_node(right, node, p);
}
/**
* block_range__create
* @start: branch target starting this basic block
* @end: branch ending this basic block
*
* Create all the required block ranges to precisely span the given range.
*/
struct block_range_iter block_range__create(u64 start, u64 end)
{
struct rb_node **p = &block_ranges.root.rb_node;
struct rb_node *n, *parent = NULL;
struct block_range *next, *entry = NULL;
struct block_range_iter iter = { NULL, NULL };
while (*p != NULL) {
parent = *p;
entry = rb_entry(parent, struct block_range, node);
if (start < entry->start)
p = &parent->rb_left;
else if (start > entry->end)
p = &parent->rb_right;
else
break;
}
/*
* Didn't find anything.. there's a hole at @start, however @end might
* be inside/behind the next range.
*/
if (!*p) {
if (!entry) /* tree empty */
goto do_whole;
/*
* If the last node is before, advance one to find the next.
*/
n = parent;
if (entry->end < start) {
n = rb_next(n);
if (!n)
goto do_whole;
}
next = rb_entry(n, struct block_range, node);
if (next->start <= end) { /* add head: [start...][n->start...] */
struct block_range *head = malloc(sizeof(struct block_range));
if (!head)
return iter;
*head = (struct block_range){
.start = start,
.end = next->start - 1,
.is_target = 1,
.is_branch = 0,
};
rb_link_left_of_node(&head->node, &next->node);
rb_insert_color(&head->node, &block_ranges.root);
block_range__debug();
iter.start = head;
goto do_tail;
}
do_whole:
/*
* The whole [start..end] range is non-overlapping.
*/
entry = malloc(sizeof(struct block_range));
if (!entry)
return iter;
*entry = (struct block_range){
.start = start,
.end = end,
.is_target = 1,
.is_branch = 1,
};
rb_link_node(&entry->node, parent, p);
rb_insert_color(&entry->node, &block_ranges.root);
block_range__debug();
iter.start = entry;
iter.end = entry;
goto done;
}
/*
* We found a range that overlapped with ours, split if needed.
*/
if (entry->start < start) { /* split: [e->start...][start...] */
struct block_range *head = malloc(sizeof(struct block_range));
if (!head)
return iter;
*head = (struct block_range){
.start = entry->start,
.end = start - 1,
.is_target = entry->is_target,
.is_branch = 0,
.coverage = entry->coverage,
.entry = entry->entry,
};
entry->start = start;
entry->is_target = 1;
entry->entry = 0;
rb_link_left_of_node(&head->node, &entry->node);
rb_insert_color(&head->node, &block_ranges.root);
block_range__debug();
} else if (entry->start == start)
entry->is_target = 1;
iter.start = entry;
do_tail:
/*
* At this point we've got: @iter.start = [@start...] but @end can still be
* inside or beyond it.
*/
entry = iter.start;
for (;;) {
/*
* If @end is inside @entry, split.
*/
if (end < entry->end) { /* split: [...end][...e->end] */
struct block_range *tail = malloc(sizeof(struct block_range));
if (!tail)
return iter;
*tail = (struct block_range){
.start = end + 1,
.end = entry->end,
.is_target = 0,
.is_branch = entry->is_branch,
.coverage = entry->coverage,
.taken = entry->taken,
.pred = entry->pred,
};
entry->end = end;
entry->is_branch = 1;
entry->taken = 0;
entry->pred = 0;
rb_link_right_of_node(&tail->node, &entry->node);
rb_insert_color(&tail->node, &block_ranges.root);
block_range__debug();
iter.end = entry;
goto done;
}
/*
* If @end matches @entry, done
*/
if (end == entry->end) {
entry->is_branch = 1;
iter.end = entry;
goto done;
}
next = block_range__next(entry);
if (!next)
goto add_tail;
/*
* If @end is in beyond @entry but not inside @next, add tail.
*/
if (end < next->start) { /* add tail: [...e->end][...end] */
struct block_range *tail;
add_tail:
tail = malloc(sizeof(struct block_range));
if (!tail)
return iter;
*tail = (struct block_range){
.start = entry->end + 1,
.end = end,
.is_target = 0,
.is_branch = 1,
};
rb_link_right_of_node(&tail->node, &entry->node);
rb_insert_color(&tail->node, &block_ranges.root);
block_range__debug();
iter.end = tail;
goto done;
}
/*
* If there is a hole between @entry and @next, fill it.
*/
if (entry->end + 1 != next->start) {
struct block_range *hole = malloc(sizeof(struct block_range));
if (!hole)
return iter;
*hole = (struct block_range){
.start = entry->end + 1,
.end = next->start - 1,
.is_target = 0,
.is_branch = 0,
};
rb_link_left_of_node(&hole->node, &next->node);
rb_insert_color(&hole->node, &block_ranges.root);
block_range__debug();
}
entry = next;
}
done:
assert(iter.start->start == start && iter.start->is_target);
assert(iter.end->end == end && iter.end->is_branch);
block_ranges.blocks++;
return iter;
}
/*
* Compute coverage as:
*
* br->coverage / br->sym->max_coverage
*
* This ensures each symbol has a 100% spot, to reflect that each symbol has a
* most covered section.
*
* Returns [0-1] for coverage and -1 if we had no data what so ever or the
* symbol does not exist.
*/
double block_range__coverage(struct block_range *br)
{
struct symbol *sym;
if (!br) {
if (block_ranges.blocks)
return 0;
return -1;
}
sym = br->sym;
if (!sym)
return -1;
return (double)br->coverage / symbol__annotation(sym)->max_coverage;
}

View File

@ -0,0 +1,71 @@
#ifndef __PERF_BLOCK_RANGE_H
#define __PERF_BLOCK_RANGE_H
#include "symbol.h"
/*
* struct block_range - non-overlapping parts of basic blocks
* @node: treenode
* @start: inclusive start of range
* @end: inclusive end of range
* @is_target: @start is a jump target
* @is_branch: @end is a branch instruction
* @coverage: number of blocks that cover this range
* @taken: number of times the branch is taken (requires @is_branch)
* @pred: number of times the taken branch was predicted
*/
struct block_range {
struct rb_node node;
struct symbol *sym;
u64 start;
u64 end;
int is_target, is_branch;
u64 coverage;
u64 entry;
u64 taken;
u64 pred;
};
static inline struct block_range *block_range__next(struct block_range *br)
{
struct rb_node *n = rb_next(&br->node);
if (!n)
return NULL;
return rb_entry(n, struct block_range, node);
}
struct block_range_iter {
struct block_range *start;
struct block_range *end;
};
static inline struct block_range *block_range_iter(struct block_range_iter *iter)
{
return iter->start;
}
static inline bool block_range_iter__next(struct block_range_iter *iter)
{
if (iter->start == iter->end)
return false;
iter->start = block_range__next(iter->start);
return true;
}
static inline bool block_range_iter__valid(struct block_range_iter *iter)
{
if (!iter->start || !iter->end)
return false;
return true;
}
extern struct block_range *block_range__find(u64 addr);
extern struct block_range_iter block_range__create(u64 start, u64 end);
extern double block_range__coverage(struct block_range *br);
#endif /* __PERF_BLOCK_RANGE_H */