perf report: Add "Fractal" mode output - support callchains with relative overhead rate

The current callchain displays the overhead rates as absolute:
relative to the total overhead.

This patch provides relative overhead percentage, in which each
branch of the callchain tree is a independant instrumentated object.

This provides a 'fractal' view of the call-chain profile: each
sub-graph looks like a profile in itself - relative to its parent.

You can produce such output by using the "fractal" mode
that you can abbreviate via f, fr, fra, frac, etc...

./perf report -s sym -c fractal

Example:

     8.46%  [k] copy_user_generic_string
                |
                |--52.01%-- generic_file_aio_read
                |          do_sync_read
                |          vfs_read
                |          |
                |          |--97.20%-- sys_pread64
                |          |          system_call_fastpath
                |          |          pread64
                |          |
                |           --2.81%-- sys_read
                |                     system_call_fastpath
                |                     __read
                |
                |--39.85%-- generic_file_buffered_write
                |          __generic_file_aio_write_nolock
                |          generic_file_aio_write
                |          do_sync_write
                |          reiserfs_file_write
                |          vfs_write
                |          |
                |          |--97.05%-- sys_pwrite64
                |          |          system_call_fastpath
                |          |          __pwrite64
                |          |
                |           --2.95%-- sys_write
                |                     system_call_fastpath
                |                     __write_nocancel
[...]

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Anton Blanchard <anton@samba.org>
Cc: Jens Axboe <jens.axboe@oracle.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
LKML-Reference: <1246772361-9960-5-git-send-email-fweisbec@gmail.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c
index 5d244af..9d3c814 100644
--- a/tools/perf/util/callchain.c
+++ b/tools/perf/util/callchain.c
@@ -32,13 +32,14 @@
 		rnode = rb_entry(parent, struct callchain_node, rb_node);
 
 		switch (mode) {
-		case FLAT:
+		case CHAIN_FLAT:
 			if (rnode->hit < chain->hit)
 				p = &(*p)->rb_left;
 			else
 				p = &(*p)->rb_right;
 			break;
-		case GRAPH:
+		case CHAIN_GRAPH_ABS: /* Falldown */
+		case CHAIN_GRAPH_REL:
 			if (rnode->cumul_hit < chain->cumul_hit)
 				p = &(*p)->rb_left;
 			else
@@ -53,43 +54,96 @@
 	rb_insert_color(&chain->rb_node, root);
 }
 
-/*
- * Once we get every callchains from the stream, we can now
- * sort them by hit
- */
-void sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node,
-		     u64 min_hit)
+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);
+		__sort_chain_flat(rb_root, child, min_hit);
 
 	if (node->hit && node->hit >= min_hit)
-		rb_insert_callchain(rb_root, node, FLAT);
+		rb_insert_callchain(rb_root, node, CHAIN_FLAT);
 }
 
-static void __sort_chain_graph(struct callchain_node *node, u64 min_hit)
+/*
+ * 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(child, min_hit);
+		__sort_chain_graph_abs(child, min_hit);
 		if (child->cumul_hit >= min_hit)
-			rb_insert_callchain(&node->rb_root, child, GRAPH);
+			rb_insert_callchain(&node->rb_root, child,
+					    CHAIN_GRAPH_ABS);
 	}
 }
 
-void
-sort_chain_graph(struct rb_root *rb_root, struct callchain_node *chain_root,
-		 u64 min_hit)
+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(chain_root, min_hit);
+	__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 = node->cumul_hit * min_percent / 100.0;
+
+	chain_for_each_child(child, node) {
+		__sort_chain_graph_rel(child, min_percent);
+		if (child->cumul_hit >= 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);
+	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;
+	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