// 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;
}
Messung V0.5 in Prozent C=96 H=89 G=92
¤ Dauer der Verarbeitung: 0.10 Sekunden
¤
*© Formatika GbR, Deutschland