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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
[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;
};
struct mmap_event {
struct perf_event_header header;
__u32 pid, tid;
__u64 start;
__u64 len;
__u64 pgoff;
char filename[PATH_MAX];
};
struct comm_event {
struct perf_event_header header;
__u32 pid,tid;
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 = {
.header = " Command",
.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 = {
.header = " Shared Object ",
.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->dso)
ret += fprintf(fp, " %s: ", self->dso->name);
else
ret += fprintf(fp, " %#016llx: ", (__u64)self->ip);
if (self->sym)
ret += fprintf(fp, "%s", self->sym->name);
else
ret += fprintf(fp, "%#016llx", (__u64)self->ip);
return ret;
}
static struct sort_entry sort_sym = {
.header = " Shared Object: Symbol",
.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
{
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
597
598
599
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
}
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
667
668
669
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;
if (sort__need_collapse)
next = rb_first(&collapse_hists);
else
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);
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;
static int
process_event(event_t *event, unsigned long offset, unsigned long head)

Arnaldo Carvalho de Melo
committed
{
if (event->header.misc & PERF_EVENT_MISC_OVERFLOW) {
char level;
int show = 0;
struct dso *dso = NULL;
struct thread *thread = threads__findnew(event->ip.pid);
struct map *map = NULL;

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

Arnaldo Carvalho de Melo
committed
if (event->header.misc & PERF_EVENT_MISC_KERNEL) {
show = SHOW_KERNEL;
level = 'k';

Arnaldo Carvalho de Melo
committed
dso = kernel_dso;
dprintf(" ...... dso: %s\n", dso->name);

Arnaldo Carvalho de Melo
committed
} else if (event->header.misc & PERF_EVENT_MISC_USER) {

Arnaldo Carvalho de Melo
committed
show = SHOW_USER;
level = '.';
map = thread__find_map(thread, ip);

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

Arnaldo Carvalho de Melo
committed
} 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
}
total++;
} else switch (event->header.type) {
case PERF_EVENT_MMAP: {
struct thread *thread = threads__findnew(event->mmap.pid);
struct map *map = map__new(&event->mmap);

Arnaldo Carvalho de Melo
committed
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) {
if (verbose)
fprintf(stderr, "problem processing PERF_EVENT_MMAP, skipping event.\n");
}

Arnaldo Carvalho de Melo
committed
thread__insert_map(thread, map);

Arnaldo Carvalho de Melo
committed
break;
}
case PERF_EVENT_COMM: {
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);

Arnaldo Carvalho de Melo
committed
if (thread == NULL ||
thread__set_comm(thread, event->comm.comm)) {
fprintf(stderr, "problem processing PERF_EVENT_COMM, skipping event.\n");
}

Arnaldo Carvalho de Melo
committed
break;
}
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
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
default:
return -1;
}
return 0;
}
static int __cmd_report(void)
{
unsigned long offset = 0;
unsigned long head = 0;
struct stat stat;
char *buf;
event_t *event;
int ret, rc = EXIT_FAILURE;
uint32_t size;
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;

Arnaldo Carvalho de Melo
committed

Arnaldo Carvalho de Melo
committed
if (offset + head < stat.st_size)
goto more;
rc = EXIT_SUCCESS;
close(input);
dprintf(" IP events: %10ld\n", total);
dprintf(" mmap events: %10ld\n", total_mmap);
dprintf(" comm events: %10ld\n", total_comm);
dprintf(" unknown events: %10ld\n", total_unknown);
dsos__fprintf(stdout);
output__resort();
output__fprintf(stdout, total);

Arnaldo Carvalho de Melo
committed
return rc;
}
static const char * const report_usage[] = {
"perf report [<options>] <command>",
NULL
};