root/tools/perf/ui/stdio/hist.c
// SPDX-License-Identifier: GPL-2.0
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/string.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, " ");

        return ret;
}

static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask,
                                          int left_margin)
{
        int i;
        size_t ret = callchain__fprintf_left_margin(fp, left_margin);

        for (i = 0; i < depth; i++)
                if (depth_mask & (1 << i))
                        ret += fprintf(fp, "|          ");
                else
                        ret += fprintf(fp, "           ");

        ret += fprintf(fp, "\n");

        return ret;
}

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, 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++) {
                if (depth_mask & (1 << i))
                        ret += fprintf(fp, "|");
                else
                        ret += fprintf(fp, " ");
                if (!period && i == depth - 1) {
                        ret += fprintf(fp, "--");
                        ret += callchain_node__fprintf_value(node, fp, total_samples);
                        ret += fprintf(fp, "--");
                } else
                        ret += fprintf(fp, "%s", "          ");
        }

        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;
}

static struct symbol *rem_sq_bracket;
static struct callchain_list rem_hits;

static void init_rem_hits(void)
{
        rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6);
        if (!rem_sq_bracket) {
                fprintf(stderr, "Not enough memory to display remaining hits\n");
                return;
        }

        strcpy(rem_sq_bracket->name, "[...]");
        rem_hits.ms.sym = rem_sq_bracket;
}

static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root,
                                         u64 total_samples, int depth,
                                         int depth_mask, int left_margin)
{
        struct rb_node *node, *next;
        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;

        node = rb_first(root);
        while (node) {
                u64 new_total;
                u64 cumul;

                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
                 * the depth. We don't want to keep the pipes of the current
                 * level for the last child of this depth.
                 * Except if we have remaining filtered hits. They will
                 * supersede the last child
                 */
                next = rb_next(node);
                if (!next && (callchain_param.mode != CHAIN_GRAPH_REL || !remaining))
                        new_depth_mask &= ~(1 << (depth - 1));

                /*
                 * But we keep the older depth mask for the line separator
                 * to keep the level link until we reach the last child
                 */
                ret += ipchain__fprintf_graph_line(fp, depth, depth_mask,
                                                   left_margin);
                i = 0;
                list_for_each_entry(chain, &child->val, list) {
                        ret += ipchain__fprintf_graph(fp, child, chain, depth,
                                                      new_depth_mask, i++,
                                                      total_samples,
                                                      left_margin);
                }

                if (callchain_param.mode == CHAIN_GRAPH_REL)
                        new_total = child->children_hit;
                else
                        new_total = total_samples;

                ret += __callchain__fprintf_graph(fp, &child->rb_root, new_total,
                                                  depth + 1,
                                                  new_depth_mask | (1 << depth),
                                                  left_margin);
                node = next;
                if (++entries_printed == callchain_param.print_limit)
                        break;
        }

        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_node, &rem_hits, depth,
                                              new_depth_mask, 0, total_samples,
                                              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, u64 parent_samples,
                                       int left_margin)
{
        struct callchain_node *cnode;
        struct callchain_list *chain;
        u32 entries_printed = 0;
        bool printed = false;
        struct rb_node *node;
        int i = 0;
        int ret = 0;
        char bf[1024];

        node = rb_first(root);
        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) {
                        /*
                         * If we sort by symbol, the first entry is the same than
                         * the symbol. No need to print it otherwise it appears as
                         * displayed twice.
                         */
                        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");
                                ret += callchain__fprintf_left_margin(fp, left_margin);
                                ret += fprintf(fp, "---");
                                left_margin += 3;
                                printed = true;
                        } else
                                ret += callchain__fprintf_left_margin(fp, left_margin);

                        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;
                }
                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);
        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 *node,
                                        u64 total_samples)
{
        struct callchain_list *chain;
        size_t ret = 0;
        char bf[1024];

        if (!node)
                return 0;

        ret += __callchain__fprintf_flat(fp, node->parent, total_samples);


        list_for_each_entry(chain, &node->val, list) {
                if (chain->ip >= PERF_CONTEXT_MAX)
                        continue;
                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 *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 += 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)
                        break;

                rb_node = rb_next(rb_node);
        }

        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, total_samples,
                                                parent_samples, left_margin);
                break;
        case CHAIN_GRAPH_ABS:
                return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples,
                                                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:
                pr_err("Bad callchain mode\n");
        }

        return 0;
}

int __hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp,
                           struct perf_hpp_list *hpp_list)
{
        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);

                printed += hist_entry_callchain__fprintf(he, total, 0, fp);
                goto out;
        }

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,
                               char *bf, size_t bfsz, FILE *fp,
                               bool ignore_callchains)
{
        int ret;
        int callchain_ret = 0;
        struct perf_hpp hpp = {
                .buf            = bf,
                .size           = size,
        };
        struct hists *hists = he->hists;
        u64 total_period = hists->stats.total_period;

        if (size == 0 || size > bfsz)
                size = hpp.size = bfsz;

        if (symbol_conf.report_hierarchy)
                return hist_entry__hierarchy_fprintf(he, &hpp, hists, fp);

        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 (hist_entry__has_callchains(he) && !ignore_callchains)
                callchain_ret = hist_entry_callchain__fprintf(he, total_period,
                                                              0, fp);

        ret += callchain_ret;

        return ret;
}

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 perf_hpp_list_node *fmt_node;
        struct perf_hpp_list *hpp_list = hists->hpp_list;
        const char *sep = symbol_conf.field_sep;

        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;

                                fmt->header(fmt, hpp, hists, line, NULL);

                                header_width += fprintf(fp, "%s", strim(hpp->buf));
                        }
                }

next_line:
                fprintf(fp, "\n");
        }

        fprintf(fp, "# ");

        /* 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(fmt, hpp, hists, line, &span);

                if (!span)
                        fprintf(fp, "%s", hpp->buf);
        }
}

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)
                return hpp_list->nr_header_lines;

        first = true;

        fprintf(fp, "# ");

        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(fmt, hpp, hists);
                for (i = 0; i < width; i++)
                        fprintf(fp, ".");
        }

        fprintf(fp, "\n");
        fprintf(fp, "#\n");
        return hpp_list->nr_header_lines + 2;
}

int hists__fprintf_headers(struct hists *hists, FILE *fp)
{
        char bf[1024];
        struct perf_hpp dummy_hpp = {
                .buf    = bf,
                .size   = sizeof(bf),
        };

        fprintf(fp, "# ");

        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;

        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;

        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;

                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, line, linesz, fp, ignore_callchains);

                if (max_rows && ++nr_rows >= max_rows)
                        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) {
                        maps__fprintf(thread__maps(h->thread), fp);
                        fprintf(fp, "%.10s end\n", graph_dotted_line);
                }
        }

        free(line);
out:
        zfree(&rem_sq_bracket);

        return ret;
}

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;

                name = perf_event__name(i);
                if (!strcmp(name, "UNKNOWN"))
                        continue;
                if (symbol_conf.skip_empty && !stats->nr_events[i])
                        continue;

                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;
}