301fde27c7
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>
399 lines
8.7 KiB
C
399 lines
8.7 KiB
C
/*
|
|
* Copyright (C) 2009-2010, Frederic Weisbecker <fweisbec@gmail.com>
|
|
*
|
|
* Handle the callchains from the stream in an ad-hoc radix tree and then
|
|
* sort them in an rbtree.
|
|
*
|
|
* Using a radix for code path provides a fast retrieval and factorizes
|
|
* memory use. Also that lets us use the paths in a hierarchical graph view.
|
|
*
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
|
|
#include "callchain.h"
|
|
|
|
#define chain_for_each_child(child, parent) \
|
|
list_for_each_entry(child, &parent->children, brothers)
|
|
|
|
static void
|
|
rb_insert_callchain(struct rb_root *root, struct callchain_node *chain,
|
|
enum chain_mode mode)
|
|
{
|
|
struct rb_node **p = &root->rb_node;
|
|
struct rb_node *parent = NULL;
|
|
struct callchain_node *rnode;
|
|
u64 chain_cumul = cumul_hits(chain);
|
|
|
|
while (*p) {
|
|
u64 rnode_cumul;
|
|
|
|
parent = *p;
|
|
rnode = rb_entry(parent, struct callchain_node, rb_node);
|
|
rnode_cumul = cumul_hits(rnode);
|
|
|
|
switch (mode) {
|
|
case CHAIN_FLAT:
|
|
if (rnode->hit < chain->hit)
|
|
p = &(*p)->rb_left;
|
|
else
|
|
p = &(*p)->rb_right;
|
|
break;
|
|
case CHAIN_GRAPH_ABS: /* Falldown */
|
|
case CHAIN_GRAPH_REL:
|
|
if (rnode_cumul < chain_cumul)
|
|
p = &(*p)->rb_left;
|
|
else
|
|
p = &(*p)->rb_right;
|
|
break;
|
|
case CHAIN_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
rb_link_node(&chain->rb_node, parent, p);
|
|
rb_insert_color(&chain->rb_node, root);
|
|
}
|
|
|
|
static void
|
|
__sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node,
|
|
u64 min_hit)
|
|
{
|
|
struct callchain_node *child;
|
|
|
|
chain_for_each_child(child, node)
|
|
__sort_chain_flat(rb_root, child, min_hit);
|
|
|
|
if (node->hit && node->hit >= min_hit)
|
|
rb_insert_callchain(rb_root, node, CHAIN_FLAT);
|
|
}
|
|
|
|
/*
|
|
* Once we get every callchains from the stream, we can now
|
|
* sort them by hit
|
|
*/
|
|
static void
|
|
sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node,
|
|
u64 min_hit, struct callchain_param *param __used)
|
|
{
|
|
__sort_chain_flat(rb_root, node, min_hit);
|
|
}
|
|
|
|
static void __sort_chain_graph_abs(struct callchain_node *node,
|
|
u64 min_hit)
|
|
{
|
|
struct callchain_node *child;
|
|
|
|
node->rb_root = RB_ROOT;
|
|
|
|
chain_for_each_child(child, node) {
|
|
__sort_chain_graph_abs(child, min_hit);
|
|
if (cumul_hits(child) >= min_hit)
|
|
rb_insert_callchain(&node->rb_root, child,
|
|
CHAIN_GRAPH_ABS);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sort_chain_graph_abs(struct rb_root *rb_root, struct callchain_node *chain_root,
|
|
u64 min_hit, struct callchain_param *param __used)
|
|
{
|
|
__sort_chain_graph_abs(chain_root, min_hit);
|
|
rb_root->rb_node = chain_root->rb_root.rb_node;
|
|
}
|
|
|
|
static void __sort_chain_graph_rel(struct callchain_node *node,
|
|
double min_percent)
|
|
{
|
|
struct callchain_node *child;
|
|
u64 min_hit;
|
|
|
|
node->rb_root = RB_ROOT;
|
|
min_hit = ceil(node->children_hit * min_percent);
|
|
|
|
chain_for_each_child(child, node) {
|
|
__sort_chain_graph_rel(child, min_percent);
|
|
if (cumul_hits(child) >= min_hit)
|
|
rb_insert_callchain(&node->rb_root, child,
|
|
CHAIN_GRAPH_REL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sort_chain_graph_rel(struct rb_root *rb_root, struct callchain_node *chain_root,
|
|
u64 min_hit __used, struct callchain_param *param)
|
|
{
|
|
__sort_chain_graph_rel(chain_root, param->min_percent / 100.0);
|
|
rb_root->rb_node = chain_root->rb_root.rb_node;
|
|
}
|
|
|
|
int register_callchain_param(struct callchain_param *param)
|
|
{
|
|
switch (param->mode) {
|
|
case CHAIN_GRAPH_ABS:
|
|
param->sort = sort_chain_graph_abs;
|
|
break;
|
|
case CHAIN_GRAPH_REL:
|
|
param->sort = sort_chain_graph_rel;
|
|
break;
|
|
case CHAIN_FLAT:
|
|
param->sort = sort_chain_flat;
|
|
break;
|
|
case CHAIN_NONE:
|
|
default:
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Create a child for a parent. If inherit_children, then the new child
|
|
* will become the new parent of it's parent children
|
|
*/
|
|
static struct callchain_node *
|
|
create_child(struct callchain_node *parent, bool inherit_children)
|
|
{
|
|
struct callchain_node *new;
|
|
|
|
new = malloc(sizeof(*new));
|
|
if (!new) {
|
|
perror("not enough memory to create child for code path tree");
|
|
return NULL;
|
|
}
|
|
new->parent = parent;
|
|
INIT_LIST_HEAD(&new->children);
|
|
INIT_LIST_HEAD(&new->val);
|
|
|
|
if (inherit_children) {
|
|
struct callchain_node *next;
|
|
|
|
list_splice(&parent->children, &new->children);
|
|
INIT_LIST_HEAD(&parent->children);
|
|
|
|
chain_for_each_child(next, new)
|
|
next->parent = new;
|
|
}
|
|
list_add_tail(&new->brothers, &parent->children);
|
|
|
|
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
|
|
*/
|
|
static void
|
|
fill_node(struct callchain_node *node, struct resolved_chain *chain, int start)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = start; i < chain->nr; i++) {
|
|
struct callchain_list *call;
|
|
|
|
call = malloc(sizeof(*call));
|
|
if (!call) {
|
|
perror("not enough memory for the code path tree");
|
|
return;
|
|
}
|
|
call->ip = chain->ips[i].ip;
|
|
call->sym = chain->ips[i].sym;
|
|
list_add_tail(&call->list, &node->val);
|
|
}
|
|
node->val_nr = chain->nr - start;
|
|
if (!node->val_nr)
|
|
pr_warning("Warning: empty node in callchain tree\n");
|
|
}
|
|
|
|
static void
|
|
add_child(struct callchain_node *parent, struct resolved_chain *chain,
|
|
int start)
|
|
{
|
|
struct callchain_node *new;
|
|
|
|
new = create_child(parent, false);
|
|
fill_node(new, chain, start);
|
|
|
|
new->children_hit = 0;
|
|
new->hit = 1;
|
|
}
|
|
|
|
/*
|
|
* Split the parent in two parts (a new child is created) and
|
|
* give a part of its callchain to the created child.
|
|
* Then create another child to host the given callchain of new branch
|
|
*/
|
|
static void
|
|
split_add_child(struct callchain_node *parent, struct resolved_chain *chain,
|
|
struct callchain_list *to_split, int idx_parents, int idx_local)
|
|
{
|
|
struct callchain_node *new;
|
|
struct list_head *old_tail;
|
|
unsigned int idx_total = idx_parents + idx_local;
|
|
|
|
/* split */
|
|
new = create_child(parent, true);
|
|
|
|
/* split the callchain and move a part to the new child */
|
|
old_tail = parent->val.prev;
|
|
list_del_range(&to_split->list, old_tail);
|
|
new->val.next = &to_split->list;
|
|
new->val.prev = old_tail;
|
|
to_split->list.prev = &new->val;
|
|
old_tail->next = &new->val;
|
|
|
|
/* split the hits */
|
|
new->hit = parent->hit;
|
|
new->children_hit = parent->children_hit;
|
|
parent->children_hit = cumul_hits(new);
|
|
new->val_nr = parent->val_nr - idx_local;
|
|
parent->val_nr = idx_local;
|
|
|
|
/* create a new child for the new branch if any */
|
|
if (idx_total < chain->nr) {
|
|
parent->hit = 0;
|
|
add_child(parent, chain, idx_total);
|
|
parent->children_hit++;
|
|
} else {
|
|
parent->hit = 1;
|
|
}
|
|
}
|
|
|
|
static int
|
|
__append_chain(struct callchain_node *root, struct resolved_chain *chain,
|
|
unsigned int start);
|
|
|
|
static void
|
|
__append_chain_children(struct callchain_node *root,
|
|
struct resolved_chain *chain,
|
|
unsigned int start)
|
|
{
|
|
struct callchain_node *rnode;
|
|
|
|
/* lookup in childrens */
|
|
chain_for_each_child(rnode, root) {
|
|
unsigned int ret = __append_chain(rnode, chain, start);
|
|
|
|
if (!ret)
|
|
goto inc_children_hit;
|
|
}
|
|
/* nothing in children, add to the current node */
|
|
add_child(root, chain, start);
|
|
|
|
inc_children_hit:
|
|
root->children_hit++;
|
|
}
|
|
|
|
static int
|
|
__append_chain(struct callchain_node *root, struct resolved_chain *chain,
|
|
unsigned int start)
|
|
{
|
|
struct callchain_list *cnode;
|
|
unsigned int i = start;
|
|
bool found = false;
|
|
|
|
/*
|
|
* Lookup in the current node
|
|
* If we have a symbol, then compare the start to match
|
|
* anywhere inside a function.
|
|
*/
|
|
list_for_each_entry(cnode, &root->val, list) {
|
|
struct symbol *sym;
|
|
|
|
if (i == chain->nr)
|
|
break;
|
|
|
|
sym = chain->ips[i].sym;
|
|
|
|
if (cnode->sym && sym) {
|
|
if (cnode->sym->start != sym->start)
|
|
break;
|
|
} else if (cnode->ip != chain->ips[i].ip)
|
|
break;
|
|
|
|
if (!found)
|
|
found = true;
|
|
i++;
|
|
}
|
|
|
|
/* matches not, relay on the parent */
|
|
if (!found)
|
|
return -1;
|
|
|
|
/* we match only a part of the node. Split it and add the new chain */
|
|
if (i - start < root->val_nr) {
|
|
split_add_child(root, chain, cnode, start, i - start);
|
|
return 0;
|
|
}
|
|
|
|
/* we match 100% of the path, increment the hit */
|
|
if (i - start == root->val_nr && i == chain->nr) {
|
|
root->hit++;
|
|
return 0;
|
|
}
|
|
|
|
/* We match the node and still have a part remaining */
|
|
__append_chain_children(root, chain, i);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 resolved_chain *filtered;
|
|
|
|
if (!chain->nr)
|
|
return 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;
|
|
}
|