perf hists browser: Implement hierarchy output
Implement hierarchy mode in TUI. The output is look like stdio but it also supports to fold/unfold children dynamically. Signed-off-by: Namhyung Kim <namhyung@kernel.org> Acked-by: Pekka Enberg <penberg@kernel.org> Cc: Andi Kleen <andi@firstfloor.org> Cc: David Ahern <dsahern@gmail.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Stephane Eranian <eranian@google.com> Cc: Wang Nan <wangnan0@huawei.com> Link: http://lkml.kernel.org/r/1456326830-30456-14-git-send-email-namhyung@kernel.org Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
parent
492b101060
commit
d0506edbec
1 changed files with 268 additions and 22 deletions
|
@ -1260,6 +1260,158 @@ static int hist_browser__show_entry(struct hist_browser *browser,
|
||||||
return printed;
|
return printed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int hist_browser__show_hierarchy_entry(struct hist_browser *browser,
|
||||||
|
struct hist_entry *entry,
|
||||||
|
unsigned short row,
|
||||||
|
int level, int nr_sort_keys)
|
||||||
|
{
|
||||||
|
int printed = 0;
|
||||||
|
int width = browser->b.width;
|
||||||
|
char folded_sign = ' ';
|
||||||
|
bool current_entry = ui_browser__is_current_entry(&browser->b, row);
|
||||||
|
off_t row_offset = entry->row_offset;
|
||||||
|
bool first = true;
|
||||||
|
struct perf_hpp_fmt *fmt;
|
||||||
|
struct hpp_arg arg = {
|
||||||
|
.b = &browser->b,
|
||||||
|
.current_entry = current_entry,
|
||||||
|
};
|
||||||
|
int column = 0;
|
||||||
|
int hierarchy_indent = (nr_sort_keys - 1) * HIERARCHY_INDENT;
|
||||||
|
|
||||||
|
if (current_entry) {
|
||||||
|
browser->he_selection = entry;
|
||||||
|
browser->selection = &entry->ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
hist_entry__init_have_children(entry);
|
||||||
|
folded_sign = hist_entry__folded(entry);
|
||||||
|
arg.folded_sign = folded_sign;
|
||||||
|
|
||||||
|
if (entry->leaf && row_offset) {
|
||||||
|
row_offset--;
|
||||||
|
goto show_callchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
hist_browser__gotorc(browser, row, 0);
|
||||||
|
|
||||||
|
if (current_entry && browser->b.navkeypressed)
|
||||||
|
ui_browser__set_color(&browser->b, HE_COLORSET_SELECTED);
|
||||||
|
else
|
||||||
|
ui_browser__set_color(&browser->b, HE_COLORSET_NORMAL);
|
||||||
|
|
||||||
|
ui_browser__write_nstring(&browser->b, "", level * HIERARCHY_INDENT);
|
||||||
|
width -= level * HIERARCHY_INDENT;
|
||||||
|
|
||||||
|
hists__for_each_format(entry->hists, fmt) {
|
||||||
|
char s[2048];
|
||||||
|
struct perf_hpp hpp = {
|
||||||
|
.buf = s,
|
||||||
|
.size = sizeof(s),
|
||||||
|
.ptr = &arg,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (perf_hpp__should_skip(fmt, entry->hists) ||
|
||||||
|
column++ < browser->b.horiz_scroll)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (perf_hpp__is_sort_entry(fmt) ||
|
||||||
|
perf_hpp__is_dynamic_entry(fmt))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (current_entry && browser->b.navkeypressed) {
|
||||||
|
ui_browser__set_color(&browser->b,
|
||||||
|
HE_COLORSET_SELECTED);
|
||||||
|
} else {
|
||||||
|
ui_browser__set_color(&browser->b,
|
||||||
|
HE_COLORSET_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
ui_browser__printf(&browser->b, "%c", folded_sign);
|
||||||
|
width--;
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
ui_browser__printf(&browser->b, " ");
|
||||||
|
width -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fmt->color) {
|
||||||
|
int ret = fmt->color(fmt, &hpp, entry);
|
||||||
|
hist_entry__snprintf_alignment(entry, &hpp, fmt, ret);
|
||||||
|
/*
|
||||||
|
* fmt->color() already used ui_browser to
|
||||||
|
* print the non alignment bits, skip it (+ret):
|
||||||
|
*/
|
||||||
|
ui_browser__printf(&browser->b, "%s", s + ret);
|
||||||
|
} else {
|
||||||
|
int ret = fmt->entry(fmt, &hpp, entry);
|
||||||
|
hist_entry__snprintf_alignment(entry, &hpp, fmt, ret);
|
||||||
|
ui_browser__printf(&browser->b, "%s", s);
|
||||||
|
}
|
||||||
|
width -= hpp.buf - s;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_browser__write_nstring(&browser->b, "", hierarchy_indent);
|
||||||
|
width -= hierarchy_indent;
|
||||||
|
|
||||||
|
if (column >= browser->b.horiz_scroll) {
|
||||||
|
char s[2048];
|
||||||
|
struct perf_hpp hpp = {
|
||||||
|
.buf = s,
|
||||||
|
.size = sizeof(s),
|
||||||
|
.ptr = &arg,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (current_entry && browser->b.navkeypressed) {
|
||||||
|
ui_browser__set_color(&browser->b,
|
||||||
|
HE_COLORSET_SELECTED);
|
||||||
|
} else {
|
||||||
|
ui_browser__set_color(&browser->b,
|
||||||
|
HE_COLORSET_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_browser__write_nstring(&browser->b, "", 2);
|
||||||
|
width -= 2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* No need to call hist_entry__snprintf_alignment()
|
||||||
|
* since this fmt is always the last column in the
|
||||||
|
* hierarchy mode.
|
||||||
|
*/
|
||||||
|
fmt = entry->fmt;
|
||||||
|
if (fmt->color) {
|
||||||
|
width -= fmt->color(fmt, &hpp, entry);
|
||||||
|
} else {
|
||||||
|
width -= fmt->entry(fmt, &hpp, entry);
|
||||||
|
ui_browser__printf(&browser->b, "%s", s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The scroll bar isn't being used */
|
||||||
|
if (!browser->b.navkeypressed)
|
||||||
|
width += 1;
|
||||||
|
|
||||||
|
ui_browser__write_nstring(&browser->b, "", width);
|
||||||
|
|
||||||
|
++row;
|
||||||
|
++printed;
|
||||||
|
|
||||||
|
show_callchain:
|
||||||
|
if (entry->leaf && folded_sign == '-' && row != browser->b.rows) {
|
||||||
|
struct callchain_print_arg carg = {
|
||||||
|
.row_offset = row_offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
printed += hist_browser__show_callchain(browser, entry,
|
||||||
|
level + 1, row,
|
||||||
|
hist_browser__show_callchain_entry, &carg,
|
||||||
|
hist_browser__check_output_full);
|
||||||
|
}
|
||||||
|
|
||||||
|
return printed;
|
||||||
|
}
|
||||||
|
|
||||||
static int advance_hpp_check(struct perf_hpp *hpp, int inc)
|
static int advance_hpp_check(struct perf_hpp *hpp, int inc)
|
||||||
{
|
{
|
||||||
advance_hpp(hpp, inc);
|
advance_hpp(hpp, inc);
|
||||||
|
@ -1325,6 +1477,7 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser)
|
||||||
u16 header_offset = 0;
|
u16 header_offset = 0;
|
||||||
struct rb_node *nd;
|
struct rb_node *nd;
|
||||||
struct hist_browser *hb = container_of(browser, struct hist_browser, b);
|
struct hist_browser *hb = container_of(browser, struct hist_browser, b);
|
||||||
|
int nr_sort = hb->hists->hpp_list->nr_sort_keys;
|
||||||
|
|
||||||
if (hb->show_headers) {
|
if (hb->show_headers) {
|
||||||
hist_browser__show_headers(hb);
|
hist_browser__show_headers(hb);
|
||||||
|
@ -1335,18 +1488,28 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser)
|
||||||
hb->he_selection = NULL;
|
hb->he_selection = NULL;
|
||||||
hb->selection = NULL;
|
hb->selection = NULL;
|
||||||
|
|
||||||
for (nd = browser->top; nd; nd = rb_next(nd)) {
|
for (nd = browser->top; nd; nd = rb_hierarchy_next(nd)) {
|
||||||
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
||||||
float percent;
|
float percent;
|
||||||
|
|
||||||
if (h->filtered)
|
if (h->filtered) {
|
||||||
|
/* let it move to sibling */
|
||||||
|
h->unfolded = false;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
percent = hist_entry__get_percent_limit(h);
|
percent = hist_entry__get_percent_limit(h);
|
||||||
if (percent < hb->min_pcnt)
|
if (percent < hb->min_pcnt)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
row += hist_browser__show_entry(hb, h, row);
|
if (symbol_conf.report_hierarchy) {
|
||||||
|
row += hist_browser__show_hierarchy_entry(hb, h, row,
|
||||||
|
h->depth,
|
||||||
|
nr_sort);
|
||||||
|
} else {
|
||||||
|
row += hist_browser__show_entry(hb, h, row);
|
||||||
|
}
|
||||||
|
|
||||||
if (row == browser->rows)
|
if (row == browser->rows)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1364,7 +1527,14 @@ static struct rb_node *hists__filter_entries(struct rb_node *nd,
|
||||||
if (!h->filtered && percent >= min_pcnt)
|
if (!h->filtered && percent >= min_pcnt)
|
||||||
return nd;
|
return nd;
|
||||||
|
|
||||||
nd = rb_next(nd);
|
/*
|
||||||
|
* If it's filtered, its all children also were filtered.
|
||||||
|
* So move to sibling node.
|
||||||
|
*/
|
||||||
|
if (rb_next(nd))
|
||||||
|
nd = rb_next(nd);
|
||||||
|
else
|
||||||
|
nd = rb_hierarchy_next(nd);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1380,7 +1550,7 @@ static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
|
||||||
if (!h->filtered && percent >= min_pcnt)
|
if (!h->filtered && percent >= min_pcnt)
|
||||||
return nd;
|
return nd;
|
||||||
|
|
||||||
nd = rb_prev(nd);
|
nd = rb_hierarchy_prev(nd);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1410,8 +1580,8 @@ static void ui_browser__hists_seek(struct ui_browser *browser,
|
||||||
nd = browser->top;
|
nd = browser->top;
|
||||||
goto do_offset;
|
goto do_offset;
|
||||||
case SEEK_END:
|
case SEEK_END:
|
||||||
nd = hists__filter_prev_entries(rb_last(browser->entries),
|
nd = rb_hierarchy_last(rb_last(browser->entries));
|
||||||
hb->min_pcnt);
|
nd = hists__filter_prev_entries(nd, hb->min_pcnt);
|
||||||
first = false;
|
first = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1445,7 +1615,7 @@ do_offset:
|
||||||
if (offset > 0) {
|
if (offset > 0) {
|
||||||
do {
|
do {
|
||||||
h = rb_entry(nd, struct hist_entry, rb_node);
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
||||||
if (h->unfolded) {
|
if (h->unfolded && h->leaf) {
|
||||||
u16 remaining = h->nr_rows - h->row_offset;
|
u16 remaining = h->nr_rows - h->row_offset;
|
||||||
if (offset > remaining) {
|
if (offset > remaining) {
|
||||||
offset -= remaining;
|
offset -= remaining;
|
||||||
|
@ -1457,7 +1627,8 @@ do_offset:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nd = hists__filter_entries(rb_next(nd), hb->min_pcnt);
|
nd = hists__filter_entries(rb_hierarchy_next(nd),
|
||||||
|
hb->min_pcnt);
|
||||||
if (nd == NULL)
|
if (nd == NULL)
|
||||||
break;
|
break;
|
||||||
--offset;
|
--offset;
|
||||||
|
@ -1466,7 +1637,7 @@ do_offset:
|
||||||
} else if (offset < 0) {
|
} else if (offset < 0) {
|
||||||
while (1) {
|
while (1) {
|
||||||
h = rb_entry(nd, struct hist_entry, rb_node);
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
||||||
if (h->unfolded) {
|
if (h->unfolded && h->leaf) {
|
||||||
if (first) {
|
if (first) {
|
||||||
if (-offset > h->row_offset) {
|
if (-offset > h->row_offset) {
|
||||||
offset += h->row_offset;
|
offset += h->row_offset;
|
||||||
|
@ -1490,7 +1661,7 @@ do_offset:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nd = hists__filter_prev_entries(rb_prev(nd),
|
nd = hists__filter_prev_entries(rb_hierarchy_prev(nd),
|
||||||
hb->min_pcnt);
|
hb->min_pcnt);
|
||||||
if (nd == NULL)
|
if (nd == NULL)
|
||||||
break;
|
break;
|
||||||
|
@ -1503,7 +1674,7 @@ do_offset:
|
||||||
* row_offset at its last entry.
|
* row_offset at its last entry.
|
||||||
*/
|
*/
|
||||||
h = rb_entry(nd, struct hist_entry, rb_node);
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
||||||
if (h->unfolded)
|
if (h->unfolded && h->leaf)
|
||||||
h->row_offset = h->nr_rows;
|
h->row_offset = h->nr_rows;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1517,13 +1688,14 @@ do_offset:
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hist_browser__fprintf_callchain(struct hist_browser *browser,
|
static int hist_browser__fprintf_callchain(struct hist_browser *browser,
|
||||||
struct hist_entry *he, FILE *fp)
|
struct hist_entry *he, FILE *fp,
|
||||||
|
int level)
|
||||||
{
|
{
|
||||||
struct callchain_print_arg arg = {
|
struct callchain_print_arg arg = {
|
||||||
.fp = fp,
|
.fp = fp,
|
||||||
};
|
};
|
||||||
|
|
||||||
hist_browser__show_callchain(browser, he, 1, 0,
|
hist_browser__show_callchain(browser, he, level, 0,
|
||||||
hist_browser__fprintf_callchain_entry, &arg,
|
hist_browser__fprintf_callchain_entry, &arg,
|
||||||
hist_browser__check_dump_full);
|
hist_browser__check_dump_full);
|
||||||
return arg.printed;
|
return arg.printed;
|
||||||
|
@ -1566,7 +1738,65 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser,
|
||||||
printed += fprintf(fp, "%s\n", s);
|
printed += fprintf(fp, "%s\n", s);
|
||||||
|
|
||||||
if (folded_sign == '-')
|
if (folded_sign == '-')
|
||||||
printed += hist_browser__fprintf_callchain(browser, he, fp);
|
printed += hist_browser__fprintf_callchain(browser, he, fp, 1);
|
||||||
|
|
||||||
|
return printed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int hist_browser__fprintf_hierarchy_entry(struct hist_browser *browser,
|
||||||
|
struct hist_entry *he,
|
||||||
|
FILE *fp, int level,
|
||||||
|
int nr_sort_keys)
|
||||||
|
{
|
||||||
|
char s[8192];
|
||||||
|
int printed = 0;
|
||||||
|
char folded_sign = ' ';
|
||||||
|
struct perf_hpp hpp = {
|
||||||
|
.buf = s,
|
||||||
|
.size = sizeof(s),
|
||||||
|
};
|
||||||
|
struct perf_hpp_fmt *fmt;
|
||||||
|
bool first = true;
|
||||||
|
int ret;
|
||||||
|
int hierarchy_indent = (nr_sort_keys + 1) * HIERARCHY_INDENT;
|
||||||
|
|
||||||
|
printed = fprintf(fp, "%*s", level * HIERARCHY_INDENT, "");
|
||||||
|
|
||||||
|
folded_sign = hist_entry__folded(he);
|
||||||
|
printed += fprintf(fp, "%c", folded_sign);
|
||||||
|
|
||||||
|
hists__for_each_format(he->hists, fmt) {
|
||||||
|
if (perf_hpp__should_skip(fmt, he->hists))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (perf_hpp__is_sort_entry(fmt) ||
|
||||||
|
perf_hpp__is_dynamic_entry(fmt))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!first) {
|
||||||
|
ret = scnprintf(hpp.buf, hpp.size, " ");
|
||||||
|
advance_hpp(&hpp, ret);
|
||||||
|
} else
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
ret = fmt->entry(fmt, &hpp, he);
|
||||||
|
advance_hpp(&hpp, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = scnprintf(hpp.buf, hpp.size, "%*s", hierarchy_indent, "");
|
||||||
|
advance_hpp(&hpp, ret);
|
||||||
|
|
||||||
|
fmt = he->fmt;
|
||||||
|
ret = fmt->entry(fmt, &hpp, he);
|
||||||
|
advance_hpp(&hpp, ret);
|
||||||
|
|
||||||
|
printed += fprintf(fp, "%s\n", rtrim(s));
|
||||||
|
|
||||||
|
if (he->leaf && folded_sign == '-') {
|
||||||
|
printed += hist_browser__fprintf_callchain(browser, he, fp,
|
||||||
|
he->depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
return printed;
|
return printed;
|
||||||
}
|
}
|
||||||
|
@ -1576,12 +1806,22 @@ static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
|
||||||
struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
|
struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
|
||||||
browser->min_pcnt);
|
browser->min_pcnt);
|
||||||
int printed = 0;
|
int printed = 0;
|
||||||
|
int nr_sort = browser->hists->hpp_list->nr_sort_keys;
|
||||||
|
|
||||||
while (nd) {
|
while (nd) {
|
||||||
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
||||||
|
|
||||||
printed += hist_browser__fprintf_entry(browser, h, fp);
|
if (symbol_conf.report_hierarchy) {
|
||||||
nd = hists__filter_entries(rb_next(nd), browser->min_pcnt);
|
printed += hist_browser__fprintf_hierarchy_entry(browser,
|
||||||
|
h, fp,
|
||||||
|
h->depth,
|
||||||
|
nr_sort);
|
||||||
|
} else {
|
||||||
|
printed += hist_browser__fprintf_entry(browser, h, fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
nd = hists__filter_entries(rb_hierarchy_next(nd),
|
||||||
|
browser->min_pcnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
return printed;
|
return printed;
|
||||||
|
@ -2149,12 +2389,12 @@ static void hist_browser__update_percent_limit(struct hist_browser *hb,
|
||||||
|
|
||||||
hb->min_pcnt = callchain_param.min_percent = percent;
|
hb->min_pcnt = callchain_param.min_percent = percent;
|
||||||
|
|
||||||
if (!symbol_conf.use_callchain)
|
|
||||||
return;
|
|
||||||
|
|
||||||
while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
|
while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
|
||||||
he = rb_entry(nd, struct hist_entry, rb_node);
|
he = rb_entry(nd, struct hist_entry, rb_node);
|
||||||
|
|
||||||
|
if (!he->leaf || !symbol_conf.use_callchain)
|
||||||
|
goto next;
|
||||||
|
|
||||||
if (callchain_param.mode == CHAIN_GRAPH_REL) {
|
if (callchain_param.mode == CHAIN_GRAPH_REL) {
|
||||||
total = he->stat.period;
|
total = he->stat.period;
|
||||||
|
|
||||||
|
@ -2167,11 +2407,17 @@ static void hist_browser__update_percent_limit(struct hist_browser *hb,
|
||||||
callchain_param.sort(&he->sorted_chain, he->callchain,
|
callchain_param.sort(&he->sorted_chain, he->callchain,
|
||||||
min_callchain_hits, &callchain_param);
|
min_callchain_hits, &callchain_param);
|
||||||
|
|
||||||
|
next:
|
||||||
|
/*
|
||||||
|
* Tentatively set unfolded so that the rb_hierarchy_next()
|
||||||
|
* can toggle children of folded entries too.
|
||||||
|
*/
|
||||||
|
he->unfolded = he->has_children;
|
||||||
|
nd = rb_hierarchy_next(nd);
|
||||||
|
|
||||||
/* force to re-evaluate folding state of callchains */
|
/* force to re-evaluate folding state of callchains */
|
||||||
he->init_have_children = false;
|
he->init_have_children = false;
|
||||||
hist_entry__set_folding(he, hb, false);
|
hist_entry__set_folding(he, hb, false);
|
||||||
|
|
||||||
nd = rb_next(nd);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue