mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
perf: Fix orphan callchain branches
Callchains have markers inside their capture to tell we enter a context (kernel, user, ...). Those are not displayed in the callchains but they are incidentally an active part of the radix tree where callchains are stored, just like any other address. If we have the two following callchains: addr1 -> addr2 -> user context -> addr3 addr1 -> addr2 -> user context -> addr4 addr1 -> addr2 -> addr 5 This is pretty common if addr1 and addr2 are part of an interrupt path, addr3 and addr4 are user addresses and addr5 is a kernel non interrupt path. This will be stored as follows in the tree: addr1 addr2 / \ / addr5 user context / \ addr3 addr4 But we ignore the context markers in the report, hence the addr3 and addr4 will appear as orphan branches: |--28.30%-- hrtimer_interrupt | smp_apic_timer_interrupt | apic_timer_interrupt | | <------------- here, no parent! | | | | | |--11.11%-- 0x7fae7bccb875 | | | | | |--11.11%-- 0xffffffffff60013b | | | | | |--11.11%-- __pthread_mutex_lock_internal | | | | | |--11.11%-- __errno_location Fix this by removing the context markers when we process the callchains to the tree. Reported-by: Arnaldo Carvalho de Melo <acme@ghostprotocols.net> Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com> Cc: Paul Mackerras <paulus@samba.org> Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> LKML-Reference: <1269274173-20328-1-git-send-email-acme@infradead.org> Signed-off-by: Ingo Molnar <mingo@elte.hu>
This commit is contained in:
parent
d2f1e15b66
commit
301fde27c7
4 changed files with 88 additions and 36 deletions
|
@ -83,6 +83,7 @@ static int perf_session__add_hist_entry(struct perf_session *self,
|
||||||
{
|
{
|
||||||
struct symbol **syms = NULL, *parent = NULL;
|
struct symbol **syms = NULL, *parent = NULL;
|
||||||
bool hit;
|
bool hit;
|
||||||
|
int err;
|
||||||
struct hist_entry *he;
|
struct hist_entry *he;
|
||||||
struct event_stat_id *stats;
|
struct event_stat_id *stats;
|
||||||
struct perf_event_attr *attr;
|
struct perf_event_attr *attr;
|
||||||
|
@ -109,8 +110,11 @@ static int perf_session__add_hist_entry(struct perf_session *self,
|
||||||
if (symbol_conf.use_callchain) {
|
if (symbol_conf.use_callchain) {
|
||||||
if (!hit)
|
if (!hit)
|
||||||
callchain_init(&he->callchain);
|
callchain_init(&he->callchain);
|
||||||
append_chain(&he->callchain, data->callchain, syms);
|
err = append_chain(&he->callchain, data->callchain, syms);
|
||||||
free(syms);
|
free(syms);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009, Frederic Weisbecker <fweisbec@gmail.com>
|
* Copyright (C) 2009-2010, Frederic Weisbecker <fweisbec@gmail.com>
|
||||||
*
|
*
|
||||||
* Handle the callchains from the stream in an ad-hoc radix tree and then
|
* Handle the callchains from the stream in an ad-hoc radix tree and then
|
||||||
* sort them in an rbtree.
|
* sort them in an rbtree.
|
||||||
|
@ -183,12 +183,23 @@ create_child(struct callchain_node *parent, bool inherit_children)
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct resolved_ip {
|
||||||
|
u64 ip;
|
||||||
|
struct symbol *sym;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct resolved_chain {
|
||||||
|
u64 nr;
|
||||||
|
struct resolved_ip ips[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fill the node with callchain values
|
* Fill the node with callchain values
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
fill_node(struct callchain_node *node, struct ip_callchain *chain,
|
fill_node(struct callchain_node *node, struct resolved_chain *chain, int start)
|
||||||
int start, struct symbol **syms)
|
|
||||||
{
|
{
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
|
||||||
|
@ -200,8 +211,8 @@ fill_node(struct callchain_node *node, struct ip_callchain *chain,
|
||||||
perror("not enough memory for the code path tree");
|
perror("not enough memory for the code path tree");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
call->ip = chain->ips[i];
|
call->ip = chain->ips[i].ip;
|
||||||
call->sym = syms[i];
|
call->sym = chain->ips[i].sym;
|
||||||
list_add_tail(&call->list, &node->val);
|
list_add_tail(&call->list, &node->val);
|
||||||
}
|
}
|
||||||
node->val_nr = chain->nr - start;
|
node->val_nr = chain->nr - start;
|
||||||
|
@ -210,13 +221,13 @@ fill_node(struct callchain_node *node, struct ip_callchain *chain,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
add_child(struct callchain_node *parent, struct ip_callchain *chain,
|
add_child(struct callchain_node *parent, struct resolved_chain *chain,
|
||||||
int start, struct symbol **syms)
|
int start)
|
||||||
{
|
{
|
||||||
struct callchain_node *new;
|
struct callchain_node *new;
|
||||||
|
|
||||||
new = create_child(parent, false);
|
new = create_child(parent, false);
|
||||||
fill_node(new, chain, start, syms);
|
fill_node(new, chain, start);
|
||||||
|
|
||||||
new->children_hit = 0;
|
new->children_hit = 0;
|
||||||
new->hit = 1;
|
new->hit = 1;
|
||||||
|
@ -228,9 +239,8 @@ add_child(struct callchain_node *parent, struct ip_callchain *chain,
|
||||||
* Then create another child to host the given callchain of new branch
|
* Then create another child to host the given callchain of new branch
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
split_add_child(struct callchain_node *parent, struct ip_callchain *chain,
|
split_add_child(struct callchain_node *parent, struct resolved_chain *chain,
|
||||||
struct callchain_list *to_split, int idx_parents, int idx_local,
|
struct callchain_list *to_split, int idx_parents, int idx_local)
|
||||||
struct symbol **syms)
|
|
||||||
{
|
{
|
||||||
struct callchain_node *new;
|
struct callchain_node *new;
|
||||||
struct list_head *old_tail;
|
struct list_head *old_tail;
|
||||||
|
@ -257,7 +267,7 @@ split_add_child(struct callchain_node *parent, struct ip_callchain *chain,
|
||||||
/* create a new child for the new branch if any */
|
/* create a new child for the new branch if any */
|
||||||
if (idx_total < chain->nr) {
|
if (idx_total < chain->nr) {
|
||||||
parent->hit = 0;
|
parent->hit = 0;
|
||||||
add_child(parent, chain, idx_total, syms);
|
add_child(parent, chain, idx_total);
|
||||||
parent->children_hit++;
|
parent->children_hit++;
|
||||||
} else {
|
} else {
|
||||||
parent->hit = 1;
|
parent->hit = 1;
|
||||||
|
@ -265,32 +275,33 @@ split_add_child(struct callchain_node *parent, struct ip_callchain *chain,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
__append_chain(struct callchain_node *root, struct ip_callchain *chain,
|
__append_chain(struct callchain_node *root, struct resolved_chain *chain,
|
||||||
unsigned int start, struct symbol **syms);
|
unsigned int start);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
__append_chain_children(struct callchain_node *root, struct ip_callchain *chain,
|
__append_chain_children(struct callchain_node *root,
|
||||||
struct symbol **syms, unsigned int start)
|
struct resolved_chain *chain,
|
||||||
|
unsigned int start)
|
||||||
{
|
{
|
||||||
struct callchain_node *rnode;
|
struct callchain_node *rnode;
|
||||||
|
|
||||||
/* lookup in childrens */
|
/* lookup in childrens */
|
||||||
chain_for_each_child(rnode, root) {
|
chain_for_each_child(rnode, root) {
|
||||||
unsigned int ret = __append_chain(rnode, chain, start, syms);
|
unsigned int ret = __append_chain(rnode, chain, start);
|
||||||
|
|
||||||
if (!ret)
|
if (!ret)
|
||||||
goto inc_children_hit;
|
goto inc_children_hit;
|
||||||
}
|
}
|
||||||
/* nothing in children, add to the current node */
|
/* nothing in children, add to the current node */
|
||||||
add_child(root, chain, start, syms);
|
add_child(root, chain, start);
|
||||||
|
|
||||||
inc_children_hit:
|
inc_children_hit:
|
||||||
root->children_hit++;
|
root->children_hit++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
__append_chain(struct callchain_node *root, struct ip_callchain *chain,
|
__append_chain(struct callchain_node *root, struct resolved_chain *chain,
|
||||||
unsigned int start, struct symbol **syms)
|
unsigned int start)
|
||||||
{
|
{
|
||||||
struct callchain_list *cnode;
|
struct callchain_list *cnode;
|
||||||
unsigned int i = start;
|
unsigned int i = start;
|
||||||
|
@ -302,13 +313,19 @@ __append_chain(struct callchain_node *root, struct ip_callchain *chain,
|
||||||
* anywhere inside a function.
|
* anywhere inside a function.
|
||||||
*/
|
*/
|
||||||
list_for_each_entry(cnode, &root->val, list) {
|
list_for_each_entry(cnode, &root->val, list) {
|
||||||
|
struct symbol *sym;
|
||||||
|
|
||||||
if (i == chain->nr)
|
if (i == chain->nr)
|
||||||
break;
|
break;
|
||||||
if (cnode->sym && syms[i]) {
|
|
||||||
if (cnode->sym->start != syms[i]->start)
|
sym = chain->ips[i].sym;
|
||||||
|
|
||||||
|
if (cnode->sym && sym) {
|
||||||
|
if (cnode->sym->start != sym->start)
|
||||||
break;
|
break;
|
||||||
} else if (cnode->ip != chain->ips[i])
|
} else if (cnode->ip != chain->ips[i].ip)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (!found)
|
if (!found)
|
||||||
found = true;
|
found = true;
|
||||||
i++;
|
i++;
|
||||||
|
@ -320,7 +337,7 @@ __append_chain(struct callchain_node *root, struct ip_callchain *chain,
|
||||||
|
|
||||||
/* we match only a part of the node. Split it and add the new chain */
|
/* we match only a part of the node. Split it and add the new chain */
|
||||||
if (i - start < root->val_nr) {
|
if (i - start < root->val_nr) {
|
||||||
split_add_child(root, chain, cnode, start, i - start, syms);
|
split_add_child(root, chain, cnode, start, i - start);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,15 +348,51 @@ __append_chain(struct callchain_node *root, struct ip_callchain *chain,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We match the node and still have a part remaining */
|
/* We match the node and still have a part remaining */
|
||||||
__append_chain_children(root, chain, syms, i);
|
__append_chain_children(root, chain, i);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void append_chain(struct callchain_node *root, struct ip_callchain *chain,
|
static void
|
||||||
|
filter_context(struct ip_callchain *old, struct resolved_chain *new,
|
||||||
|
struct symbol **syms)
|
||||||
|
{
|
||||||
|
int i, j = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < (int)old->nr; i++) {
|
||||||
|
if (old->ips[i] >= PERF_CONTEXT_MAX)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
new->ips[j].ip = old->ips[i];
|
||||||
|
new->ips[j].sym = syms[i];
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
new->nr = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int append_chain(struct callchain_node *root, struct ip_callchain *chain,
|
||||||
struct symbol **syms)
|
struct symbol **syms)
|
||||||
{
|
{
|
||||||
|
struct resolved_chain *filtered;
|
||||||
|
|
||||||
if (!chain->nr)
|
if (!chain->nr)
|
||||||
return;
|
return 0;
|
||||||
__append_chain_children(root, chain, syms, 0);
|
|
||||||
|
filtered = malloc(sizeof(*filtered) +
|
||||||
|
chain->nr * sizeof(struct resolved_ip));
|
||||||
|
if (!filtered)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
filter_context(chain, filtered, syms);
|
||||||
|
|
||||||
|
if (!filtered->nr)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
__append_chain_children(root, filtered, 0);
|
||||||
|
end:
|
||||||
|
free(filtered);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,6 @@ static inline u64 cumul_hits(struct callchain_node *node)
|
||||||
}
|
}
|
||||||
|
|
||||||
int register_callchain_param(struct callchain_param *param);
|
int register_callchain_param(struct callchain_param *param);
|
||||||
void append_chain(struct callchain_node *root, struct ip_callchain *chain,
|
int append_chain(struct callchain_node *root, struct ip_callchain *chain,
|
||||||
struct symbol **syms);
|
struct symbol **syms);
|
||||||
#endif /* __PERF_CALLCHAIN_H */
|
#endif /* __PERF_CALLCHAIN_H */
|
||||||
|
|
|
@ -328,8 +328,6 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
|
||||||
left_margin);
|
left_margin);
|
||||||
i = 0;
|
i = 0;
|
||||||
list_for_each_entry(chain, &child->val, list) {
|
list_for_each_entry(chain, &child->val, list) {
|
||||||
if (chain->ip >= PERF_CONTEXT_MAX)
|
|
||||||
continue;
|
|
||||||
ret += ipchain__fprintf_graph(fp, chain, depth,
|
ret += ipchain__fprintf_graph(fp, chain, depth,
|
||||||
new_depth_mask, i++,
|
new_depth_mask, i++,
|
||||||
new_total,
|
new_total,
|
||||||
|
@ -368,9 +366,6 @@ static size_t callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
list_for_each_entry(chain, &self->val, list) {
|
list_for_each_entry(chain, &self->val, list) {
|
||||||
if (chain->ip >= PERF_CONTEXT_MAX)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!i++ && sort__first_dimension == SORT_SYM)
|
if (!i++ && sort__first_dimension == SORT_SYM)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue