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

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

Arnaldo Carvalho de Melo
committed
#include "perf.h"
#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 *sort_order = "comm,dso";

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)
static int full_paths;

Arnaldo Carvalho de Melo
committed
static unsigned long page_size;
static unsigned long mmap_window = 32;
const char *perf_event_names[] = {

Arnaldo Carvalho de Melo
committed
[PERF_EVENT_MMAP] = " PERF_EVENT_MMAP",
[PERF_EVENT_MUNMAP] = " PERF_EVENT_MUNMAP",
[PERF_EVENT_COMM] = " PERF_EVENT_COMM",
};
struct ip_event {
struct perf_event_header header;
__u64 ip;
__u32 pid, tid;
};

Arnaldo Carvalho de Melo
committed
struct mmap_event {
struct perf_event_header header;
__u32 pid, tid;
__u64 start;
__u64 len;
__u64 pgoff;
char filename[PATH_MAX];
};

Arnaldo Carvalho de Melo
committed
struct comm_event {
struct perf_event_header header;

Arnaldo Carvalho de Melo
committed
char comm[16];
};
typedef union event_union {
struct perf_event_header header;
struct ip_event ip;
struct mmap_event mmap;
struct comm_event comm;
} event_t;
static LIST_HEAD(dsos);
static struct dso *kernel_dso;
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);
if (nr < 0) {
fprintf(stderr, "Failed to open: %s\n", name);
goto out_delete_dso;

Arnaldo Carvalho de Melo
committed
}
if (!nr && verbose) {
fprintf(stderr,
"No symbols found in: %s, maybe install a debug package?\n",
name);
}
dsos__add(dso);

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 int load_kernel(void)
{

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

Arnaldo Carvalho de Melo
committed
return -1;

Arnaldo Carvalho de Melo
committed
err = dso__load_kernel(kernel_dso, vmlinux, NULL);

Arnaldo Carvalho de Melo
committed
if (err) {
dso__delete(kernel_dso);
kernel_dso = NULL;
} else
dsos__add(kernel_dso);

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;
uint64_t start;
uint64_t end;
uint64_t pgoff;
struct dso *dso;
};
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;
}
}

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;
}
return self;
out_delete:
free(self);
return NULL;
}

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 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)
{
list_add_tail(&map->node, &self->maps);
}
static struct map *thread__find_map(struct thread *self, uint64_t ip)
{
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;
}
/*
* 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;
uint64_t ip;
char level;
uint32_t 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 *);
};
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 = {
.header = " Command: Pid ",
.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) {
if (!comm_l && !comm_r)
return 0;
else if (!comm_l)
return -1;
else
return 1;
}
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) {
if (!dso_l && !dso_r)
return 0;
else if (!dso_l)
return -1;
else
return 1;
}
return strcmp(dso_l->name, dso_r->name);
}
static size_t
sort__dso_print(FILE *fp, struct hist_entry *self)
{
if (self->dso)
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)
{
uint64_t 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, " %s", self->sym->name);
ret += fprintf(fp, " %#016llx", (__u64)self->ip);
return ret;
}
static struct sort_entry sort_sym = {
.cmp = sort__sym_cmp,
.print = sort__sym_print,
struct sort_dimension {
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, },
};
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;
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
hist_entry__fprintf(FILE *fp, struct hist_entry *self, uint64_t total_samples)
{
struct sort_entry *se;
size_t ret;
if (total_samples) {
(self->count * 100.0) / total_samples);
} else
ret = fprintf(fp, "%12d ", self->count);
list_for_each_entry(se, &hist_entry__sort_list, list)
ret += se->print(fp, self);
ret += fprintf(fp, "\n");
return ret;
}
/*
* collect histogram counts
*/
static int
hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
struct symbol *sym, uint64_t ip, char level)

Arnaldo Carvalho de Melo
committed
{
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
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 = 1,
};
int cmp;
while (*p != NULL) {
parent = *p;
he = rb_entry(parent, struct hist_entry, rb_node);
cmp = hist_entry__cmp(&entry, he);
if (!cmp) {
he->count++;
return 0;
}
if (cmp < 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
he = malloc(sizeof(*he));
if (!he)
return -ENOMEM;
*he = entry;
rb_link_node(&he->rb_node, parent, p);
rb_insert_color(&he->rb_node, &hist);
return 0;

Arnaldo Carvalho de Melo
committed
}
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
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;
static void output__insert_entry(struct hist_entry *he)
struct rb_node **p = &output_hists.rb_node;
struct rb_node *parent = NULL;
struct hist_entry *iter;
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);
static void output__resort(void)
struct hist_entry *n;
tree = &collapse_hists;
next = rb_first(tree);
while (next) {
n = rb_entry(next, struct hist_entry, rb_node);
next = rb_next(&n->rb_node);
output__insert_entry(n);
static size_t output__fprintf(FILE *fp, uint64_t total_samples)
struct hist_entry *pos;
struct sort_entry *se;
struct rb_node *nd;
size_t ret = 0;
fprintf(fp, "#\n");
fprintf(fp, "# Overhead");
list_for_each_entry(se, &hist_entry__sort_list, list)
fprintf(fp, " %s", se->header);
fprintf(fp, "\n");
fprintf(fp, "# ........");
list_for_each_entry(se, &hist_entry__sort_list, list) {
int i;
fprintf(fp, " ");
for (i = 0; i < strlen(se->header)-1; i++)
fprintf(fp, ".");
fprintf(fp, "\n");
fprintf(fp, "#\n");
for (nd = rb_first(&output_hists); nd; nd = rb_next(nd)) {
pos = rb_entry(nd, struct hist_entry, rb_node);
ret += hist_entry__fprintf(fp, pos, total_samples);
}
return ret;
}
static void register_idle_thread(void)
{
struct thread *thread = threads__findnew(0);
if (thread == NULL ||
thread__set_comm(thread, "[idle]")) {
fprintf(stderr, "problem inserting idle task.\n");
exit(-1);
}
}
static unsigned long total = 0, total_mmap = 0, total_comm = 0, total_unknown = 0;
process_overflow_event(event_t *event, unsigned long offset, unsigned long head)
{
char level;
int show = 0;
struct dso *dso = NULL;
struct thread *thread = threads__findnew(event->ip.pid);
uint64_t ip = event->ip.ip;
struct map *map = NULL;
dprintf("%p [%p]: PERF_EVENT (IP, %d): %d: %p\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->header.misc,
event->ip.pid,
(void *)(long)ip);
dprintf(" ... thread: %s:%d\n", thread->comm, thread->pid);
if (thread == NULL) {
fprintf(stderr, "problem processing %d event, skipping it.\n",
event->header.type);
return -1;
}
if (event->header.misc & PERF_EVENT_MISC_KERNEL) {
show = SHOW_KERNEL;
level = 'k';
dprintf(" ...... dso: %s\n", dso->name);
} else if (event->header.misc & PERF_EVENT_MISC_USER) {
show = SHOW_USER;
level = '.';
map = thread__find_map(thread, ip);
if (map != NULL) {
dso = map->dso;
ip -= map->start + map->pgoff;

Arnaldo Carvalho de Melo
committed
} 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;

Arnaldo Carvalho de Melo
committed
}
dprintf(" ...... dso: %s\n", dso ? dso->name : "<not found>");
} else {
show = SHOW_HV;
level = 'H';
dprintf(" ...... dso: [hypervisor]\n");
}

Arnaldo Carvalho de Melo
committed
if (show & show_mask) {
struct symbol *sym = dso__find_symbol(dso, ip);

Arnaldo Carvalho de Melo
committed
if (hist_entry__add(thread, map, dso, sym, ip, level)) {
fprintf(stderr,
"problem incrementing symbol count, skipping event\n");
}

Arnaldo Carvalho de Melo
committed
}

Arnaldo Carvalho de Melo
committed
static int
process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread = threads__findnew(event->mmap.pid);
struct map *map = map__new(&event->mmap);
dprintf("%p [%p]: PERF_EVENT_MMAP: [%p(%p) @ %p]: %s\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
(void *)(long)event->mmap.start,
(void *)(long)event->mmap.len,
(void *)(long)event->mmap.pgoff,
event->mmap.filename);
if (thread == NULL || map == NULL) {
dprintf("problem processing PERF_EVENT_MMAP, skipping event.\n");
}
thread__insert_map(thread, map);
total_mmap++;
return 0;
}
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread = threads__findnew(event->comm.pid);
dprintf("%p [%p]: PERF_EVENT_COMM: %s:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->comm.comm, event->comm.pid);
if (thread == NULL ||
thread__set_comm(thread, event->comm.comm)) {
dprintf("problem processing PERF_EVENT_COMM, skipping event.\n");
return -1;

Arnaldo Carvalho de Melo
committed
}
total_comm++;
return 0;
}
static int
process_event(event_t *event, unsigned long offset, unsigned long head)
{
if (event->header.misc & PERF_EVENT_MISC_OVERFLOW)
return process_overflow_event(event, offset, head);
switch (event->header.type) {
case PERF_EVENT_MMAP:
return process_mmap_event(event, offset, head);
case PERF_EVENT_COMM:
return process_comm_event(event, offset, head);
/*
* We dont process them right now but they are fine:
*/
case PERF_EVENT_MUNMAP:
case PERF_EVENT_PERIOD:
case PERF_EVENT_THROTTLE:
case PERF_EVENT_UNTHROTTLE:
return 0;
default:
return -1;
}
return 0;
}
static int __cmd_report(void)
{
int ret, rc = EXIT_FAILURE;
unsigned long offset = 0;
unsigned long head = 0;
struct stat stat;
event_t *event;
uint32_t size;
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
register_idle_thread();
input = open(input_name, O_RDONLY);
if (input < 0) {
perror("failed to open file");
exit(-1);
}
ret = fstat(input, &stat);
if (ret < 0) {
perror("failed to stat file");
exit(-1);
}
if (!stat.st_size) {
fprintf(stderr, "zero-sized file, nothing to do!\n");
exit(0);
}
if (load_kernel() < 0) {
perror("failed to load kernel symbols");
return EXIT_FAILURE;
}
if (!full_paths) {
if (getcwd(__cwd, sizeof(__cwd)) == NULL) {
perror("failed to get the current directory");
return EXIT_FAILURE;
}
cwdlen = strlen(cwd);
} else {
cwd = NULL;
cwdlen = 0;
}
remap:
buf = (char *)mmap(NULL, page_size * mmap_window, PROT_READ,
MAP_SHARED, input, offset);
if (buf == MAP_FAILED) {
perror("failed to mmap file");
exit(-1);
}
more:
event = (event_t *)(buf + head);
size = event->header.size;
if (!size)
size = 8;
if (head + event->header.size >= page_size * mmap_window) {
unsigned long shift = page_size * (head / page_size);
int ret;
ret = munmap(buf, page_size * mmap_window);
assert(ret == 0);
offset += shift;
head -= shift;
goto remap;
}
size = event->header.size;
if (!size || process_event(event, offset, head) < 0) {
dprintf("%p [%p]: skipping unknown header type: %d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->header.type);
/*
* assume we lost track of the stream, check alignment, and
* increment a single u64 in the hope to catch on again 'soon'.
*/
if (unlikely(head & 7))
head &= ~7ULL;
size = 8;