|
@@ -32,6 +32,7 @@ struct hist_browser {
|
|
|
bool show_headers;
|
|
|
float min_pcnt;
|
|
|
u64 nr_non_filtered_entries;
|
|
|
+ u64 nr_hierarchy_entries;
|
|
|
u64 nr_callchain_rows;
|
|
|
};
|
|
|
|
|
@@ -58,11 +59,11 @@ static int hist_browser__get_folding(struct hist_browser *browser)
|
|
|
|
|
|
for (nd = rb_first(&hists->entries);
|
|
|
(nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
|
|
|
- nd = rb_next(nd)) {
|
|
|
+ nd = rb_hierarchy_next(nd)) {
|
|
|
struct hist_entry *he =
|
|
|
rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
|
|
- if (he->unfolded)
|
|
|
+ if (he->leaf && he->unfolded)
|
|
|
unfolded_rows += he->nr_rows;
|
|
|
}
|
|
|
return unfolded_rows;
|
|
@@ -72,7 +73,9 @@ static u32 hist_browser__nr_entries(struct hist_browser *hb)
|
|
|
{
|
|
|
u32 nr_entries;
|
|
|
|
|
|
- if (hist_browser__has_filter(hb))
|
|
|
+ if (symbol_conf.report_hierarchy)
|
|
|
+ nr_entries = hb->nr_hierarchy_entries;
|
|
|
+ else if (hist_browser__has_filter(hb))
|
|
|
nr_entries = hb->nr_non_filtered_entries;
|
|
|
else
|
|
|
nr_entries = hb->hists->nr_entries;
|
|
@@ -247,6 +250,35 @@ static int callchain__count_rows(struct rb_root *chain)
|
|
|
return n;
|
|
|
}
|
|
|
|
|
|
+static int hierarchy_count_rows(struct hist_browser *hb, struct hist_entry *he,
|
|
|
+ bool include_children)
|
|
|
+{
|
|
|
+ int count = 0;
|
|
|
+ struct rb_node *node;
|
|
|
+ struct hist_entry *child;
|
|
|
+
|
|
|
+ if (he->leaf)
|
|
|
+ return callchain__count_rows(&he->sorted_chain);
|
|
|
+
|
|
|
+ node = rb_first(&he->hroot_out);
|
|
|
+ while (node) {
|
|
|
+ float percent;
|
|
|
+
|
|
|
+ child = rb_entry(node, struct hist_entry, rb_node);
|
|
|
+ percent = hist_entry__get_percent_limit(child);
|
|
|
+
|
|
|
+ if (!child->filtered && percent >= hb->min_pcnt) {
|
|
|
+ count++;
|
|
|
+
|
|
|
+ if (include_children && child->unfolded)
|
|
|
+ count += hierarchy_count_rows(hb, child, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ node = rb_next(node);
|
|
|
+ }
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
static bool hist_entry__toggle_fold(struct hist_entry *he)
|
|
|
{
|
|
|
if (!he)
|
|
@@ -326,11 +358,17 @@ static void callchain__init_have_children(struct rb_root *root)
|
|
|
|
|
|
static void hist_entry__init_have_children(struct hist_entry *he)
|
|
|
{
|
|
|
- if (!he->init_have_children) {
|
|
|
+ if (he->init_have_children)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (he->leaf) {
|
|
|
he->has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
|
|
|
callchain__init_have_children(&he->sorted_chain);
|
|
|
- he->init_have_children = true;
|
|
|
+ } else {
|
|
|
+ he->has_children = !RB_EMPTY_ROOT(&he->hroot_out);
|
|
|
}
|
|
|
+
|
|
|
+ he->init_have_children = true;
|
|
|
}
|
|
|
|
|
|
static bool hist_browser__toggle_fold(struct hist_browser *browser)
|
|
@@ -349,17 +387,41 @@ static bool hist_browser__toggle_fold(struct hist_browser *browser)
|
|
|
has_children = callchain_list__toggle_fold(cl);
|
|
|
|
|
|
if (has_children) {
|
|
|
+ int child_rows = 0;
|
|
|
+
|
|
|
hist_entry__init_have_children(he);
|
|
|
browser->b.nr_entries -= he->nr_rows;
|
|
|
- browser->nr_callchain_rows -= he->nr_rows;
|
|
|
|
|
|
- if (he->unfolded)
|
|
|
- he->nr_rows = callchain__count_rows(&he->sorted_chain);
|
|
|
+ if (he->leaf)
|
|
|
+ browser->nr_callchain_rows -= he->nr_rows;
|
|
|
else
|
|
|
+ browser->nr_hierarchy_entries -= he->nr_rows;
|
|
|
+
|
|
|
+ if (symbol_conf.report_hierarchy)
|
|
|
+ child_rows = hierarchy_count_rows(browser, he, true);
|
|
|
+
|
|
|
+ if (he->unfolded) {
|
|
|
+ if (he->leaf)
|
|
|
+ he->nr_rows = callchain__count_rows(&he->sorted_chain);
|
|
|
+ else
|
|
|
+ he->nr_rows = hierarchy_count_rows(browser, he, false);
|
|
|
+
|
|
|
+ /* account grand children */
|
|
|
+ if (symbol_conf.report_hierarchy)
|
|
|
+ browser->b.nr_entries += child_rows - he->nr_rows;
|
|
|
+ } else {
|
|
|
+ if (symbol_conf.report_hierarchy)
|
|
|
+ browser->b.nr_entries -= child_rows - he->nr_rows;
|
|
|
+
|
|
|
he->nr_rows = 0;
|
|
|
+ }
|
|
|
|
|
|
browser->b.nr_entries += he->nr_rows;
|
|
|
- browser->nr_callchain_rows += he->nr_rows;
|
|
|
+
|
|
|
+ if (he->leaf)
|
|
|
+ browser->nr_callchain_rows += he->nr_rows;
|
|
|
+ else
|
|
|
+ browser->nr_hierarchy_entries += he->nr_rows;
|
|
|
|
|
|
return true;
|
|
|
}
|
|
@@ -422,13 +484,38 @@ static int callchain__set_folding(struct rb_root *chain, bool unfold)
|
|
|
return n;
|
|
|
}
|
|
|
|
|
|
-static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
|
|
|
+static int hierarchy_set_folding(struct hist_browser *hb, struct hist_entry *he,
|
|
|
+ bool unfold __maybe_unused)
|
|
|
+{
|
|
|
+ float percent;
|
|
|
+ struct rb_node *nd;
|
|
|
+ struct hist_entry *child;
|
|
|
+ int n = 0;
|
|
|
+
|
|
|
+ for (nd = rb_first(&he->hroot_out); nd; nd = rb_next(nd)) {
|
|
|
+ child = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
+ percent = hist_entry__get_percent_limit(child);
|
|
|
+ if (!child->filtered && percent >= hb->min_pcnt)
|
|
|
+ n++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return n;
|
|
|
+}
|
|
|
+
|
|
|
+static void hist_entry__set_folding(struct hist_entry *he,
|
|
|
+ struct hist_browser *hb, bool unfold)
|
|
|
{
|
|
|
hist_entry__init_have_children(he);
|
|
|
he->unfolded = unfold ? he->has_children : false;
|
|
|
|
|
|
if (he->has_children) {
|
|
|
- int n = callchain__set_folding(&he->sorted_chain, unfold);
|
|
|
+ int n;
|
|
|
+
|
|
|
+ if (he->leaf)
|
|
|
+ n = callchain__set_folding(&he->sorted_chain, unfold);
|
|
|
+ else
|
|
|
+ n = hierarchy_set_folding(hb, he, unfold);
|
|
|
+
|
|
|
he->nr_rows = unfold ? n : 0;
|
|
|
} else
|
|
|
he->nr_rows = 0;
|
|
@@ -438,19 +525,32 @@ static void
|
|
|
__hist_browser__set_folding(struct hist_browser *browser, bool unfold)
|
|
|
{
|
|
|
struct rb_node *nd;
|
|
|
- struct hists *hists = browser->hists;
|
|
|
+ struct hist_entry *he;
|
|
|
+ double percent;
|
|
|
|
|
|
- for (nd = rb_first(&hists->entries);
|
|
|
- (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
|
|
|
- nd = rb_next(nd)) {
|
|
|
- struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
- hist_entry__set_folding(he, unfold);
|
|
|
- browser->nr_callchain_rows += he->nr_rows;
|
|
|
+ nd = rb_first(&browser->hists->entries);
|
|
|
+ while (nd) {
|
|
|
+ he = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
+
|
|
|
+ /* set folding state even if it's currently folded */
|
|
|
+ nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD);
|
|
|
+
|
|
|
+ hist_entry__set_folding(he, browser, unfold);
|
|
|
+
|
|
|
+ percent = hist_entry__get_percent_limit(he);
|
|
|
+ if (he->filtered || percent < browser->min_pcnt)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (!he->depth || unfold)
|
|
|
+ browser->nr_hierarchy_entries++;
|
|
|
+ if (he->leaf)
|
|
|
+ browser->nr_callchain_rows += he->nr_rows;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
|
|
|
{
|
|
|
+ browser->nr_hierarchy_entries = 0;
|
|
|
browser->nr_callchain_rows = 0;
|
|
|
__hist_browser__set_folding(browser, unfold);
|
|
|
|
|
@@ -1160,6 +1260,158 @@ static int hist_browser__show_entry(struct hist_browser *browser,
|
|
|
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)
|
|
|
{
|
|
|
advance_hpp(hpp, inc);
|
|
@@ -1199,11 +1451,80 @@ static int hists_browser__scnprintf_headers(struct hist_browser *browser, char *
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *browser, char *buf, size_t size)
|
|
|
+{
|
|
|
+ struct hists *hists = browser->hists;
|
|
|
+ struct perf_hpp dummy_hpp = {
|
|
|
+ .buf = buf,
|
|
|
+ .size = size,
|
|
|
+ };
|
|
|
+ struct perf_hpp_fmt *fmt;
|
|
|
+ size_t ret = 0;
|
|
|
+ int column = 0;
|
|
|
+ int nr_sort_keys = hists->hpp_list->nr_sort_keys;
|
|
|
+ bool first = true;
|
|
|
+
|
|
|
+ ret = scnprintf(buf, size, " ");
|
|
|
+ if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ hists__for_each_format(hists, fmt) {
|
|
|
+ if (column++ < browser->b.horiz_scroll)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (perf_hpp__is_sort_entry(fmt) || perf_hpp__is_dynamic_entry(fmt))
|
|
|
+ break;
|
|
|
+
|
|
|
+ ret = fmt->header(fmt, &dummy_hpp, hists_to_evsel(hists));
|
|
|
+ if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
+ break;
|
|
|
+
|
|
|
+ ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " ");
|
|
|
+ if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "%*s",
|
|
|
+ (nr_sort_keys - 1) * HIERARCHY_INDENT, "");
|
|
|
+ if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ hists__for_each_format(hists, fmt) {
|
|
|
+ if (!perf_hpp__is_sort_entry(fmt) && !perf_hpp__is_dynamic_entry(fmt))
|
|
|
+ continue;
|
|
|
+ if (perf_hpp__should_skip(fmt, hists))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (first) {
|
|
|
+ first = false;
|
|
|
+ } else {
|
|
|
+ ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " / ");
|
|
|
+ if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = fmt->header(fmt, &dummy_hpp, hists_to_evsel(hists));
|
|
|
+ dummy_hpp.buf[ret] = '\0';
|
|
|
+ rtrim(dummy_hpp.buf);
|
|
|
+
|
|
|
+ ret = strlen(dummy_hpp.buf);
|
|
|
+ if (advance_hpp_check(&dummy_hpp, ret))
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
static void hist_browser__show_headers(struct hist_browser *browser)
|
|
|
{
|
|
|
char headers[1024];
|
|
|
|
|
|
- hists_browser__scnprintf_headers(browser, headers, sizeof(headers));
|
|
|
+ if (symbol_conf.report_hierarchy)
|
|
|
+ hists_browser__scnprintf_hierarchy_headers(browser, headers,
|
|
|
+ sizeof(headers));
|
|
|
+ else
|
|
|
+ hists_browser__scnprintf_headers(browser, headers,
|
|
|
+ sizeof(headers));
|
|
|
ui_browser__gotorc(&browser->b, 0, 0);
|
|
|
ui_browser__set_color(&browser->b, HE_COLORSET_ROOT);
|
|
|
ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1);
|
|
@@ -1225,6 +1546,7 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser)
|
|
|
u16 header_offset = 0;
|
|
|
struct rb_node *nd;
|
|
|
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) {
|
|
|
hist_browser__show_headers(hb);
|
|
@@ -1235,18 +1557,28 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser)
|
|
|
hb->he_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);
|
|
|
float percent;
|
|
|
|
|
|
- if (h->filtered)
|
|
|
+ if (h->filtered) {
|
|
|
+ /* let it move to sibling */
|
|
|
+ h->unfolded = false;
|
|
|
continue;
|
|
|
+ }
|
|
|
|
|
|
percent = hist_entry__get_percent_limit(h);
|
|
|
if (percent < hb->min_pcnt)
|
|
|
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)
|
|
|
break;
|
|
|
}
|
|
@@ -1264,7 +1596,14 @@ static struct rb_node *hists__filter_entries(struct rb_node *nd,
|
|
|
if (!h->filtered && percent >= min_pcnt)
|
|
|
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;
|
|
@@ -1280,7 +1619,7 @@ static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
|
|
|
if (!h->filtered && percent >= min_pcnt)
|
|
|
return nd;
|
|
|
|
|
|
- nd = rb_prev(nd);
|
|
|
+ nd = rb_hierarchy_prev(nd);
|
|
|
}
|
|
|
|
|
|
return NULL;
|
|
@@ -1310,8 +1649,8 @@ static void ui_browser__hists_seek(struct ui_browser *browser,
|
|
|
nd = browser->top;
|
|
|
goto do_offset;
|
|
|
case SEEK_END:
|
|
|
- nd = hists__filter_prev_entries(rb_last(browser->entries),
|
|
|
- hb->min_pcnt);
|
|
|
+ nd = rb_hierarchy_last(rb_last(browser->entries));
|
|
|
+ nd = hists__filter_prev_entries(nd, hb->min_pcnt);
|
|
|
first = false;
|
|
|
break;
|
|
|
default:
|
|
@@ -1345,7 +1684,7 @@ do_offset:
|
|
|
if (offset > 0) {
|
|
|
do {
|
|
|
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;
|
|
|
if (offset > remaining) {
|
|
|
offset -= remaining;
|
|
@@ -1357,7 +1696,8 @@ do_offset:
|
|
|
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)
|
|
|
break;
|
|
|
--offset;
|
|
@@ -1366,7 +1706,7 @@ do_offset:
|
|
|
} else if (offset < 0) {
|
|
|
while (1) {
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
- if (h->unfolded) {
|
|
|
+ if (h->unfolded && h->leaf) {
|
|
|
if (first) {
|
|
|
if (-offset > h->row_offset) {
|
|
|
offset += h->row_offset;
|
|
@@ -1390,7 +1730,7 @@ do_offset:
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- nd = hists__filter_prev_entries(rb_prev(nd),
|
|
|
+ nd = hists__filter_prev_entries(rb_hierarchy_prev(nd),
|
|
|
hb->min_pcnt);
|
|
|
if (nd == NULL)
|
|
|
break;
|
|
@@ -1403,7 +1743,7 @@ do_offset:
|
|
|
* row_offset at its last entry.
|
|
|
*/
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
- if (h->unfolded)
|
|
|
+ if (h->unfolded && h->leaf)
|
|
|
h->row_offset = h->nr_rows;
|
|
|
break;
|
|
|
}
|
|
@@ -1417,13 +1757,14 @@ do_offset:
|
|
|
}
|
|
|
|
|
|
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 = {
|
|
|
.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__check_dump_full);
|
|
|
return arg.printed;
|
|
@@ -1466,7 +1807,65 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser,
|
|
|
printed += fprintf(fp, "%s\n", s);
|
|
|
|
|
|
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;
|
|
|
}
|
|
@@ -1476,12 +1875,22 @@ static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
|
|
|
struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
|
|
|
browser->min_pcnt);
|
|
|
int printed = 0;
|
|
|
+ int nr_sort = browser->hists->hpp_list->nr_sort_keys;
|
|
|
|
|
|
while (nd) {
|
|
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
|
|
- printed += hist_browser__fprintf_entry(browser, h, fp);
|
|
|
- nd = hists__filter_entries(rb_next(nd), browser->min_pcnt);
|
|
|
+ if (symbol_conf.report_hierarchy) {
|
|
|
+ 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;
|
|
@@ -2025,17 +2434,18 @@ static void hist_browser__update_nr_entries(struct hist_browser *hb)
|
|
|
u64 nr_entries = 0;
|
|
|
struct rb_node *nd = rb_first(&hb->hists->entries);
|
|
|
|
|
|
- if (hb->min_pcnt == 0) {
|
|
|
+ if (hb->min_pcnt == 0 && !symbol_conf.report_hierarchy) {
|
|
|
hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
|
|
|
nr_entries++;
|
|
|
- nd = rb_next(nd);
|
|
|
+ nd = rb_hierarchy_next(nd);
|
|
|
}
|
|
|
|
|
|
hb->nr_non_filtered_entries = nr_entries;
|
|
|
+ hb->nr_hierarchy_entries = nr_entries;
|
|
|
}
|
|
|
|
|
|
static void hist_browser__update_percent_limit(struct hist_browser *hb,
|
|
@@ -2048,12 +2458,12 @@ static void hist_browser__update_percent_limit(struct hist_browser *hb,
|
|
|
|
|
|
hb->min_pcnt = callchain_param.min_percent = percent;
|
|
|
|
|
|
- if (!symbol_conf.use_callchain)
|
|
|
- return;
|
|
|
-
|
|
|
while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
|
|
|
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) {
|
|
|
total = he->stat.period;
|
|
|
|
|
@@ -2066,11 +2476,17 @@ static void hist_browser__update_percent_limit(struct hist_browser *hb,
|
|
|
callchain_param.sort(&he->sorted_chain, he->callchain,
|
|
|
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 */
|
|
|
he->init_have_children = false;
|
|
|
- hist_entry__set_folding(he, false);
|
|
|
-
|
|
|
- nd = rb_next(nd);
|
|
|
+ hist_entry__set_folding(he, hb, false);
|
|
|
}
|
|
|
}
|
|
|
|