callchain.c 10.4 KB
Newer Older
1
/*
2
 * Copyright (C) 2009-2010, Frederic Weisbecker <fweisbec@gmail.com>
3 4 5 6
 *
 * Handle the callchains from the stream in an ad-hoc radix tree and then
 * sort them in an rbtree.
 *
7 8 9
 * 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.
 *
10 11 12 13 14 15
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
16
#include <math.h>
17

18
#include "util.h"
19 20
#include "callchain.h"

21
bool ip_callchain__valid(struct ip_callchain *chain, const event_t *event)
22 23 24 25 26 27
{
	unsigned int chain_size = event->header.size;
	chain_size -= (unsigned long)&event->ip.__more_data - (unsigned long)event;
	return chain->nr * sizeof(u64) <= chain_size;
}

28 29 30
#define chain_for_each_child(child, parent)	\
	list_for_each_entry(child, &parent->children, brothers)

31 32 33
#define chain_for_each_child_safe(child, next, parent)	\
	list_for_each_entry_safe(child, next, &parent->children, brothers)

34
static void
35 36
rb_insert_callchain(struct rb_root *root, struct callchain_node *chain,
		    enum chain_mode mode)
37 38 39 40
{
	struct rb_node **p = &root->rb_node;
	struct rb_node *parent = NULL;
	struct callchain_node *rnode;
41
	u64 chain_cumul = cumul_hits(chain);
42 43

	while (*p) {
44 45
		u64 rnode_cumul;

46 47
		parent = *p;
		rnode = rb_entry(parent, struct callchain_node, rb_node);
48
		rnode_cumul = cumul_hits(rnode);
49

50
		switch (mode) {
51
		case CHAIN_FLAT:
52 53 54 55 56
			if (rnode->hit < chain->hit)
				p = &(*p)->rb_left;
			else
				p = &(*p)->rb_right;
			break;
57 58
		case CHAIN_GRAPH_ABS: /* Falldown */
		case CHAIN_GRAPH_REL:
59
			if (rnode_cumul < chain_cumul)
60 61 62 63
				p = &(*p)->rb_left;
			else
				p = &(*p)->rb_right;
			break;
64
		case CHAIN_NONE:
65 66 67
		default:
			break;
		}
68 69 70 71 72 73
	}

	rb_link_node(&chain->rb_node, parent, p);
	rb_insert_color(&chain->rb_node, root);
}

74 75 76 77 78 79 80 81 82 83 84 85 86
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);
}

87 88 89 90
/*
 * Once we get every callchains from the stream, we can now
 * sort them by hit
 */
91
static void
92
sort_chain_flat(struct rb_root *rb_root, struct callchain_root *root,
93 94
		u64 min_hit, struct callchain_param *param __used)
{
95
	__sort_chain_flat(rb_root, &root->node, min_hit);
96 97 98 99
}

static void __sort_chain_graph_abs(struct callchain_node *node,
				   u64 min_hit)
100 101 102
{
	struct callchain_node *child;

103
	node->rb_root = RB_ROOT;
104

105 106
	chain_for_each_child(child, node) {
		__sort_chain_graph_abs(child, min_hit);
107
		if (cumul_hits(child) >= min_hit)
108 109 110 111 112 113
			rb_insert_callchain(&node->rb_root, child,
					    CHAIN_GRAPH_ABS);
	}
}

static void
114
sort_chain_graph_abs(struct rb_root *rb_root, struct callchain_root *chain_root,
115 116
		     u64 min_hit, struct callchain_param *param __used)
{
117 118
	__sort_chain_graph_abs(&chain_root->node, min_hit);
	rb_root->rb_node = chain_root->node.rb_root.rb_node;
119 120
}

121 122
static void __sort_chain_graph_rel(struct callchain_node *node,
				   double min_percent)
123 124
{
	struct callchain_node *child;
125
	u64 min_hit;
126 127

	node->rb_root = RB_ROOT;
128
	min_hit = ceil(node->children_hit * min_percent);
129 130

	chain_for_each_child(child, node) {
131
		__sort_chain_graph_rel(child, min_percent);
132
		if (cumul_hits(child) >= min_hit)
133 134
			rb_insert_callchain(&node->rb_root, child,
					    CHAIN_GRAPH_REL);
135 136 137
	}
}

138
static void
139
sort_chain_graph_rel(struct rb_root *rb_root, struct callchain_root *chain_root,
140
		     u64 min_hit __used, struct callchain_param *param)
141
{
142 143
	__sort_chain_graph_rel(&chain_root->node, param->min_percent / 100.0);
	rb_root->rb_node = chain_root->node.rb_root.rb_node;
144 145
}

146 147 148 149 150 151 152 153 154 155 156 157
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;
158
	case CHAIN_NONE:
159 160 161 162 163 164
	default:
		return -1;
	}
	return 0;
}

165 166 167 168 169 170
/*
 * 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)
171 172 173
{
	struct callchain_node *new;

174
	new = zalloc(sizeof(*new));
175 176 177 178 179 180 181
	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);
182 183 184 185 186 187 188

	if (inherit_children) {
		struct callchain_node *next;

		list_splice(&parent->children, &new->children);
		INIT_LIST_HEAD(&parent->children);

189
		chain_for_each_child(next, new)
190 191
			next->parent = new;
	}
192 193 194 195 196
	list_add_tail(&new->brothers, &parent->children);

	return new;
}

197 198

struct resolved_ip {
199 200
	u64		  ip;
	struct map_symbol ms;
201 202 203 204 205 206 207 208
};

struct resolved_chain {
	u64			nr;
	struct resolved_ip	ips[0];
};


209 210 211
/*
 * Fill the node with callchain values
 */
212
static void
213
fill_node(struct callchain_node *node, struct resolved_chain *chain, int start)
214
{
215
	unsigned int i;
216 217 218 219

	for (i = start; i < chain->nr; i++) {
		struct callchain_list *call;

220
		call = zalloc(sizeof(*call));
221 222 223 224
		if (!call) {
			perror("not enough memory for the code path tree");
			return;
		}
225
		call->ip = chain->ips[i].ip;
226
		call->ms = chain->ips[i].ms;
227 228
		list_add_tail(&call->list, &node->val);
	}
229 230
	node->val_nr = chain->nr - start;
	if (!node->val_nr)
231
		pr_warning("Warning: empty node in callchain tree\n");
232 233
}

234
static void
235
add_child(struct callchain_node *parent, struct resolved_chain *chain,
236
	  int start, u64 period)
237 238 239
{
	struct callchain_node *new;

240
	new = create_child(parent, false);
241
	fill_node(new, chain, start);
242

243
	new->children_hit = 0;
244
	new->hit = period;
245 246
}

247 248 249 250 251
/*
 * 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
 */
252
static void
253
split_add_child(struct callchain_node *parent, struct resolved_chain *chain,
254 255
		struct callchain_list *to_split, int idx_parents, int idx_local,
		u64 period)
256 257
{
	struct callchain_node *new;
258
	struct list_head *old_tail;
259
	unsigned int idx_total = idx_parents + idx_local;
260 261

	/* split */
262 263 264 265 266 267 268 269 270
	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;
271

272 273
	/* split the hits */
	new->hit = parent->hit;
274 275
	new->children_hit = parent->children_hit;
	parent->children_hit = cumul_hits(new);
276 277 278 279 280 281
	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;
282 283
		add_child(parent, chain, idx_total, period);
		parent->children_hit += period;
284
	} else {
285
		parent->hit = period;
286
	}
287 288 289
}

static int
290 291
append_chain(struct callchain_node *root, struct resolved_chain *chain,
	     unsigned int start, u64 period);
292

293
static void
294 295
append_chain_children(struct callchain_node *root, struct resolved_chain *chain,
		      unsigned int start, u64 period)
296 297 298 299
{
	struct callchain_node *rnode;

	/* lookup in childrens */
300
	chain_for_each_child(rnode, root) {
301
		unsigned int ret = append_chain(rnode, chain, start, period);
302

303
		if (!ret)
304
			goto inc_children_hit;
305
	}
306
	/* nothing in children, add to the current node */
307
	add_child(root, chain, start, period);
308

309
inc_children_hit:
310
	root->children_hit += period;
311 312 313
}

static int
314 315
append_chain(struct callchain_node *root, struct resolved_chain *chain,
	     unsigned int start, u64 period)
316 317
{
	struct callchain_list *cnode;
318
	unsigned int i = start;
319 320
	bool found = false;

321 322 323 324 325
	/*
	 * Lookup in the current node
	 * If we have a symbol, then compare the start to match
	 * anywhere inside a function.
	 */
326
	list_for_each_entry(cnode, &root->val, list) {
327 328
		struct symbol *sym;

329 330
		if (i == chain->nr)
			break;
331

332
		sym = chain->ips[i].ms.sym;
333

334 335
		if (cnode->ms.sym && sym) {
			if (cnode->ms.sym->start != sym->start)
336
				break;
337
		} else if (cnode->ip != chain->ips[i].ip)
338
			break;
339

340 341
		if (!found)
			found = true;
342
		i++;
343 344 345 346 347 348 349
	}

	/* 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 */
350
	if (i - start < root->val_nr) {
351
		split_add_child(root, chain, cnode, start, i - start, period);
352 353 354 355
		return 0;
	}

	/* we match 100% of the path, increment the hit */
356
	if (i - start == root->val_nr && i == chain->nr) {
357
		root->hit += period;
358 359 360
		return 0;
	}

361
	/* We match the node and still have a part remaining */
362
	append_chain_children(root, chain, i, period);
363 364

	return 0;
365 366
}

367 368
static void filter_context(struct ip_callchain *old, struct resolved_chain *new,
			   struct map_symbol *syms)
369 370 371 372 373 374 375 376
{
	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];
377
		new->ips[j].ms = syms[i];
378 379 380 381 382 383 384
		j++;
	}

	new->nr = j;
}


385 386
int callchain_append(struct callchain_root *root, struct ip_callchain *chain,
		     struct map_symbol *syms, u64 period)
387
{
388 389
	struct resolved_chain *filtered;

390
	if (!chain->nr)
391 392
		return 0;

393
	filtered = zalloc(sizeof(*filtered) +
394 395 396 397 398 399 400 401 402
			  chain->nr * sizeof(struct resolved_ip));
	if (!filtered)
		return -ENOMEM;

	filter_context(chain, filtered, syms);

	if (!filtered->nr)
		goto end;

403
	append_chain_children(&root->node, filtered, 0, period);
404 405 406

	if (filtered->nr > root->max_depth)
		root->max_depth = filtered->nr;
407 408 409 410
end:
	free(filtered);

	return 0;
411
}
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464

static int
merge_chain_branch(struct callchain_node *dst, struct callchain_node *src,
		   struct resolved_chain *chain)
{
	struct callchain_node *child, *next_child;
	struct callchain_list *list, *next_list;
	int old_pos = chain->nr;
	int err = 0;

	list_for_each_entry_safe(list, next_list, &src->val, list) {
		chain->ips[chain->nr].ip = list->ip;
		chain->ips[chain->nr].ms = list->ms;
		chain->nr++;
		list_del(&list->list);
		free(list);
	}

	if (src->hit)
		append_chain_children(dst, chain, 0, src->hit);

	chain_for_each_child_safe(child, next_child, src) {
		err = merge_chain_branch(dst, child, chain);
		if (err)
			break;

		list_del(&child->brothers);
		free(child);
	}

	chain->nr = old_pos;

	return err;
}

int callchain_merge(struct callchain_root *dst, struct callchain_root *src)
{
	struct resolved_chain *chain;
	int err;

	chain = malloc(sizeof(*chain) +
		       src->max_depth * sizeof(struct resolved_ip));
	if (!chain)
		return -ENOMEM;

	chain->nr = 0;

	err = merge_chain_branch(&dst->node, &src->node, chain);

	free(chain);

	return err;
}