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 "util/list.h"
#include "util/rbtree.h"

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

Arnaldo Carvalho de Melo
committed
#include "util/parse-options.h"
#include "util/parse-events.h"

Arnaldo Carvalho de Melo
committed
#define SHOW_KERNEL 1
#define SHOW_USER 2
#define SHOW_HV 4
static char const *input_name = "perf.data";
static char *vmlinux = NULL;
static char default_sort_order[] = "comm,dso";
static char *sort_order = default_sort_order;
static char *dso_list_str, *comm_list_str;
static struct strlist *dso_list, *comm_list;

Arnaldo Carvalho de Melo
committed
static int input;
static int show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV;
#define dprintf(x...) do { if (dump_trace) printf(x); } while (0)
#define cdprintf(x...) do { if (dump_trace) color_fprintf(stdout, color, x); } while (0)
#define eprintf(x...) do { if (verbose) fprintf(stderr, x); } while (0)
static int full_paths;

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;
static int callchain;

Arnaldo Carvalho de Melo
committed
struct ip_event {
struct perf_event_header header;
u64 ip;
u32 pid, tid;
unsigned char __more_data[];

Arnaldo Carvalho de Melo
committed
};

Arnaldo Carvalho de Melo
committed
struct mmap_event {
struct perf_event_header header;
u32 pid, tid;
u64 start;
u64 len;
u64 pgoff;

Arnaldo Carvalho de Melo
committed
char filename[PATH_MAX];
};

Arnaldo Carvalho de Melo
committed
struct comm_event {
struct perf_event_header header;
u32 pid, tid;

Arnaldo Carvalho de Melo
committed
char comm[16];
};
struct fork_event {
struct perf_event_header header;
u32 pid, ppid;
struct period_event {

Arnaldo Carvalho de Melo
committed
struct perf_event_header header;
u64 time;
u64 id;
u64 sample_period;
struct lost_event {
struct perf_event_header header;
u64 id;
u64 lost;
struct read_event {
struct perf_event_header header;
u32 pid,tid;
u64 value;
u64 format[3];
};
typedef union event_union {
struct perf_event_header header;
struct ip_event ip;
struct mmap_event mmap;
struct comm_event comm;
struct fork_event fork;
struct period_event period;
struct read_event read;

Arnaldo Carvalho de Melo
committed
} event_t;
static LIST_HEAD(dsos);
static struct dso *kernel_dso;

Arnaldo Carvalho de Melo
committed
static void dsos__add(struct dso *dso)
{
list_add_tail(&dso->node, &dsos);
}
static struct dso *dsos__find(const char *name)
{
struct dso *pos;
list_for_each_entry(pos, &dsos, node)
if (strcmp(pos->name, name) == 0)
return pos;
return NULL;
}
static struct dso *dsos__findnew(const char *name)
{
struct dso *dso = dsos__find(name);

Arnaldo Carvalho de Melo
committed
if (dso)
return dso;
dso = dso__new(name, 0);
if (!dso)
goto out_delete_dso;

Arnaldo Carvalho de Melo
committed
nr = dso__load(dso, NULL, verbose);
eprintf("Failed to open: %s\n", name);

Arnaldo Carvalho de Melo
committed
}
if (!nr)
eprintf("No symbols found in: %s, maybe install a debug package?\n", name);

Arnaldo Carvalho de Melo
committed
return dso;
out_delete_dso:
dso__delete(dso);
return NULL;
}
static void dsos__fprintf(FILE *fp)

Arnaldo Carvalho de Melo
committed
{
struct dso *pos;
list_for_each_entry(pos, &dsos, node)
dso__fprintf(pos, fp);
}
static struct symbol *vdso__find_symbol(struct dso *dso, u64 ip)
{
return dso__find_symbol(kernel_dso, ip);
}
static int load_kernel(void)
{

Arnaldo Carvalho de Melo
committed
int err;
kernel_dso = dso__new("[kernel]", 0);

Arnaldo Carvalho de Melo
committed
return -1;
err = dso__load_kernel(kernel_dso, vmlinux, NULL, verbose);

Arnaldo Carvalho de Melo
committed
if (err) {
dso__delete(kernel_dso);
kernel_dso = NULL;
} else
dsos__add(kernel_dso);
vdso = dso__new("[vdso]", 0);
if (!vdso)
return -1;
vdso->find_symbol = vdso__find_symbol;
dsos__add(vdso);

Arnaldo Carvalho de Melo
committed
return err;
static char __cwd[PATH_MAX];
static char *cwd = __cwd;
static int cwdlen;
static int strcommon(const char *pathname)
{
int n = 0;
while (pathname[n] == cwd[n] && n < cwdlen)
++n;
return n;
}

Arnaldo Carvalho de Melo
committed
struct map {
struct list_head node;
u64 start;
u64 end;
u64 pgoff;
u64 (*map_ip)(struct map *, u64);

Arnaldo Carvalho de Melo
committed
struct dso *dso;
};
static u64 map__map_ip(struct map *map, u64 ip)
{
return ip - map->start + map->pgoff;
}
static u64 vdso__map_ip(struct map *map, u64 ip)
static inline int is_anon_memory(const char *filename)
{
return strcmp(filename, "//anon") == 0;
static struct map *map__new(struct mmap_event *event)

Arnaldo Carvalho de Melo
committed
{
struct map *self = malloc(sizeof(*self));
if (self != NULL) {
const char *filename = event->filename;
char newfilename[PATH_MAX];
if (cwd) {
if (n == cwdlen) {
snprintf(newfilename, sizeof(newfilename),
".%s", filename + n);
filename = newfilename;
}
}
anon = is_anon_memory(filename);
if (anon) {
snprintf(newfilename, sizeof(newfilename), "/tmp/perf-%d.map", event->pid);
filename = newfilename;
}

Arnaldo Carvalho de Melo
committed
self->start = event->start;
self->end = event->start + event->len;
self->pgoff = event->pgoff;
self->dso = dsos__findnew(filename);

Arnaldo Carvalho de Melo
committed
if (self->dso == NULL)
goto out_delete;
if (self->dso == vdso || anon)
self->map_ip = vdso__map_ip;
else
self->map_ip = map__map_ip;

Arnaldo Carvalho de Melo
committed
}
return self;
out_delete:
free(self);
return NULL;
}
static struct map *map__clone(struct map *self)
{
struct map *map = malloc(sizeof(*self));
if (!map)
return NULL;
memcpy(map, self, sizeof(*self));
return map;
}
static int map__overlap(struct map *l, struct map *r)
{
if (l->start > r->start) {
struct map *t = l;
l = r;
r = t;
}
if (l->end > r->start)
return 1;
return 0;
}
static size_t map__fprintf(struct map *self, FILE *fp)
{
return fprintf(fp, " %Lx-%Lx %Lx %s\n",
self->start, self->end, self->pgoff, self->dso->name);
}

Arnaldo Carvalho de Melo
committed
struct thread {
struct rb_node rb_node;

Arnaldo Carvalho de Melo
committed
struct list_head maps;
pid_t pid;
char *comm;
};
static struct thread *thread__new(pid_t pid)
{
struct thread *self = malloc(sizeof(*self));
if (self != NULL) {
self->pid = pid;
snprintf(self->comm, 32, ":%d", self->pid);

Arnaldo Carvalho de Melo
committed
INIT_LIST_HEAD(&self->maps);
}
return self;
}
static int thread__set_comm(struct thread *self, const char *comm)
{

Arnaldo Carvalho de Melo
committed
self->comm = strdup(comm);
return self->comm ? 0 : -ENOMEM;
}
static size_t thread__fprintf(struct thread *self, FILE *fp)
{
struct map *pos;
size_t ret = fprintf(fp, "Thread %d %s\n", self->pid, self->comm);
list_for_each_entry(pos, &self->maps, node)
ret += map__fprintf(pos, fp);
return ret;
}
static struct rb_root threads;
static struct thread *last_match;

Arnaldo Carvalho de Melo
committed
static struct thread *threads__findnew(pid_t pid)

Arnaldo Carvalho de Melo
committed
{
struct rb_node **p = &threads.rb_node;
struct rb_node *parent = NULL;
struct thread *th;

Arnaldo Carvalho de Melo
committed
/*
* Font-end cache - PID lookups come in blocks,
* so most of the time we dont have to look up
* the full rbtree:
*/
if (last_match && last_match->pid == pid)
return last_match;
while (*p != NULL) {
parent = *p;
th = rb_entry(parent, struct thread, rb_node);

Arnaldo Carvalho de Melo
committed
if (th->pid == pid) {
last_match = th;
return th;

Arnaldo Carvalho de Melo
committed
if (pid < th->pid)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;

Arnaldo Carvalho de Melo
committed
}
th = thread__new(pid);
if (th != NULL) {
rb_link_node(&th->rb_node, parent, p);
rb_insert_color(&th->rb_node, &threads);
}
return th;

Arnaldo Carvalho de Melo
committed
}
static void thread__insert_map(struct thread *self, struct map *map)
{
struct map *pos, *tmp;
list_for_each_entry_safe(pos, tmp, &self->maps, node) {
if (map__overlap(pos, map)) {
if (verbose >= 2) {
printf("overlapping maps:\n");
map__fprintf(map, stdout);
map__fprintf(pos, stdout);
}
if (map->start <= pos->start && map->end > pos->start)
pos->start = map->end;
if (map->end >= pos->end && map->start < pos->end)
pos->end = map->start;
if (verbose >= 2) {
printf("after collision:\n");
map__fprintf(pos, stdout);
}
if (pos->start >= pos->end) {
list_del_init(&pos->node);
free(pos);
}

Arnaldo Carvalho de Melo
committed
list_add_tail(&map->node, &self->maps);
}
static int thread__fork(struct thread *self, struct thread *parent)
{
struct map *map;
if (self->comm)
free(self->comm);
self->comm = strdup(parent->comm);
if (!self->comm)
return -ENOMEM;
list_for_each_entry(map, &parent->maps, node) {
struct map *new = map__clone(map);
if (!new)
return -ENOMEM;
thread__insert_map(self, new);
}
return 0;
}
static struct map *thread__find_map(struct thread *self, u64 ip)

Arnaldo Carvalho de Melo
committed
{
struct map *pos;

Arnaldo Carvalho de Melo
committed
if (self == NULL)
return NULL;
list_for_each_entry(pos, &self->maps, node)
if (ip >= pos->start && ip <= pos->end)
return pos;
return NULL;
}
static size_t threads__fprintf(FILE *fp)
{
size_t ret = 0;
struct rb_node *nd;
for (nd = rb_first(&threads); nd; nd = rb_next(nd)) {
struct thread *pos = rb_entry(nd, struct thread, rb_node);
ret += thread__fprintf(pos, fp);
}
return ret;
}
/*
* 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;
char *header;
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 *);
};
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)
{
return fprintf(fp, "%16s:%5d", self->thread->comm ?: "", self->thread->pid);
static struct sort_entry sort_thread = {
.cmp = sort__thread_cmp,
.print = sort__thread_print,
};
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)
{
return fprintf(fp, "%16s", self->thread->comm);
}
static struct sort_entry sort_comm = {
.cmp = sort__comm_cmp,
.collapse = sort__comm_collapse,
.print = sort__comm_print,
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)
{
return fprintf(fp, "%-25s", self->dso->name);
return fprintf(fp, "%016llx ", (u64)self->ip);
}
static struct sort_entry sort_dso = {
.cmp = sort__dso_cmp,
.print = sort__dso_print,
};
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);
}
static size_t
sort__sym_print(FILE *fp, struct hist_entry *self)
{
size_t ret = 0;
if (verbose)
ret += fprintf(fp, "%#018llx ", (u64)self->ip);
if (self->sym) {
ret += fprintf(fp, "[%c] %s",
self->dso == kernel_dso ? 'k' : '.', self->sym->name);
} else {
ret += 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

Ingo Molnar
committed
sort__parent_print(FILE *fp, struct hist_entry *self)
{
size_t ret = 0;

Ingo Molnar
committed
ret += fprintf(fp, "%-20s", self->parent ? self->parent->name : "[other]");
return ret;
}

Ingo Molnar
committed
static struct sort_entry sort_parent = {
.header = "Parent symbol ",
.cmp = sort__parent_cmp,
.print = sort__parent_print,

Ingo Molnar
committed
static int sort__has_parent = 0;
char *name;
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(char *tok)
{
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;
}
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
static size_t
callchain__fprintf(FILE *fp, struct callchain_node *self, u64 total_samples)
{
struct callchain_list *chain;
size_t ret = 0;
if (!self)
return 0;
ret += callchain__fprintf(fp, self->parent, total_samples);
list_for_each_entry(chain, &self->val, list)
ret += fprintf(fp, " %p\n", (void *)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;
ret += fprintf(fp, " %6.2f%%\n", percent);
ret += callchain__fprintf(fp, chain, total_samples);
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) {
double percent = self->count * 100.0 / total_samples;
char *color = PERF_COLOR_NORMAL;
/*
* We color high-overhead entries in red, mid-overhead
* entries in green - and keep the low overhead places
* normal:
color = PERF_COLOR_RED;
} else {
if (percent >= 0.5)
color = PERF_COLOR_GREEN;
}
ret = color_fprintf(fp, color, " %6.2f%%",
(self->count * 100.0) / total_samples);
} else
ret = fprintf(fp, "%12Ld ", self->count);
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (exclude_other && (se == &sort_parent))
continue;
ret += se->print(fp, self);
ret += fprintf(fp, "\n");
if (callchain)
hist_entry_callchain__fprintf(fp, self, total_samples);
return ret;
}
/*
*
*/
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;
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
if (!thread)
return NULL;
if (dso)
goto got_dso;
if (map)
goto got_map;
map = thread__find_map(thread, ip);
if (map != NULL) {
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;
}
dprintf(" ...... dso: %s\n", dso ? dso->name : "<not found>");
dprintf(" ...... map: %Lx -> %Lx\n", *ipp, ip);
*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))
/*
* 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 hist_entry entry = {
.thread = thread,
.map = map,
.dso = dso,
.sym = sym,
.ip = ip,
.level = level,
.count = count,
.sorted_chain = RB_ROOT

Ingo Molnar
committed
if (sort__has_parent && chain) {
u64 context = PERF_CONTEXT_MAX;
int i;
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_KERNEL:
dso = kernel_dso;
break;
default:
break;
}
sym = resolve_symbol(thread, NULL, &dso, &ip);
if (sym && call__match(sym)) {
entry.parent = sym;
break;
}
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);
return 0;
}
if (cmp < 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}