Newer
Older
/*
* builtin-report.c
*
* Builtin report command: Analyze the perf.data input file,
* look up and read DSOs and symbol information and display
* a histogram of results, along various sorting keys.
*/
#include "util/util.h"
#include "util/color.h"
#include <linux/list.h>
#include <linux/rbtree.h>

Arnaldo Carvalho de Melo
committed
#include "util/symbol.h"
#include "util/string.h"
#include "util/callchain.h"
#include "util/values.h"

Arnaldo Carvalho de Melo
committed
#include "util/parse-options.h"
#include "util/parse-events.h"
#include "util/thread.h"
static char const *input_name = "perf.data";
static char default_sort_order[] = "comm,dso,symbol";
static char *sort_order = default_sort_order;
static char *dso_list_str, *comm_list_str, *sym_list_str,
*col_width_list_str;
static struct strlist *dso_list, *comm_list, *sym_list;
static char *field_sep;

Arnaldo Carvalho de Melo
committed
static int input;
static int show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV;
static int full_paths;
static int show_nr_samples;
static int show_threads;
static struct perf_read_values show_threads_values;
static char default_pretty_printing_style[] = "normal";
static char *pretty_printing_style = default_pretty_printing_style;

Arnaldo Carvalho de Melo
committed
static unsigned long page_size;
static unsigned long mmap_window = 32;
static char default_parent_pattern[] = "^sys_|^do_page_fault";
static char *parent_pattern = default_parent_pattern;

Ingo Molnar
committed
static regex_t parent_regex;

Frederic Weisbecker
committed
static char callchain_default_opt[] = "fractal,0.5";
static int callchain;

Frederic Weisbecker
committed
static char __cwd[PATH_MAX];
static char *cwd = __cwd;
static int cwdlen;
static struct rb_root threads;
static struct thread *last_match;
static struct perf_header *header;

Frederic Weisbecker
committed
static
struct callchain_param callchain_param = {

Frederic Weisbecker
committed
.mode = CHAIN_GRAPH_REL,

Frederic Weisbecker
committed
.min_percent = 0.5
};
static int repsep_fprintf(FILE *fp, const char *fmt, ...)
{
int n;
va_list ap;
va_start(ap, fmt);
if (!field_sep)
n = vfprintf(fp, fmt, ap);
else {
char *bf = NULL;
n = vasprintf(&bf, fmt, ap);
if (n > 0) {
char *sep = bf;
while (1) {
sep = strchr(sep, *field_sep);
if (sep == NULL)
break;
*sep = '.';
}
}
fputs(bf, fp);
free(bf);
}
va_end(ap);
return n;
}
static unsigned int dsos__col_width,
comms__col_width,
threads__col_width;
/*
* histogram, sorted on item, collects counts
*/
static struct rb_root hist;
struct hist_entry {
struct rb_node rb_node;
struct thread *thread;
struct map *map;
struct dso *dso;
struct symbol *sym;
struct symbol *parent;
u64 ip;
char level;
struct callchain_node callchain;
struct rb_root sorted_chain;
u64 count;
/*
* configurable sorting bits
*/
struct sort_entry {
struct list_head list;
int64_t (*cmp)(struct hist_entry *, struct hist_entry *);
int64_t (*collapse)(struct hist_entry *, struct hist_entry *);
size_t (*print)(FILE *fp, struct hist_entry *, unsigned int width);
unsigned int *width;
static int64_t cmp_null(void *l, void *r)
{
if (!l && !r)
return 0;
else if (!l)
return -1;
else
return 1;
}
sort__thread_cmp(struct hist_entry *left, struct hist_entry *right)
return right->thread->pid - left->thread->pid;
}
static size_t
sort__thread_print(FILE *fp, struct hist_entry *self, unsigned int width)
return repsep_fprintf(fp, "%*s:%5d", width - 6,
self->thread->comm ?: "", self->thread->pid);
static struct sort_entry sort_thread = {
.header = "Command: Pid",
.cmp = sort__thread_cmp,
.print = sort__thread_print,
.width = &threads__col_width,
static int64_t
sort__comm_cmp(struct hist_entry *left, struct hist_entry *right)
{
return right->thread->pid - left->thread->pid;
}
static int64_t
sort__comm_collapse(struct hist_entry *left, struct hist_entry *right)
{
char *comm_l = left->thread->comm;
char *comm_r = right->thread->comm;
if (!comm_l || !comm_r)
return cmp_null(comm_l, comm_r);
return strcmp(comm_l, comm_r);
}
static size_t
sort__comm_print(FILE *fp, struct hist_entry *self, unsigned int width)
return repsep_fprintf(fp, "%*s", width, self->thread->comm);
}
static struct sort_entry sort_comm = {
.header = "Command",
.cmp = sort__comm_cmp,
.collapse = sort__comm_collapse,
.print = sort__comm_print,
.width = &comms__col_width,
static int64_t
sort__dso_cmp(struct hist_entry *left, struct hist_entry *right)
{
struct dso *dso_l = left->dso;
struct dso *dso_r = right->dso;
if (!dso_l || !dso_r)
return cmp_null(dso_l, dso_r);
return strcmp(dso_l->name, dso_r->name);
}
static size_t
sort__dso_print(FILE *fp, struct hist_entry *self, unsigned int width)
return repsep_fprintf(fp, "%-*s", width, self->dso->name);
return repsep_fprintf(fp, "%*llx", width, (u64)self->ip);
}
static struct sort_entry sort_dso = {
.header = "Shared Object",
.cmp = sort__dso_cmp,
.print = sort__dso_print,
.width = &dsos__col_width,
static int64_t
sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
{
u64 ip_l, ip_r;
if (left->sym == right->sym)
return 0;
ip_l = left->sym ? left->sym->start : left->ip;
ip_r = right->sym ? right->sym->start : right->ip;
return (int64_t)(ip_r - ip_l);
}
sort__sym_print(FILE *fp, struct hist_entry *self, unsigned int width __used)
{
size_t ret = 0;
if (verbose)

Arnaldo Carvalho de Melo
committed
ret += repsep_fprintf(fp, "%#018llx %c ", (u64)self->ip,
dso__symtab_origin(self->dso));
ret += repsep_fprintf(fp, "[%c] ", self->level);
ret += repsep_fprintf(fp, "%s", self->sym->name);
if (self->sym->module)
ret += repsep_fprintf(fp, "\t[%s]",
self->sym->module->name);
ret += repsep_fprintf(fp, "%#016llx", (u64)self->ip);
return ret;
}
static struct sort_entry sort_sym = {
.cmp = sort__sym_cmp,
.print = sort__sym_print,

Ingo Molnar
committed
/* --sort parent */

Ingo Molnar
committed
sort__parent_cmp(struct hist_entry *left, struct hist_entry *right)

Ingo Molnar
committed
struct symbol *sym_l = left->parent;
struct symbol *sym_r = right->parent;
if (!sym_l || !sym_r)
return cmp_null(sym_l, sym_r);
return strcmp(sym_l->name, sym_r->name);
}
static size_t
sort__parent_print(FILE *fp, struct hist_entry *self, unsigned int width)
return repsep_fprintf(fp, "%-*s", width,
self->parent ? self->parent->name : "[other]");
static unsigned int parent_symbol__col_width;

Ingo Molnar
committed
static struct sort_entry sort_parent = {
.header = "Parent symbol",

Ingo Molnar
committed
.cmp = sort__parent_cmp,
.print = sort__parent_print,
.width = &parent_symbol__col_width,

Ingo Molnar
committed
static int sort__has_parent = 0;
struct sort_entry *entry;
int taken;
};
static struct sort_dimension sort_dimensions[] = {
{ .name = "pid", .entry = &sort_thread, },
{ .name = "comm", .entry = &sort_comm, },
{ .name = "dso", .entry = &sort_dso, },
{ .name = "symbol", .entry = &sort_sym, },

Ingo Molnar
committed
{ .name = "parent", .entry = &sort_parent, },
static LIST_HEAD(hist_entry__sort_list);
static int sort_dimension__add(const char *tok)
unsigned int i;
for (i = 0; i < ARRAY_SIZE(sort_dimensions); i++) {
struct sort_dimension *sd = &sort_dimensions[i];
if (sd->taken)
continue;
if (strncasecmp(tok, sd->name, strlen(tok)))
if (sd->entry->collapse)
sort__need_collapse = 1;

Ingo Molnar
committed
if (sd->entry == &sort_parent) {
int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED);
if (ret) {
char err[BUFSIZ];

Ingo Molnar
committed
regerror(ret, &parent_regex, err, sizeof(err));
fprintf(stderr, "Invalid regex: %s\n%s",
parent_pattern, err);

Ingo Molnar
committed
sort__has_parent = 1;
list_add_tail(&sd->entry->list, &hist_entry__sort_list);
sd->taken = 1;
return 0;
}
return -ESRCH;
}
static int64_t
hist_entry__cmp(struct hist_entry *left, struct hist_entry *right)
{
struct sort_entry *se;
int64_t cmp = 0;
list_for_each_entry(se, &hist_entry__sort_list, list) {
cmp = se->cmp(left, right);
if (cmp)
break;
}
return cmp;
}
static int64_t
hist_entry__collapse(struct hist_entry *left, struct hist_entry *right)
{
struct sort_entry *se;
int64_t cmp = 0;
list_for_each_entry(se, &hist_entry__sort_list, list) {
int64_t (*f)(struct hist_entry *, struct hist_entry *);
f = se->collapse ?: se->cmp;
cmp = f(left, right);
if (cmp)
break;
}
return cmp;
}
static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask)
{
int i;
size_t ret = 0;
ret += fprintf(fp, "%s", " ");
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_list *chain, int depth,
int depth_mask, int count, u64 total_samples,
int hits)
{
int i;
size_t ret = 0;
ret += fprintf(fp, "%s", " ");
for (i = 0; i < depth; i++) {
if (depth_mask & (1 << i))
ret += fprintf(fp, "|");
else
ret += fprintf(fp, " ");
if (!count && i == depth - 1) {
double percent;
percent = hits * 100.0 / total_samples;

Frederic Weisbecker
committed
ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent);
} else
ret += fprintf(fp, "%s", " ");
}
if (chain->sym)
ret += fprintf(fp, "%s\n", chain->sym->name);
else
ret += fprintf(fp, "%p\n", (void *)(long)chain->ip);
return ret;
}

Frederic Weisbecker
committed
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.sym = rem_sq_bracket;
}
static size_t
callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
u64 total_samples, int depth, int depth_mask)
{
struct rb_node *node, *next;
struct callchain_node *child;
struct callchain_list *chain;
int new_depth_mask = depth_mask;

Frederic Weisbecker
committed
u64 new_total;

Frederic Weisbecker
committed
u64 remaining;
size_t ret = 0;
int i;

Frederic Weisbecker
committed
if (callchain_param.mode == CHAIN_GRAPH_REL)
new_total = self->children_hit;

Frederic Weisbecker
committed
else
new_total = total_samples;

Frederic Weisbecker
committed
remaining = new_total;
node = rb_first(&self->rb_root);
while (node) {

Frederic Weisbecker
committed
u64 cumul;
child = rb_entry(node, struct callchain_node, rb_node);

Frederic Weisbecker
committed
cumul = cumul_hits(child);
remaining -= cumul;
/*
* The depth mask manages the output of pipes that show
* the depth. We don't want to keep the pipes of the current

Frederic Weisbecker
committed
* 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);

Frederic Weisbecker
committed
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 seperator
* to keep the level link until we reach the last child
*/
ret += ipchain__fprintf_graph_line(fp, depth, depth_mask);
i = 0;
list_for_each_entry(chain, &child->val, list) {
if (chain->ip >= PERF_CONTEXT_MAX)
continue;
ret += ipchain__fprintf_graph(fp, chain, depth,
new_depth_mask, i++,

Frederic Weisbecker
committed
new_total,

Frederic Weisbecker
committed
cumul);

Frederic Weisbecker
committed
ret += callchain__fprintf_graph(fp, child, new_total,
depth + 1,
new_depth_mask | (1 << depth));
node = next;
}

Frederic Weisbecker
committed
if (callchain_param.mode == CHAIN_GRAPH_REL &&
remaining && remaining != new_total) {
if (!rem_sq_bracket)
return ret;
new_depth_mask &= ~(1 << (depth - 1));
ret += ipchain__fprintf_graph(fp, &rem_hits, depth,
new_depth_mask, 0, new_total,
remaining);
}
return ret;
}
static size_t
callchain__fprintf_flat(FILE *fp, struct callchain_node *self,
u64 total_samples)
{
struct callchain_list *chain;
size_t ret = 0;
if (!self)
return 0;
ret += callchain__fprintf_flat(fp, self->parent, total_samples);
list_for_each_entry(chain, &self->val, list) {
if (chain->ip >= PERF_CONTEXT_MAX)
continue;
if (chain->sym)
ret += fprintf(fp, " %s\n", chain->sym->name);
else
ret += fprintf(fp, " %p\n",
(void *)(long)chain->ip);
return ret;
}
static size_t
hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self,
u64 total_samples)
{
struct rb_node *rb_node;
struct callchain_node *chain;
size_t ret = 0;
rb_node = rb_first(&self->sorted_chain);
while (rb_node) {
double percent;
chain = rb_entry(rb_node, struct callchain_node, rb_node);
percent = chain->hit * 100.0 / total_samples;

Frederic Weisbecker
committed
switch (callchain_param.mode) {
case CHAIN_FLAT:

Frederic Weisbecker
committed
ret += percent_color_fprintf(fp, " %6.2f%%\n",
percent);
ret += callchain__fprintf_flat(fp, chain, total_samples);

Frederic Weisbecker
committed
break;
case CHAIN_GRAPH_ABS: /* Falldown */
case CHAIN_GRAPH_REL:
ret += callchain__fprintf_graph(fp, chain,
total_samples, 1, 1);

Frederic Weisbecker
committed
default:
break;
ret += fprintf(fp, "\n");
rb_node = rb_next(rb_node);
}
return ret;
}
hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples)
{
struct sort_entry *se;
size_t ret;
if (exclude_other && !self->parent)
return 0;
if (total_samples)
ret = percent_color_fprintf(fp,
field_sep ? "%.2f" : " %6.2f%%",
(self->count * 100.0) / total_samples);
ret = fprintf(fp, field_sep ? "%lld" : "%12lld ", self->count);
if (show_nr_samples) {
if (field_sep)
fprintf(fp, "%c%lld", *field_sep, self->count);
else
fprintf(fp, "%11lld", self->count);
}
list_for_each_entry(se, &hist_entry__sort_list, list) {
fprintf(fp, "%s", field_sep ?: " ");
ret += se->print(fp, self, se->width ? *se->width : 0);
ret += fprintf(fp, "\n");
if (callchain)
hist_entry_callchain__fprintf(fp, self, total_samples);
return ret;
}
static void dso__calc_col_width(struct dso *self)
{
if (!col_width_list_str && !field_sep &&
(!dso_list || strlist__has_entry(dso_list, self->name))) {
unsigned int slen = strlen(self->name);
if (slen > dsos__col_width)
dsos__col_width = slen;
}
self->slen_calculated = 1;
}
static int thread__set_comm_adjust(struct thread *self, const char *comm)
{
int ret = thread__set_comm(self, comm);
if (ret)
return ret;
if (!col_width_list_str && !field_sep &&
(!comm_list || strlist__has_entry(comm_list, comm))) {
unsigned int slen = strlen(comm);
if (slen > comms__col_width) {
comms__col_width = slen;
threads__col_width = slen + 6;
}
}
return 0;
}
static struct symbol *
resolve_symbol(struct thread *thread, struct map **mapp,
struct dso **dsop, u64 *ipp)
{
struct dso *dso = dsop ? *dsop : NULL;
struct map *map = mapp ? *mapp : NULL;
if (!thread)
return NULL;
if (dso)
goto got_dso;
if (map)
goto got_map;
map = thread__find_map(thread, ip);
if (map != NULL) {
/*
* We have to do this here as we may have a dso
* with no symbol hit that has a name longer than
* the ones with symbols sampled.
*/
if (!sort_dso.elide && !map->dso->slen_calculated)
dso__calc_col_width(map->dso);
if (mapp)
*mapp = map;
got_map:
ip = map->map_ip(map, ip);
dso = map->dso;
} else {
/*
* If this is outside of all known maps,
* and is a negative address, try to look it
* up in the kernel dso, as it might be a
* vsyscall (which executes in user-mode):
*/
if ((long long)ip < 0)
dso = kernel_dso;
}
dump_printf(" ...... dso: %s\n", dso ? dso->name : "<not found>");
dump_printf(" ...... map: %Lx -> %Lx\n", *ipp, ip);
if (dsop)
*dsop = dso;
if (!dso)
return NULL;
got_dso:
return dso->find_symbol(dso, ip);
}
static int call__match(struct symbol *sym)

Ingo Molnar
committed
if (sym->name && !regexec(&parent_regex, sym->name, 0, NULL, 0))
static struct symbol **
resolve_callchain(struct thread *thread, struct map *map __used,
struct ip_callchain *chain, struct hist_entry *entry)
{
u64 context = PERF_CONTEXT_MAX;
struct symbol **syms = NULL;
unsigned int i;
if (callchain) {
syms = calloc(chain->nr, sizeof(*syms));
if (!syms) {
fprintf(stderr, "Can't allocate memory for symbols\n");
exit(-1);
}
}
for (i = 0; i < chain->nr; i++) {
u64 ip = chain->ips[i];
struct dso *dso = NULL;
struct symbol *sym;
if (ip >= PERF_CONTEXT_MAX) {
context = ip;
continue;
}
switch (context) {
case PERF_CONTEXT_HV:
dso = hypervisor_dso;
break;
case PERF_CONTEXT_KERNEL:
dso = kernel_dso;
break;
default:
break;
}
sym = resolve_symbol(thread, NULL, &dso, &ip);
if (sym) {
if (sort__has_parent && call__match(sym) &&
!entry->parent)
entry->parent = sym;
if (!callchain)
break;
syms[i] = sym;
}
}
return syms;
}
/*
* collect histogram counts
*/
static int
hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
struct symbol *sym, u64 ip, struct ip_callchain *chain,
char level, u64 count)

Arnaldo Carvalho de Melo
committed
{
struct rb_node **p = &hist.rb_node;
struct rb_node *parent = NULL;
struct hist_entry *he;
struct symbol **syms = NULL;
struct hist_entry entry = {
.thread = thread,
.map = map,
.dso = dso,
.sym = sym,
.ip = ip,
.level = level,
.count = count,
.sorted_chain = RB_ROOT
if ((sort__has_parent || callchain) && chain)
syms = resolve_callchain(thread, map, chain, &entry);
while (*p != NULL) {
parent = *p;
he = rb_entry(parent, struct hist_entry, rb_node);
cmp = hist_entry__cmp(&entry, he);
if (!cmp) {
he->count += count;
if (callchain) {
append_chain(&he->callchain, chain, syms);
free(syms);
}
return 0;
}
if (cmp < 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
he = malloc(sizeof(*he));
if (!he)
return -ENOMEM;
*he = entry;
if (callchain) {
callchain_init(&he->callchain);
append_chain(&he->callchain, chain, syms);
free(syms);
rb_link_node(&he->rb_node, parent, p);
rb_insert_color(&he->rb_node, &hist);
return 0;

Arnaldo Carvalho de Melo
committed
}
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
static void hist_entry__free(struct hist_entry *he)
{
free(he);
}
/*
* collapse the histogram
*/
static struct rb_root collapse_hists;
static void collapse__insert_entry(struct hist_entry *he)
{
struct rb_node **p = &collapse_hists.rb_node;
struct rb_node *parent = NULL;
struct hist_entry *iter;
int64_t cmp;
while (*p != NULL) {
parent = *p;
iter = rb_entry(parent, struct hist_entry, rb_node);
cmp = hist_entry__collapse(iter, he);
if (!cmp) {
iter->count += he->count;
hist_entry__free(he);
return;
}
if (cmp < 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
rb_link_node(&he->rb_node, parent, p);
rb_insert_color(&he->rb_node, &collapse_hists);
}
static void collapse__resort(void)
{
struct rb_node *next;
struct hist_entry *n;
if (!sort__need_collapse)
return;
next = rb_first(&hist);
while (next) {
n = rb_entry(next, struct hist_entry, rb_node);
next = rb_next(&n->rb_node);
rb_erase(&n->rb_node, &hist);
collapse__insert_entry(n);
}
}
/*
* reverse the map, sort on count.
*/
static struct rb_root output_hists;

Frederic Weisbecker
committed
static void output__insert_entry(struct hist_entry *he, u64 min_callchain_hits)
struct rb_node **p = &output_hists.rb_node;
struct rb_node *parent = NULL;
struct hist_entry *iter;

Frederic Weisbecker
committed
if (callchain)
callchain_param.sort(&he->sorted_chain, &he->callchain,
min_callchain_hits, &callchain_param);
while (*p != NULL) {
parent = *p;
iter = rb_entry(parent, struct hist_entry, rb_node);
if (he->count > iter->count)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
rb_link_node(&he->rb_node, parent, p);
rb_insert_color(&he->rb_node, &output_hists);

Frederic Weisbecker
committed
static void output__resort(u64 total_samples)
struct hist_entry *n;

Frederic Weisbecker
committed
u64 min_callchain_hits;

Frederic Weisbecker
committed
min_callchain_hits = total_samples * (callchain_param.min_percent / 100);
tree = &collapse_hists;
next = rb_first(tree);
while (next) {
n = rb_entry(next, struct hist_entry, rb_node);
next = rb_next(&n->rb_node);

Frederic Weisbecker
committed
output__insert_entry(n, min_callchain_hits);
static size_t output__fprintf(FILE *fp, u64 total_samples)
struct hist_entry *pos;
struct sort_entry *se;
struct rb_node *nd;
size_t ret = 0;
unsigned int width;
char *col_width = col_width_list_str;
int raw_printing_style;
raw_printing_style = !strcmp(pretty_printing_style, "raw");

Frederic Weisbecker
committed
init_rem_hits();
fprintf(fp, "# Samples: %Ld\n", (u64)total_samples);
fprintf(fp, "#\n");
fprintf(fp, "# Overhead");
if (show_nr_samples) {