diff options
Diffstat (limited to 'tools/perf/ui/stdio/hist.c')
| -rw-r--r-- | tools/perf/ui/stdio/hist.c | 752 |
1 files changed, 603 insertions, 149 deletions
diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index ae7a75432249..8c4c8925df2c 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -1,16 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <limits.h> #include <stdio.h> +#include <stdlib.h> +#include <linux/string.h> -#include "../../util/util.h" +#include "../../util/callchain.h" +#include "../../util/debug.h" +#include "../../util/event.h" #include "../../util/hist.h" +#include "../../util/map.h" +#include "../../util/maps.h" +#include "../../util/symbol.h" #include "../../util/sort.h" #include "../../util/evsel.h" - +#include "../../util/srcline.h" +#include "../../util/string2.h" +#include "../../util/thread.h" +#include "../../util/block-info.h" +#include <linux/ctype.h> +#include <linux/zalloc.h> static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin) { int i; int ret = fprintf(fp, " "); + if (left_margin > USHRT_MAX) + left_margin = USHRT_MAX; + for (i = 0; i < left_margin; i++) ret += fprintf(fp, " "); @@ -34,13 +51,16 @@ static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask, return ret; } -static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, +static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_node *node, + struct callchain_list *chain, int depth, int depth_mask, int period, - u64 total_samples, u64 hits, - int left_margin) + u64 total_samples, int left_margin) { int i; size_t ret = 0; + char bf[1024], *alloc_str = NULL; + char buf[64]; + const char *str; ret += callchain__fprintf_left_margin(fp, left_margin); for (i = 0; i < depth; i++) { @@ -49,17 +69,28 @@ static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, else ret += fprintf(fp, " "); if (!period && i == depth - 1) { - double percent; - - percent = hits * 100.0 / total_samples; - ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent); + ret += fprintf(fp, "--"); + ret += callchain_node__fprintf_value(node, fp, total_samples); + ret += fprintf(fp, "--"); } else ret += fprintf(fp, "%s", " "); } - if (chain->ms.sym) - ret += fprintf(fp, "%s\n", chain->ms.sym->name); - else - ret += fprintf(fp, "0x%0" PRIx64 "\n", chain->ip); + + str = callchain_list__sym_name(chain, bf, sizeof(bf), false); + + if (symbol_conf.show_branchflag_count) { + callchain_list_counts__printf_value(chain, NULL, + buf, sizeof(buf)); + + if (asprintf(&alloc_str, "%s%s", str, buf) < 0) + str = "Not enough memory!"; + else + str = alloc_str; + } + + fputs(str, fp); + fputc('\n', fp); + free(alloc_str); return ret; } @@ -84,13 +115,14 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, int depth_mask, int left_margin) { struct rb_node *node, *next; - struct callchain_node *child; + struct callchain_node *child = NULL; struct callchain_list *chain; int new_depth_mask = depth_mask; u64 remaining; size_t ret = 0; int i; uint entries_printed = 0; + int cumul_count = 0; remaining = total_samples; @@ -102,6 +134,7 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, child = rb_entry(node, struct callchain_node, rb_node); cumul = callchain_cumul_hits(child); remaining -= cumul; + cumul_count += callchain_cumul_counts(child); /* * The depth mask manages the output of pipes that show @@ -122,10 +155,9 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, left_margin); i = 0; list_for_each_entry(chain, &child->val, list) { - ret += ipchain__fprintf_graph(fp, chain, depth, + ret += ipchain__fprintf_graph(fp, child, chain, depth, new_depth_mask, i++, total_samples, - cumul, left_margin); } @@ -145,21 +177,50 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, if (callchain_param.mode == CHAIN_GRAPH_REL && remaining && remaining != total_samples) { + struct callchain_node rem_node = { + .hit = remaining, + }; if (!rem_sq_bracket) return ret; + if (callchain_param.value == CCVAL_COUNT && child && child->parent) { + rem_node.count = child->parent->children_count - cumul_count; + if (rem_node.count <= 0) + return ret; + } + new_depth_mask &= ~(1 << (depth - 1)); - ret += ipchain__fprintf_graph(fp, &rem_hits, depth, + ret += ipchain__fprintf_graph(fp, &rem_node, &rem_hits, depth, new_depth_mask, 0, total_samples, - remaining, left_margin); + left_margin); } return ret; } +/* + * If have one single callchain root, don't bother printing + * its percentage (100 % in fractal mode and the same percentage + * than the hist in graph mode). This also avoid one level of column. + * + * However when percent-limit applied, it's possible that single callchain + * node have different (non-100% in fractal mode) percentage. + */ +static bool need_percent_display(struct rb_node *node, u64 parent_samples) +{ + struct callchain_node *cnode; + + if (rb_next(node)) + return true; + + cnode = rb_entry(node, struct callchain_node, rb_node); + return callchain_cumul_hits(cnode) != parent_samples; +} + static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, - u64 total_samples, int left_margin) + u64 total_samples, u64 parent_samples, + int left_margin) { struct callchain_node *cnode; struct callchain_list *chain; @@ -168,14 +229,10 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, struct rb_node *node; int i = 0; int ret = 0; + char bf[1024]; - /* - * If have one single callchain root, don't bother printing - * its percentage (100 % in fractal mode and the same percentage - * than the hist in graph mode). This also avoid one level of column. - */ node = rb_first(root); - if (node && !rb_next(node)) { + if (node && !need_percent_display(node, parent_samples)) { cnode = rb_entry(node, struct callchain_node, rb_node); list_for_each_entry(chain, &cnode->val, list) { /* @@ -183,8 +240,10 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, * the symbol. No need to print it otherwise it appears as * displayed twice. */ - if (!i++ && sort__first_dimension == SORT_SYM) + if (!i++ && field_order == NULL && + sort_order && strstarts(sort_order, "sym")) continue; + if (!printed) { ret += callchain__fprintf_left_margin(fp, left_margin); ret += fprintf(fp, "|\n"); @@ -195,10 +254,15 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, } else ret += callchain__fprintf_left_margin(fp, left_margin); - if (chain->ms.sym) - ret += fprintf(fp, " %s\n", chain->ms.sym->name); - else - ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); + ret += fprintf(fp, "%s", + callchain_list__sym_name(chain, bf, + sizeof(bf), + false)); + + if (symbol_conf.show_branchflag_count) + ret += callchain_list_counts__printf_value( + chain, fp, NULL, 0); + ret += fprintf(fp, "\n"); if (++entries_printed == callchain_param.print_limit) break; @@ -206,55 +270,56 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, root = &cnode->rb_root; } + if (callchain_param.mode == CHAIN_GRAPH_REL) + total_samples = parent_samples; + ret += __callchain__fprintf_graph(fp, root, total_samples, 1, 1, left_margin); - ret += fprintf(fp, "\n"); + if (ret) { + /* do not add a blank line if it printed nothing */ + ret += fprintf(fp, "\n"); + } return ret; } -static size_t __callchain__fprintf_flat(FILE *fp, - struct callchain_node *self, +static size_t __callchain__fprintf_flat(FILE *fp, struct callchain_node *node, u64 total_samples) { struct callchain_list *chain; size_t ret = 0; + char bf[1024]; - if (!self) + if (!node) return 0; - ret += __callchain__fprintf_flat(fp, self->parent, total_samples); + ret += __callchain__fprintf_flat(fp, node->parent, total_samples); - list_for_each_entry(chain, &self->val, list) { + list_for_each_entry(chain, &node->val, list) { if (chain->ip >= PERF_CONTEXT_MAX) continue; - if (chain->ms.sym) - ret += fprintf(fp, " %s\n", chain->ms.sym->name); - else - ret += fprintf(fp, " %p\n", - (void *)(long)chain->ip); + ret += fprintf(fp, " %s\n", callchain_list__sym_name(chain, + bf, sizeof(bf), false)); } return ret; } -static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *self, +static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *tree, u64 total_samples) { size_t ret = 0; u32 entries_printed = 0; - struct rb_node *rb_node; struct callchain_node *chain; + struct rb_node *rb_node = rb_first(tree); - rb_node = rb_first(self); while (rb_node) { - double percent; - chain = rb_entry(rb_node, struct callchain_node, rb_node); - percent = chain->hit * 100.0 / total_samples; - ret = percent_color_fprintf(fp, " %6.2f%%\n", percent); + ret += fprintf(fp, " "); + ret += callchain_node__fprintf_value(chain, fp, total_samples); + ret += fprintf(fp, "\n"); ret += __callchain__fprintf_flat(fp, chain, total_samples); ret += fprintf(fp, "\n"); if (++entries_printed == callchain_param.print_limit) @@ -266,22 +331,81 @@ static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *self, return ret; } +static size_t __callchain__fprintf_folded(FILE *fp, struct callchain_node *node) +{ + const char *sep = symbol_conf.field_sep ?: ";"; + struct callchain_list *chain; + size_t ret = 0; + char bf[1024]; + bool first; + + if (!node) + return 0; + + ret += __callchain__fprintf_folded(fp, node->parent); + + first = (ret == 0); + list_for_each_entry(chain, &node->val, list) { + if (chain->ip >= PERF_CONTEXT_MAX) + continue; + ret += fprintf(fp, "%s%s", first ? "" : sep, + callchain_list__sym_name(chain, + bf, sizeof(bf), false)); + first = false; + } + + return ret; +} + +static size_t callchain__fprintf_folded(FILE *fp, struct rb_root *tree, + u64 total_samples) +{ + size_t ret = 0; + u32 entries_printed = 0; + struct callchain_node *chain; + struct rb_node *rb_node = rb_first(tree); + + while (rb_node) { + + chain = rb_entry(rb_node, struct callchain_node, rb_node); + + ret += callchain_node__fprintf_value(chain, fp, total_samples); + ret += fprintf(fp, " "); + ret += __callchain__fprintf_folded(fp, chain); + ret += fprintf(fp, "\n"); + if (++entries_printed == callchain_param.print_limit) + break; + + rb_node = rb_next(rb_node); + } + + return ret; +} + static size_t hist_entry_callchain__fprintf(struct hist_entry *he, u64 total_samples, int left_margin, FILE *fp) { + u64 parent_samples = he->stat.period; + + if (symbol_conf.cumulate_callchain) + parent_samples = he->stat_acc->period; + switch (callchain_param.mode) { case CHAIN_GRAPH_REL: - return callchain__fprintf_graph(fp, &he->sorted_chain, he->stat.period, - left_margin); + return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, + parent_samples, left_margin); break; case CHAIN_GRAPH_ABS: return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, - left_margin); + parent_samples, left_margin); break; case CHAIN_FLAT: return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples); break; + case CHAIN_FOLDED: + return callchain__fprintf_folded(fp, &he->sorted_chain, total_samples); + break; case CHAIN_NONE: break; default: @@ -291,177 +415,501 @@ static size_t hist_entry_callchain__fprintf(struct hist_entry *he, return 0; } -static size_t hist_entry__callchain_fprintf(struct hist_entry *he, - struct hists *hists, - FILE *fp) +int __hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp, + struct perf_hpp_list *hpp_list) { - int left_margin = 0; - u64 total_period = hists->stats.total_period; + const char *sep = symbol_conf.field_sep; + struct perf_hpp_fmt *fmt; + char *start = hpp->buf; + int ret; + bool first = true; + + if (symbol_conf.exclude_other && !he->parent) + return 0; + + perf_hpp_list__for_each_format(hpp_list, fmt) { + if (perf_hpp__should_skip(fmt, he->hists)) + continue; + + /* + * If there's no field_sep, we still need + * to display initial ' '. + */ + if (!sep || !first) { + ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " "); + advance_hpp(hpp, ret); + } else + first = false; + + if (perf_hpp__use_color() && fmt->color) + ret = fmt->color(fmt, hpp, he); + else + ret = fmt->entry(fmt, hpp, he); + + ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret); + advance_hpp(hpp, ret); + } + + return hpp->buf - start; +} + +static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp) +{ + return __hist_entry__snprintf(he, hpp, he->hists->hpp_list); +} + +static int hist_entry__hierarchy_fprintf(struct hist_entry *he, + struct perf_hpp *hpp, + struct hists *hists, + FILE *fp) +{ + const char *sep = symbol_conf.field_sep; + struct perf_hpp_fmt *fmt; + struct perf_hpp_list_node *fmt_node; + char *buf = hpp->buf; + size_t size = hpp->size; + int ret, printed = 0; + bool first = true; + + if (symbol_conf.exclude_other && !he->parent) + return 0; + + ret = scnprintf(hpp->buf, hpp->size, "%*s", he->depth * HIERARCHY_INDENT, ""); + advance_hpp(hpp, ret); + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + /* + * If there's no field_sep, we still need + * to display initial ' '. + */ + if (!sep || !first) { + ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " "); + advance_hpp(hpp, ret); + } else + first = false; + + if (perf_hpp__use_color() && fmt->color) + ret = fmt->color(fmt, hpp, he); + else + ret = fmt->entry(fmt, hpp, he); + + ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret); + advance_hpp(hpp, ret); + } + + if (!sep) + ret = scnprintf(hpp->buf, hpp->size, "%*s", + (hists->nr_hpp_node - 2) * HIERARCHY_INDENT, ""); + advance_hpp(hpp, ret); + + printed += fprintf(fp, "%s", buf); + + perf_hpp_list__for_each_format(he->hpp_list, fmt) { + hpp->buf = buf; + hpp->size = size; + + /* + * No need to call hist_entry__snprintf_alignment() since this + * fmt is always the last column in the hierarchy mode. + */ + if (perf_hpp__use_color() && fmt->color) + fmt->color(fmt, hpp, he); + else + fmt->entry(fmt, hpp, he); + + /* + * dynamic entries are right-aligned but we want left-aligned + * in the hierarchy mode + */ + printed += fprintf(fp, "%s%s", sep ?: " ", skip_spaces(buf)); + } + printed += putc('\n', fp); + + if (he->leaf && hist_entry__has_callchains(he) && symbol_conf.use_callchain) { + u64 total = hists__total_period(hists); - if (sort__first_dimension == SORT_COMM) { - struct sort_entry *se = list_first_entry(&hist_entry__sort_list, - typeof(*se), list); - left_margin = hists__col_len(hists, se->se_width_idx); - left_margin -= thread__comm_len(he->thread); + printed += hist_entry_callchain__fprintf(he, total, 0, fp); + goto out; } - return hist_entry_callchain__fprintf(he, total_period, left_margin, fp); +out: + return printed; +} + +static int hist_entry__block_fprintf(struct hist_entry *he, + char *bf, size_t size, + FILE *fp) +{ + struct block_hist *bh = container_of(he, struct block_hist, he); + int ret = 0; + + for (unsigned int i = 0; i < bh->block_hists.nr_entries; i++) { + struct perf_hpp hpp = { + .buf = bf, + .size = size, + .skip = false, + }; + + bh->block_idx = i; + hist_entry__snprintf(he, &hpp); + + if (!hpp.skip) + ret += fprintf(fp, "%s\n", bf); + } + + return ret; +} + +static int hist_entry__individual_block_fprintf(struct hist_entry *he, + char *bf, size_t size, + FILE *fp) +{ + int ret = 0; + + struct perf_hpp hpp = { + .buf = bf, + .size = size, + .skip = false, + }; + + hist_entry__snprintf(he, &hpp); + if (!hpp.skip) + ret += fprintf(fp, "%s\n", bf); + + return ret; } static int hist_entry__fprintf(struct hist_entry *he, size_t size, - struct hists *hists, FILE *fp) + char *bf, size_t bfsz, FILE *fp, + bool ignore_callchains) { - char bf[512]; int ret; + int callchain_ret = 0; struct perf_hpp hpp = { .buf = bf, .size = size, }; - bool color = !symbol_conf.field_sep; + struct hists *hists = he->hists; + u64 total_period = hists->stats.total_period; + + if (size == 0 || size > bfsz) + size = hpp.size = bfsz; - if (size == 0 || size > sizeof(bf)) - size = hpp.size = sizeof(bf); + if (symbol_conf.report_hierarchy) + return hist_entry__hierarchy_fprintf(he, &hpp, hists, fp); - ret = hist_entry__period_snprintf(&hpp, he, color); - hist_entry__sort_snprintf(he, bf + ret, size - ret, hists); + if (symbol_conf.report_block) + return hist_entry__block_fprintf(he, bf, size, fp); + + if (symbol_conf.report_individual_block) + return hist_entry__individual_block_fprintf(he, bf, size, fp); + + hist_entry__snprintf(he, &hpp); ret = fprintf(fp, "%s\n", bf); - if (symbol_conf.use_callchain) - ret += hist_entry__callchain_fprintf(he, hists, fp); + if (hist_entry__has_callchains(he) && !ignore_callchains) + callchain_ret = hist_entry_callchain__fprintf(he, total_period, + 0, fp); + + ret += callchain_ret; return ret; } -size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, - int max_cols, float min_pcnt, FILE *fp) +static int print_hierarchy_indent(const char *sep, int indent, + const char *line, FILE *fp) +{ + int width; + + if (sep != NULL || indent < 2) + return 0; + + width = (indent - 2) * HIERARCHY_INDENT; + + return fprintf(fp, "%-*.*s", width, width, line); +} + +static int hists__fprintf_hierarchy_headers(struct hists *hists, + struct perf_hpp *hpp, FILE *fp) { + bool first_node, first_col; + int indent; + int depth; + unsigned width = 0; + unsigned header_width = 0; struct perf_hpp_fmt *fmt; - struct sort_entry *se; - struct rb_node *nd; - size_t ret = 0; - unsigned int width; + struct perf_hpp_list_node *fmt_node; + struct perf_hpp_list *hpp_list = hists->hpp_list; const char *sep = symbol_conf.field_sep; - const char *col_width = symbol_conf.col_width_list_str; - int nr_rows = 0; - char bf[96]; - struct perf_hpp dummy_hpp = { - .buf = bf, - .size = sizeof(bf), - .ptr = hists_to_evsel(hists), - }; - bool first = true; - init_rem_hits(); + indent = hists->nr_hpp_node; + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + + for (int line = 0; line < hpp_list->nr_header_lines; line++) { + /* first # is displayed one level up */ + if (line) + fprintf(fp, "# "); + + /* preserve max indent depth for column headers */ + print_hierarchy_indent(sep, indent, " ", fp); + + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + fmt->header(fmt, hpp, hists, line, NULL); + fprintf(fp, "%s%s", hpp->buf, sep ?: " "); + } + + if (line < hpp_list->nr_header_lines - 1) + goto next_line; + + /* combine sort headers with ' / ' */ + first_node = true; + list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { + if (!first_node) + header_width += fprintf(fp, " / "); + first_node = false; + + first_col = true; + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (perf_hpp__should_skip(fmt, hists)) + continue; + + if (!first_col) + header_width += fprintf(fp, "+"); + first_col = false; - if (!show_header) - goto print_entries; + fmt->header(fmt, hpp, hists, line, NULL); + + header_width += fprintf(fp, "%s", strim(hpp->buf)); + } + } + +next_line: + fprintf(fp, "\n"); + } fprintf(fp, "# "); - perf_hpp__for_each_format(fmt) { - if (!first) + /* preserve max indent depth for initial dots */ + print_hierarchy_indent(sep, indent, dots, fp); + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + + first_col = true; + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (!first_col) + fprintf(fp, "%s", sep ?: ".."); + first_col = false; + + width = fmt->width(fmt, hpp, hists); + fprintf(fp, "%.*s", width, dots); + } + + depth = 0; + list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { + first_col = true; + width = depth * HIERARCHY_INDENT; + + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (perf_hpp__should_skip(fmt, hists)) + continue; + + if (!first_col) + width++; /* for '+' sign between column header */ + first_col = false; + + width += fmt->width(fmt, hpp, hists); + } + + if (width > header_width) + header_width = width; + + depth++; + } + + fprintf(fp, "%s%-.*s", sep ?: " ", header_width, dots); + + fprintf(fp, "\n#\n"); + + return 2; +} + +static void fprintf_line(struct hists *hists, struct perf_hpp *hpp, + int line, FILE *fp) +{ + struct perf_hpp_fmt *fmt; + const char *sep = symbol_conf.field_sep; + bool first = true; + int span = 0; + + hists__for_each_format(hists, fmt) { + if (perf_hpp__should_skip(fmt, hists)) + continue; + + if (!first && !span) fprintf(fp, "%s", sep ?: " "); else first = false; - fmt->header(&dummy_hpp); - fprintf(fp, "%s", bf); - } + fmt->header(fmt, hpp, hists, line, &span); - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (se->elide) - continue; - if (sep) { - fprintf(fp, "%c%s", *sep, se->se_header); - continue; - } - width = strlen(se->se_header); - if (symbol_conf.col_width_list_str) { - if (col_width) { - hists__set_col_len(hists, se->se_width_idx, - atoi(col_width)); - col_width = strchr(col_width, ','); - if (col_width) - ++col_width; - } - } - if (!hists__new_col_len(hists, se->se_width_idx, width)) - width = hists__col_len(hists, se->se_width_idx); - fprintf(fp, " %*s", width, se->se_header); + if (!span) + fprintf(fp, "%s", hpp->buf); } +} - fprintf(fp, "\n"); - if (max_rows && ++nr_rows >= max_rows) - goto out; +static int +hists__fprintf_standard_headers(struct hists *hists, + struct perf_hpp *hpp, + FILE *fp) +{ + struct perf_hpp_list *hpp_list = hists->hpp_list; + struct perf_hpp_fmt *fmt; + unsigned int width; + const char *sep = symbol_conf.field_sep; + bool first = true; + int line; + + for (line = 0; line < hpp_list->nr_header_lines; line++) { + /* first # is displayed one level up */ + if (line) + fprintf(fp, "# "); + fprintf_line(hists, hpp, line, fp); + fprintf(fp, "\n"); + } if (sep) - goto print_entries; + return hpp_list->nr_header_lines; first = true; fprintf(fp, "# "); - perf_hpp__for_each_format(fmt) { + hists__for_each_format(hists, fmt) { unsigned int i; + if (perf_hpp__should_skip(fmt, hists)) + continue; + if (!first) fprintf(fp, "%s", sep ?: " "); else first = false; - width = fmt->width(&dummy_hpp); + width = fmt->width(fmt, hpp, hists); for (i = 0; i < width; i++) fprintf(fp, "."); } - list_for_each_entry(se, &hist_entry__sort_list, list) { - unsigned int i; + fprintf(fp, "\n"); + fprintf(fp, "#\n"); + return hpp_list->nr_header_lines + 2; +} - if (se->elide) - continue; +int hists__fprintf_headers(struct hists *hists, FILE *fp) +{ + char bf[1024]; + struct perf_hpp dummy_hpp = { + .buf = bf, + .size = sizeof(bf), + }; - fprintf(fp, " "); - width = hists__col_len(hists, se->se_width_idx); - if (width == 0) - width = strlen(se->se_header); - for (i = 0; i < width; i++) - fprintf(fp, "."); - } + fprintf(fp, "# "); - fprintf(fp, "\n"); - if (max_rows && ++nr_rows >= max_rows) + if (symbol_conf.report_hierarchy) + return hists__fprintf_hierarchy_headers(hists, &dummy_hpp, fp); + else + return hists__fprintf_standard_headers(hists, &dummy_hpp, fp); + +} + +size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, + int max_cols, float min_pcnt, FILE *fp, + bool ignore_callchains) +{ + struct rb_node *nd; + size_t ret = 0; + const char *sep = symbol_conf.field_sep; + int nr_rows = 0; + size_t linesz; + char *line = NULL; + unsigned indent; + + init_rem_hits(); + + hists__reset_column_width(hists); + + if (symbol_conf.col_width_list_str) + perf_hpp__set_user_width(symbol_conf.col_width_list_str); + + if (show_header) + nr_rows += hists__fprintf_headers(hists, fp); + + if (max_rows && nr_rows >= max_rows) goto out; - fprintf(fp, "#\n"); - if (max_rows && ++nr_rows >= max_rows) + linesz = hists__sort_list_width(hists) + 3 + 1; + linesz += perf_hpp__color_overhead(); + line = malloc(linesz); + if (line == NULL) { + ret = -1; goto out; + } + + indent = hists__overhead_width(hists) + 4; -print_entries: - for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { + for (nd = rb_first_cached(&hists->entries); nd; + nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hists->stats.total_period; + float percent; if (h->filtered) continue; + if (symbol_conf.report_individual_block) + percent = block_info__total_cycles_percent(h); + else + percent = hist_entry__get_percent_limit(h); + if (percent < min_pcnt) continue; - ret += hist_entry__fprintf(h, max_cols, hists, fp); + ret += hist_entry__fprintf(h, max_cols, line, linesz, fp, ignore_callchains); if (max_rows && ++nr_rows >= max_rows) - goto out; + break; + + /* + * If all children are filtered out or percent-limited, + * display "no entry >= x.xx%" message. + */ + if (!h->leaf && !hist_entry__has_hierarchy_children(h, min_pcnt)) { + int depth = hists->nr_hpp_node + h->depth + 1; + + print_hierarchy_indent(sep, depth, " ", fp); + fprintf(fp, "%*sno entry >= %.2f%%\n", indent, "", min_pcnt); + + if (max_rows && ++nr_rows >= max_rows) + break; + } if (h->ms.map == NULL && verbose > 1) { - __map_groups__fprintf_maps(&h->thread->mg, - MAP__FUNCTION, verbose, fp); + maps__fprintf(thread__maps(h->thread), fp); fprintf(fp, "%.10s end\n", graph_dotted_line); } } + + free(line); out: - free(rem_sq_bracket); + zfree(&rem_sq_bracket); return ret; } @@ -470,19 +918,25 @@ size_t events_stats__fprintf(struct events_stats *stats, FILE *fp) { int i; size_t ret = 0; + u32 total = stats->nr_events[0]; for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) { const char *name; - if (stats->nr_events[i] == 0) - continue; - name = perf_event__name(i); if (!strcmp(name, "UNKNOWN")) continue; + if (symbol_conf.skip_empty && !stats->nr_events[i]) + continue; - ret += fprintf(fp, "%16s events: %10d\n", name, - stats->nr_events[i]); + if (i && total) { + ret += fprintf(fp, "%20s events: %10d (%4.1f%%)\n", + name, stats->nr_events[i], + 100.0 * stats->nr_events[i] / total); + } else { + ret += fprintf(fp, "%20s events: %10d\n", + name, stats->nr_events[i]); + } } return ret; |
