perf report: Simplify symbol output
[cascardo/linux.git] / Documentation / perf_counter / builtin-report.c
index 4b5ccc5..56c664d 100644 (file)
@@ -1,11 +1,19 @@
-#include "util/util.h"
+/*
+ * 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 "builtin.h"
 
-#include <libelf.h>
-#include <gelf.h>
-#include <elf.h>
+#include "util/util.h"
 
 #include "util/list.h"
+#include "util/cache.h"
 #include "util/rbtree.h"
+#include "util/symbol.h"
+#include "util/string.h"
 
 #include "perf.h"
 
 #define SHOW_USER      2
 #define SHOW_HV                4
 
-static char            const *input_name = "output.perf";
+static char            const *input_name = "perf.data";
+static char            *vmlinux = NULL;
+static char            *sort_order = "comm,dso";
 static int             input;
 static int             show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV;
 
 static int             dump_trace = 0;
+#define dprintf(x...)  do { if (dump_trace) printf(x); } while (0)
+
+static int             verbose;
+static int             full_paths;
 
 static unsigned long   page_size;
 static unsigned long   mmap_window = 32;
@@ -36,6 +50,7 @@ struct ip_event {
        __u64 ip;
        __u32 pid, tid;
 };
+
 struct mmap_event {
        struct perf_event_header header;
        __u32 pid, tid;
@@ -44,9 +59,10 @@ struct mmap_event {
        __u64 pgoff;
        char filename[PATH_MAX];
 };
+
 struct comm_event {
        struct perf_event_header header;
-       __u32 pid,tid;
+       __u32 pid, tid;
        char comm[16];
 };
 
@@ -57,595 +73,849 @@ typedef union event_union {
        struct comm_event comm;
 } event_t;
 
-struct symbol {
-       struct rb_node rb_node;
-       uint64_t       start;
-       uint64_t       end;
-       char           name[0];
-};
+static LIST_HEAD(dsos);
+static struct dso *kernel_dso;
 
-static struct symbol *symbol__new(uint64_t start, uint64_t len, const char *name)
+static void dsos__add(struct dso *dso)
 {
-       struct symbol *self = malloc(sizeof(*self) + strlen(name) + 1);
+       list_add_tail(&dso->node, &dsos);
+}
 
-       if (self != NULL) {
-               self->start = start;
-               self->end   = start + len;
-               strcpy(self->name, name);
+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);
+       int nr;
+
+       if (dso)
+               return dso;
+
+       dso = dso__new(name, 0);
+       if (!dso)
+               goto out_delete_dso;
+
+       nr = dso__load(dso, NULL);
+       if (nr < 0) {
+               fprintf(stderr, "Failed to open: %s\n", name);
+               goto out_delete_dso;
+       }
+       if (!nr && verbose) {
+               fprintf(stderr,
+               "No symbols found in: %s, maybe install a debug package?\n",
+                               name);
        }
 
-       return self;
+       dsos__add(dso);
+
+       return dso;
+
+out_delete_dso:
+       dso__delete(dso);
+       return NULL;
 }
 
-static void symbol__delete(struct symbol *self)
+static void dsos__fprintf(FILE *fp)
 {
-       free(self);
+       struct dso *pos;
+
+       list_for_each_entry(pos, &dsos, node)
+               dso__fprintf(pos, fp);
+}
+
+static int load_kernel(void)
+{
+       int err;
+
+       kernel_dso = dso__new("[kernel]", 0);
+       if (!kernel_dso)
+               return -1;
+
+       err = dso__load_kernel(kernel_dso, vmlinux, NULL);
+       if (err) {
+               dso__delete(kernel_dso);
+               kernel_dso = NULL;
+       } else
+               dsos__add(kernel_dso);
+
+       return err;
 }
 
-static size_t symbol__fprintf(struct symbol *self, FILE *fp)
+static char __cwd[PATH_MAX];
+static char *cwd = __cwd;
+static int cwdlen;
+
+static int strcommon(const char *pathname)
 {
-       return fprintf(fp, " %lx-%lx %s\n",
-                      self->start, self->end, self->name);
+       int n = 0;
+
+       while (pathname[n] == cwd[n] && n < cwdlen)
+               ++n;
+
+       return n;
 }
 
-struct dso {
+struct map {
        struct list_head node;
-       struct rb_root   syms;
-       char             name[0];
+       uint64_t         start;
+       uint64_t         end;
+       uint64_t         pgoff;
+       struct dso       *dso;
 };
 
-static struct dso *dso__new(const char *name)
+static struct map *map__new(struct mmap_event *event)
 {
-       struct dso *self = malloc(sizeof(*self) + strlen(name) + 1);
+       struct map *self = malloc(sizeof(*self));
 
        if (self != NULL) {
-               strcpy(self->name, name);
-               self->syms = RB_ROOT;
-       }
+               const char *filename = event->filename;
+               char newfilename[PATH_MAX];
 
+               if (cwd) {
+                       int n = strcommon(filename);
+
+                       if (n == cwdlen) {
+                               snprintf(newfilename, sizeof(newfilename),
+                                        ".%s", filename + n);
+                               filename = newfilename;
+                       }
+               }
+
+               self->start = event->start;
+               self->end   = event->start + event->len;
+               self->pgoff = event->pgoff;
+
+               self->dso = dsos__findnew(filename);
+               if (self->dso == NULL)
+                       goto out_delete;
+       }
        return self;
+out_delete:
+       free(self);
+       return NULL;
 }
 
-static void dso__delete_symbols(struct dso *self)
+struct thread;
+
+struct thread {
+       struct rb_node   rb_node;
+       struct list_head maps;
+       pid_t            pid;
+       char             *comm;
+};
+
+static struct thread *thread__new(pid_t pid)
 {
-       struct symbol *pos;
-       struct rb_node *next = rb_first(&self->syms);
+       struct thread *self = malloc(sizeof(*self));
 
-       while (next) {
-               pos = rb_entry(next, struct symbol, rb_node);
-               next = rb_next(&pos->rb_node);
-               symbol__delete(pos);
+       if (self != NULL) {
+               self->pid = pid;
+               self->comm = malloc(32);
+               if (self->comm)
+                       snprintf(self->comm, 32, ":%d", self->pid);
+               INIT_LIST_HEAD(&self->maps);
        }
+
+       return self;
 }
 
-static void dso__delete(struct dso *self)
+static int thread__set_comm(struct thread *self, const char *comm)
 {
-       dso__delete_symbols(self);
-       free(self);
+       if (self->comm)
+               free(self->comm);
+       self->comm = strdup(comm);
+       return self->comm ? 0 : -ENOMEM;
 }
 
-static void dso__insert_symbol(struct dso *self, struct symbol *sym)
+static struct rb_root threads;
+static struct thread *last_match;
+
+static struct thread *threads__findnew(pid_t pid)
 {
-       struct rb_node **p = &self->syms.rb_node;
+       struct rb_node **p = &threads.rb_node;
        struct rb_node *parent = NULL;
-       const uint64_t ip = sym->start;
-       struct symbol *s;
+       struct thread *th;
+
+       /*
+        * 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;
-               s = rb_entry(parent, struct symbol, rb_node);
-               if (ip < s->start)
+               th = rb_entry(parent, struct thread, rb_node);
+
+               if (th->pid == pid) {
+                       last_match = th;
+                       return th;
+               }
+
+               if (pid < th->pid)
                        p = &(*p)->rb_left;
                else
                        p = &(*p)->rb_right;
        }
-       rb_link_node(&sym->rb_node, parent, p);
-       rb_insert_color(&sym->rb_node, &self->syms);
+
+       th = thread__new(pid);
+       if (th != NULL) {
+               rb_link_node(&th->rb_node, parent, p);
+               rb_insert_color(&th->rb_node, &threads);
+               last_match = th;
+       }
+
+       return th;
 }
 
-static struct symbol *dso__find_symbol(struct dso *self, uint64_t ip)
+static void thread__insert_map(struct thread *self, struct map *map)
 {
-       if (self == NULL)
-               return NULL;
+       list_add_tail(&map->node, &self->maps);
+}
 
-       struct rb_node *n = self->syms.rb_node;
+static struct map *thread__find_map(struct thread *self, uint64_t ip)
+{
+       struct map *pos;
 
-       while (n) {
-               struct symbol *s = rb_entry(n, struct symbol, rb_node);
+       if (self == NULL)
+               return NULL;
 
-               if (ip < s->start)
-                       n = n->rb_left;
-               else if (ip > s->end)
-                       n = n->rb_right;
-               else
-                       return s;
-       }
+       list_for_each_entry(pos, &self->maps, node)
+               if (ip >= pos->start && ip <= pos->end)
+                       return pos;
 
        return NULL;
 }
 
-/**
- * elf_symtab__for_each_symbol - iterate thru all the symbols
- *
- * @self: struct elf_symtab instance to iterate
- * @index: uint32_t index
- * @sym: GElf_Sym iterator
+/*
+ * histogram, sorted on item, collects counts
  */
-#define elf_symtab__for_each_symbol(syms, nr_syms, index, sym) \
-       for (index = 0, gelf_getsym(syms, index, &sym);\
-            index < nr_syms; \
-            index++, gelf_getsym(syms, index, &sym))
 
-static inline uint8_t elf_sym__type(const GElf_Sym *sym)
+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 pid */
+
+static int64_t
+sort__thread_cmp(struct hist_entry *left, struct hist_entry *right)
 {
-       return GELF_ST_TYPE(sym->st_info);
+       return right->thread->pid - left->thread->pid;
 }
 
-static inline int elf_sym__is_function(const GElf_Sym *sym)
+static size_t
+sort__thread_print(FILE *fp, struct hist_entry *self)
 {
-       return elf_sym__type(sym) == STT_FUNC &&
-              sym->st_name != 0 &&
-              sym->st_shndx != SHN_UNDEF;
+       return fprintf(fp, " %16s:%5d", self->thread->comm ?: "", self->thread->pid);
 }
 
-static inline const char *elf_sym__name(const GElf_Sym *sym,
-                                       const Elf_Data *symstrs)
+static struct sort_entry sort_thread = {
+       .header = "         Command: Pid ",
+       .cmp    = sort__thread_cmp,
+       .print  = sort__thread_print,
+};
+
+/* --sort comm */
+
+static int64_t
+sort__comm_cmp(struct hist_entry *left, struct hist_entry *right)
 {
-       return symstrs->d_buf + sym->st_name;
+       return right->thread->pid - left->thread->pid;
 }
 
-static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep,
-                                   GElf_Shdr *shp, const char *name,
-                                   size_t *index)
+static int64_t
+sort__comm_collapse(struct hist_entry *left, struct hist_entry *right)
 {
-       Elf_Scn *sec = NULL;
-       size_t cnt = 1;
+       char *comm_l = left->thread->comm;
+       char *comm_r = right->thread->comm;
 
-       while ((sec = elf_nextscn(elf, sec)) != NULL) {
-               char *str;
-
-               gelf_getshdr(sec, shp);
-               str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name);
-               if (!strcmp(name, str)) {
-                       if (index)
-                               *index = cnt;
-                       break;
-               }
-               ++cnt;
+       if (!comm_l || !comm_r) {
+               if (!comm_l && !comm_r)
+                       return 0;
+               else if (!comm_l)
+                       return -1;
+               else
+                       return 1;
        }
 
-       return sec;
+       return strcmp(comm_l, comm_r);
 }
 
-static int dso__load(struct dso *self)
+static size_t
+sort__comm_print(FILE *fp, struct hist_entry *self)
 {
-       int fd = open(self->name, O_RDONLY), err = -1;
+       return fprintf(fp, "  %16s", self->thread->comm);
+}
 
-       if (fd == -1)
-               return -1;
+static struct sort_entry sort_comm = {
+       .header         = "          Command",
+       .cmp            = sort__comm_cmp,
+       .collapse       = sort__comm_collapse,
+       .print          = sort__comm_print,
+};
 
-       Elf *elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
-       if (elf == NULL) {
-               fprintf(stderr, "%s: cannot read %s ELF file.\n",
-                       __func__, self->name);
-               goto out_close;
-       }
+/* --sort dso */
+
+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;
 
-       GElf_Ehdr ehdr;
-       if (gelf_getehdr(elf, &ehdr) == NULL) {
-               fprintf(stderr, "%s: cannot get elf header.\n", __func__);
-               goto out_elf_end;
+       if (!dso_l || !dso_r) {
+               if (!dso_l && !dso_r)
+                       return 0;
+               else if (!dso_l)
+                       return -1;
+               else
+                       return 1;
        }
 
-       GElf_Shdr shdr;
-       Elf_Scn *sec = elf_section_by_name(elf, &ehdr, &shdr, ".symtab", NULL);
-       if (sec == NULL)
-               sec = elf_section_by_name(elf, &ehdr, &shdr, ".dynsym", NULL);
+       return strcmp(dso_l->name, dso_r->name);
+}
 
-       if (sec == NULL)
-               goto out_elf_end;
+static size_t
+sort__dso_print(FILE *fp, struct hist_entry *self)
+{
+       if (self->dso)
+               return fprintf(fp, "  %-25s", self->dso->name);
 
-       Elf_Data *syms = elf_getdata(sec, NULL);
-       if (syms == NULL)
-               goto out_elf_end;
+       return fprintf(fp, "  %016llx         ", (__u64)self->ip);
+}
 
-       sec = elf_getscn(elf, shdr.sh_link);
-       if (sec == NULL)
-               goto out_elf_end;
+static struct sort_entry sort_dso = {
+       .header = " Shared Object            ",
+       .cmp    = sort__dso_cmp,
+       .print  = sort__dso_print,
+};
 
-       Elf_Data *symstrs = elf_getdata(sec, NULL);
-       if (symstrs == NULL)
-               goto out_elf_end;
+/* --sort symbol */
 
-       const uint32_t nr_syms = shdr.sh_size / shdr.sh_entsize;
+static int64_t
+sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+       uint64_t ip_l, ip_r;
 
-       GElf_Sym sym;
-       uint32_t index;
-       elf_symtab__for_each_symbol(syms, nr_syms, index, sym) {
-               struct symbol *f;
+       if (left->sym == right->sym)
+               return 0;
 
-               if (!elf_sym__is_function(&sym))
-                       continue;
+       ip_l = left->sym ? left->sym->start : left->ip;
+       ip_r = right->sym ? right->sym->start : right->ip;
 
-               sec = elf_getscn(elf, sym.st_shndx);
-               if (!sec)
-                       goto out_elf_end;
+       return (int64_t)(ip_r - ip_l);
+}
 
-               gelf_getshdr(sec, &shdr);
-               sym.st_value -= shdr.sh_addr - shdr.sh_offset;
+static size_t
+sort__sym_print(FILE *fp, struct hist_entry *self)
+{
+       size_t ret = 0;
 
-               f = symbol__new(sym.st_value, sym.st_size,
-                               elf_sym__name(&sym, symstrs));
-               if (!f)
-                       goto out_elf_end;
+       if (verbose)
+               ret += fprintf(fp, "  %#018llx", (__u64)self->ip);
 
-               dso__insert_symbol(self, f);
-       }
+       if (self->sym)
+               ret += fprintf(fp, "  %s", self->sym->name);
+       else
+               ret += fprintf(fp, "  %#016llx", (__u64)self->ip);
 
-       err = 0;
-out_elf_end:
-       elf_end(elf);
-out_close:
-       close(fd);
-       return err;
+       return ret;
 }
 
-static size_t dso__fprintf(struct dso *self, FILE *fp)
-{
-       size_t ret = fprintf(fp, "dso: %s\n", self->name);
+static struct sort_entry sort_sym = {
+       .header = " Symbol",
+       .cmp    = sort__sym_cmp,
+       .print  = sort__sym_print,
+};
 
-       struct rb_node *nd;
-       for (nd = rb_first(&self->syms); nd; nd = rb_next(nd)) {
-               struct symbol *pos = rb_entry(nd, struct symbol, rb_node);
-               ret += symbol__fprintf(pos, fp);
-       }
+static int sort__need_collapse = 0;
 
-       return ret;
-}
+struct sort_dimension {
+       char *name;
+       struct sort_entry *entry;
+       int taken;
+};
 
-static LIST_HEAD(dsos);
-static struct dso *kernel_dso;
+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 void dsos__add(struct dso *dso)
-{
-       list_add_tail(&dso->node, &dsos);
-}
+static LIST_HEAD(hist_entry__sort_list);
 
-static struct dso *dsos__find(const char *name)
+static int sort_dimension__add(char *tok)
 {
-       struct dso *pos;
+       int i;
 
-       list_for_each_entry(pos, &dsos, node)
-               if (strcmp(pos->name, name) == 0)
-                       return pos;
-       return NULL;
-}
+       for (i = 0; i < ARRAY_SIZE(sort_dimensions); i++) {
+               struct sort_dimension *sd = &sort_dimensions[i];
 
-static struct dso *dsos__findnew(const char *name)
-{
-       struct dso *dso = dsos__find(name);
+               if (sd->taken)
+                       continue;
 
-       if (dso == NULL) {
-               dso = dso__new(name);
-               if (dso != NULL && dso__load(dso) < 0)
-                       goto out_delete_dso;
+               if (strncasecmp(tok, sd->name, strlen(tok)))
+                       continue;
 
-               dsos__add(dso);
-       }
+               if (sd->entry->collapse)
+                       sort__need_collapse = 1;
 
-       return dso;
+               list_add_tail(&sd->entry->list, &hist_entry__sort_list);
+               sd->taken = 1;
 
-out_delete_dso:
-       dso__delete(dso);
-       return NULL;
+               return 0;
+       }
+
+       return -ESRCH;
 }
 
-void dsos__fprintf(FILE *fp)
+static int64_t
+hist_entry__cmp(struct hist_entry *left, struct hist_entry *right)
 {
-       struct dso *pos;
+       struct sort_entry *se;
+       int64_t cmp = 0;
 
-       list_for_each_entry(pos, &dsos, node)
-               dso__fprintf(pos, fp);
+       list_for_each_entry(se, &hist_entry__sort_list, list) {
+               cmp = se->cmp(left, right);
+               if (cmp)
+                       break;
+       }
+
+       return cmp;
 }
 
-static int load_kallsyms(void)
+static int64_t
+hist_entry__collapse(struct hist_entry *left, struct hist_entry *right)
 {
-       kernel_dso = dso__new("[kernel]");
-       if (kernel_dso == NULL)
-               return -1;
-
-       FILE *file = fopen("/proc/kallsyms", "r");
-
-       if (file == NULL)
-               goto out_delete_dso;
+       struct sort_entry *se;
+       int64_t cmp = 0;
 
-       char *line = NULL;
-       size_t n;
+       list_for_each_entry(se, &hist_entry__sort_list, list) {
+               int64_t (*f)(struct hist_entry *, struct hist_entry *);
 
-       while (!feof(file)) {
-               unsigned long long start;
-               char c, symbf[4096];
+               f = se->collapse ?: se->cmp;
 
-               if (getline(&line, &n, file) < 0)
+               cmp = f(left, right);
+               if (cmp)
                        break;
-
-               if (!line)
-                       goto out_delete_dso;
-
-               if (sscanf(line, "%llx %c %s", &start, &c, symbf) == 3) {
-                       /*
-                        * Well fix up the end later, when we have all sorted.
-                        */
-                       struct symbol *sym = symbol__new(start, 0xdead, symbf);
-
-                       if (sym == NULL)
-                               goto out_delete_dso;
-
-                       dso__insert_symbol(kernel_dso, sym);
-               }
        }
 
-       /*
-        * Now that we have all sorted out, just set the ->end of all
-        * symbols
-        */
-       struct rb_node *nd, *prevnd = rb_first(&kernel_dso->syms);
+       return cmp;
+}
 
-       if (prevnd == NULL)
-               goto out_delete_line;
+static size_t
+hist_entry__fprintf(FILE *fp, struct hist_entry *self, uint64_t total_samples)
+{
+       struct sort_entry *se;
+       size_t ret;
 
-       for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) {
-               struct symbol *prev = rb_entry(prevnd, struct symbol, rb_node),
-                             *curr = rb_entry(nd, struct symbol, rb_node);
+       if (total_samples) {
+               ret = fprintf(fp, "   %6.2f%%",
+                               (self->count * 100.0) / total_samples);
+       } else
+               ret = fprintf(fp, "%12d ", self->count);
 
-               prev->end = curr->start - 1;
-               prevnd = nd;
-       }
+       list_for_each_entry(se, &hist_entry__sort_list, list)
+               ret += se->print(fp, self);
 
-       dsos__add(kernel_dso);
-       free(line);
-       fclose(file);
-       return 0;
+       ret += fprintf(fp, "\n");
 
-out_delete_line:
-       free(line);
-out_delete_dso:
-       dso__delete(kernel_dso);
-       return -1;
+       return ret;
 }
 
-struct map {
-       struct list_head node;
-       uint64_t         start;
-       uint64_t         end;
-       uint64_t         pgoff;
-       struct dso       *dso;
-};
+/*
+ * collect histogram counts
+ */
 
-static struct map *map__new(struct mmap_event *event)
+static int
+hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
+               struct symbol *sym, uint64_t ip, char level)
 {
-       struct map *self = malloc(sizeof(*self));
+       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;
 
-       if (self != NULL) {
-               self->start = event->start;
-               self->end   = event->start + event->len;
-               self->pgoff = event->pgoff;
+       while (*p != NULL) {
+               parent = *p;
+               he = rb_entry(parent, struct hist_entry, rb_node);
 
-               self->dso = dsos__findnew(event->filename);
-               if (self->dso == NULL)
-                       goto out_delete;
+               cmp = hist_entry__cmp(&entry, he);
+
+               if (!cmp) {
+                       he->count++;
+                       return 0;
+               }
+
+               if (cmp < 0)
+                       p = &(*p)->rb_left;
+               else
+                       p = &(*p)->rb_right;
        }
-       return self;
-out_delete:
-       free(self);
-       return NULL;
+
+       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;
 }
 
-static size_t map__fprintf(struct map *self, FILE *fp)
+static void hist_entry__free(struct hist_entry *he)
 {
-       return fprintf(fp, " %lx-%lx %lx %s\n",
-                      self->start, self->end, self->pgoff, self->dso->name);
+       free(he);
 }
 
-struct symhist {
-       struct rb_node   rb_node;
-       struct dso       *dso;
-       struct symbol    *sym;
-       uint64_t         ip;
-       uint32_t         count;
-       char             level;
-};
+/*
+ * collapse the histogram
+ */
 
-static struct symhist *symhist__new(struct symbol *sym, uint64_t ip,
-                                   struct dso *dso, char level)
+static struct rb_root collapse_hists;
+
+static void collapse__insert_entry(struct hist_entry *he)
 {
-       struct symhist *self = malloc(sizeof(*self));
+       struct rb_node **p = &collapse_hists.rb_node;
+       struct rb_node *parent = NULL;
+       struct hist_entry *iter;
+       int64_t cmp;
 
-       if (self != NULL) {
-               self->sym   = sym;
-               self->ip    = ip;
-               self->dso   = dso;
-               self->level = level;
-               self->count = 1;
-       }
+       while (*p != NULL) {
+               parent = *p;
+               iter = rb_entry(parent, struct hist_entry, rb_node);
 
-       return self;
-}
+               cmp = hist_entry__collapse(iter, he);
 
-void symhist__delete(struct symhist *self)
-{
-       free(self);
-}
+               if (!cmp) {
+                       iter->count += he->count;
+                       hist_entry__free(he);
+                       return;
+               }
 
-static void symhist__inc(struct symhist *self)
-{
-       ++self->count;
+               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 size_t symhist__fprintf(struct symhist *self, FILE *fp)
+static void collapse__resort(void)
 {
-       size_t ret = fprintf(fp, "%#llx [%c] ", (unsigned long long)self->ip, self->level);
+       struct rb_node *next;
+       struct hist_entry *n;
 
-       if (self->level != '.')
-               ret += fprintf(fp, "%s", self->sym ? self->sym->name: "<unknown>");
-       else
-               ret += fprintf(fp, "%s: %s",
-                              self->dso ? self->dso->name : "<unknown>",
-                              self->sym ? self->sym->name : "<unknown>");
-       return ret + fprintf(fp, ": %u\n", self->count);
-}
-
-struct thread {
-       struct rb_node   rb_node;
-       struct list_head maps;
-       struct rb_root   symhists;
-       pid_t            pid;
-       char             *comm;
-};
+       if (!sort__need_collapse)
+               return;
 
-static struct thread *thread__new(pid_t pid)
-{
-       struct thread *self = malloc(sizeof(*self));
+       next = rb_first(&hist);
+       while (next) {
+               n = rb_entry(next, struct hist_entry, rb_node);
+               next = rb_next(&n->rb_node);
 
-       if (self != NULL) {
-               self->pid = pid;
-               self->comm = NULL;
-               INIT_LIST_HEAD(&self->maps);
-               self->symhists = RB_ROOT;
+               rb_erase(&n->rb_node, &hist);
+               collapse__insert_entry(n);
        }
-
-       return self;
 }
 
-static int thread__symbol_incnew(struct thread *self, struct symbol *sym,
-                                uint64_t ip, struct dso *dso, char level)
+/*
+ * 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 = &self->symhists.rb_node;
+       struct rb_node **p = &output_hists.rb_node;
        struct rb_node *parent = NULL;
-       struct symhist *sh;
+       struct hist_entry *iter;
 
        while (*p != NULL) {
                parent = *p;
-               sh = rb_entry(parent, struct symhist, rb_node);
-
-               if (sh->sym == sym || ip == sh->ip) {
-                       symhist__inc(sh);
-                       return 0;
-               }
-
-               /* Handle unresolved symbols too */
-               const uint64_t start = !sh->sym ? sh->ip : sh->sym->start;
+               iter = rb_entry(parent, struct hist_entry, rb_node);
 
-               if (ip < start)
+               if (he->count > iter->count)
                        p = &(*p)->rb_left;
                else
                        p = &(*p)->rb_right;
        }
 
-       sh = symhist__new(sym, ip, dso, level);
-       if (sh == NULL)
-               return -ENOMEM;
-       rb_link_node(&sh->rb_node, parent, p);
-       rb_insert_color(&sh->rb_node, &self->symhists);
-       return 0;
+       rb_link_node(&he->rb_node, parent, p);
+       rb_insert_color(&he->rb_node, &output_hists);
 }
 
-static int thread__set_comm(struct thread *self, const char *comm)
+static void output__resort(void)
 {
-       self->comm = strdup(comm);
-       return self->comm ? 0 : -ENOMEM;
+       struct rb_node *next;
+       struct hist_entry *n;
+       struct rb_root *tree = &hist;
+
+       if (sort__need_collapse)
+               tree = &collapse_hists;
+
+       next = rb_first(tree);
+
+       while (next) {
+               n = rb_entry(next, struct hist_entry, rb_node);
+               next = rb_next(&n->rb_node);
+
+               rb_erase(&n->rb_node, tree);
+               output__insert_entry(n);
+       }
 }
 
-size_t thread__maps_fprintf(struct thread *self, FILE *fp)
+static size_t output__fprintf(FILE *fp, uint64_t total_samples)
 {
-       struct map *pos;
+       struct hist_entry *pos;
+       struct sort_entry *se;
+       struct rb_node *nd;
        size_t ret = 0;
 
-       list_for_each_entry(pos, &self->maps, node)
-               ret += map__fprintf(pos, fp);
+       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 size_t thread__fprintf(struct thread *self, FILE *fp)
+static void register_idle_thread(void)
 {
-       int ret = fprintf(fp, "thread: %d %s\n", self->pid, self->comm);
-       struct rb_node *nd;
+       struct thread *thread = threads__findnew(0);
 
-       for (nd = rb_first(&self->symhists); nd; nd = rb_next(nd)) {
-               struct symhist *pos = rb_entry(nd, struct symhist, rb_node);
-               ret += symhist__fprintf(pos, fp);
+       if (thread == NULL ||
+                       thread__set_comm(thread, "[idle]")) {
+               fprintf(stderr, "problem inserting idle task.\n");
+               exit(-1);
        }
-
-       return ret;
 }
 
-static struct rb_root threads = RB_ROOT;
+static unsigned long total = 0, total_mmap = 0, total_comm = 0, total_unknown = 0;
 
-static struct thread *threads__findnew(pid_t pid)
+static int
+process_overflow_event(event_t *event, unsigned long offset, unsigned long head)
 {
-       struct rb_node **p = &threads.rb_node;
-       struct rb_node *parent = NULL;
-       struct thread *th;
+       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;
+       }
 
-       while (*p != NULL) {
-               parent = *p;
-               th = rb_entry(parent, struct thread, rb_node);
+       if (event->header.misc & PERF_EVENT_MISC_KERNEL) {
+               show = SHOW_KERNEL;
+               level = 'k';
 
-               if (th->pid == pid)
-                       return th;
+               dso = kernel_dso;
 
-               if (pid < th->pid)
-                       p = &(*p)->rb_left;
-               else
-                       p = &(*p)->rb_right;
+               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;
+               } 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>");
+
+       } else {
+               show = SHOW_HV;
+               level = 'H';
+               dprintf(" ...... dso: [hypervisor]\n");
        }
 
-       th = thread__new(pid);
-       if (th != NULL) {
-               rb_link_node(&th->rb_node, parent, p);
-               rb_insert_color(&th->rb_node, &threads);
+       if (show & show_mask) {
+               struct symbol *sym = dso__find_symbol(dso, ip);
+
+               if (hist_entry__add(thread, map, dso, sym, ip, level)) {
+                       fprintf(stderr,
+               "problem incrementing symbol count, skipping event\n");
+                       return -1;
+               }
        }
-       return th;
+       total++;
+
+       return 0;
 }
 
-static void thread__insert_map(struct thread *self, struct map *map)
+static int
+process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
 {
-       list_add_tail(&map->node, &self->maps);
+       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");
+               return 0;
+       }
+
+       thread__insert_map(thread, map);
+       total_mmap++;
+
+       return 0;
 }
 
-static struct map *thread__find_map(struct thread *self, uint64_t ip)
+static int
+process_comm_event(event_t *event, unsigned long offset, unsigned long head)
 {
-       if (self == NULL)
-               return NULL;
+       struct thread *thread = threads__findnew(event->comm.pid);
 
-       struct map *pos;
+       dprintf("%p [%p]: PERF_EVENT_COMM: %s:%d\n",
+               (void *)(offset + head),
+               (void *)(long)(event->header.size),
+               event->comm.comm, event->comm.pid);
 
-       list_for_each_entry(pos, &self->maps, node)
-               if (ip >= pos->start && ip <= pos->end)
-                       return pos;
+       if (thread == NULL ||
+           thread__set_comm(thread, event->comm.comm)) {
+               dprintf("problem processing PERF_EVENT_COMM, skipping event.\n");
+               return -1;
+       }
+       total_comm++;
 
-       return NULL;
+       return 0;
 }
 
-static void threads__fprintf(FILE *fp)
+static int
+process_event(event_t *event, unsigned long offset, unsigned long head)
 {
-       struct rb_node *nd;
-       for (nd = rb_first(&threads); nd; nd = rb_next(nd)) {
-               struct thread *pos = rb_entry(nd, struct thread, rb_node);
-               thread__fprintf(pos, fp);
+       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;
-       char *buf;
        event_t *event;
-       int ret, rc = EXIT_FAILURE;
-       unsigned long total = 0, total_mmap = 0, total_comm = 0, total_unknown;
+       uint32_t size;
+       char *buf;
+
+       register_idle_thread();
 
        input = open(input_name, O_RDONLY);
        if (input < 0) {
@@ -664,11 +934,21 @@ static int __cmd_report(void)
                exit(0);
        }
 
-       if (load_kallsyms() < 0) {
-               perror("failed to open kallsyms");
+       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);
@@ -680,6 +960,10 @@ remap:
 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;
@@ -692,136 +976,51 @@ more:
                goto remap;
        }
 
+       size = event->header.size;
 
-       if (!event->header.size) {
-               fprintf(stderr, "zero-sized event at file offset %ld\n", offset + head);
-               fprintf(stderr, "skipping %ld bytes of events.\n", stat.st_size - offset - head);
-               goto done;
-       }
-
-       head += event->header.size;
-
-       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);
-               uint64_t ip = event->ip.ip;
+       if (!size || process_event(event, offset, head) < 0) {
 
-               if (dump_trace) {
-                       fprintf(stderr, "PERF_EVENT (IP, %d): %d: %p\n",
-                               event->header.misc,
-                               event->ip.pid,
-                               (void *)event->ip.ip);
-               }
+               dprintf("%p [%p]: skipping unknown header type: %d\n",
+                       (void *)(offset + head),
+                       (void *)(long)(event->header.size),
+                       event->header.type);
 
-               if (thread == NULL) {
-                       fprintf(stderr, "problem processing %d event, bailing out\n",
-                               event->header.type);
-                       goto done;
-               }
+               total_unknown++;
 
-               if (event->header.misc & PERF_EVENT_MISC_KERNEL) {
-                       show = SHOW_KERNEL;
-                       level = 'k';
-                       dso = kernel_dso;
-               } else if (event->header.misc & PERF_EVENT_MISC_USER) {
-                       show = SHOW_USER;
-                       level = '.';
-                       struct map *map = thread__find_map(thread, ip);
-                       if (map != NULL) {
-                               dso = map->dso;
-                               ip -= map->start + map->pgoff;
-                       }
-               } else {
-                       show = SHOW_HV;
-                       level = 'H';
-               }
+               /*
+                * assume we lost track of the stream, check alignment, and
+                * increment a single u64 in the hope to catch on again 'soon'.
+                */
 
-               if (show & show_mask) {
-                       struct symbol *sym = dso__find_symbol(dso, ip);
+               if (unlikely(head & 7))
+                       head &= ~7ULL;
 
-                       if (thread__symbol_incnew(thread, sym, ip, dso, level)) {
-                               fprintf(stderr, "problem incrementing symbol count, bailing out\n");
-                               goto done;
-                       }
-               }
-               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);
-
-               if (dump_trace) {
-                       fprintf(stderr, "PERF_EVENT_MMAP: [%p(%p) @ %p]: %s\n",
-                               (void *)event->mmap.start,
-                               (void *)event->mmap.len,
-                               (void *)event->mmap.pgoff,
-                               event->mmap.filename);
-               }
-               if (thread == NULL || map == NULL) {
-                       fprintf(stderr, "problem processing PERF_EVENT_MMAP, bailing out\n");
-                       goto done;
-               }
-               thread__insert_map(thread, map);
-               total_mmap++;
-               break;
+               size = 8;
        }
-       case PERF_EVENT_COMM: {
-               struct thread *thread = threads__findnew(event->comm.pid);
 
-               if (dump_trace) {
-                       fprintf(stderr, "PERF_EVENT_COMM: %s:%d\n",
-                               event->comm.comm, event->comm.pid);
-               }
-               if (thread == NULL ||
-                   thread__set_comm(thread, event->comm.comm)) {
-                       fprintf(stderr, "problem processing PERF_EVENT_COMM, bailing out\n");
-                       goto done;
-               }
-               total_comm++;
-               break;
-       }
-       default: {
-               fprintf(stderr, "skipping unknown header type: %d\n",
-                       event->header.type);
-               total_unknown++;
-       }
-       }
+       head += size;
 
        if (offset + head < stat.st_size)
                goto more;
 
        rc = EXIT_SUCCESS;
-done:
        close(input);
 
-       if (dump_trace) {
-               fprintf(stderr, "      IP events: %10ld\n", total);
-               fprintf(stderr, "    mmap events: %10ld\n", total_mmap);
-               fprintf(stderr, "    comm events: %10ld\n", total_comm);
-               fprintf(stderr, " unknown events: %10ld\n", total_unknown);
+       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);
 
+       if (dump_trace)
                return 0;
-       }
 
-       //dsos__fprintf(stdout);
-       threads__fprintf(stdout);
-#if 0
-       std::map<std::string, int>::iterator hi = hist.begin();
-
-       while (hi != hist.end()) {
-               rev_hist.insert(std::pair<int, std::string>(hi->second, hi->first));
-               hist.erase(hi++);
-       }
+       if (verbose >= 2)
+               dsos__fprintf(stdout);
 
-       std::multimap<int, std::string>::const_iterator ri = rev_hist.begin();
+       collapse__resort();
+       output__resort();
+       output__fprintf(stdout, total);
 
-       while (ri != rev_hist.end()) {
-               printf(" %5.2f %s\n", (100.0 * ri->first)/total, ri->second.c_str());
-               ri++;
-       }
-#endif
        return rc;
 }
 
@@ -833,18 +1032,44 @@ static const char * const report_usage[] = {
 static const struct option options[] = {
        OPT_STRING('i', "input", &input_name, "file",
                    "input file name"),
+       OPT_BOOLEAN('v', "verbose", &verbose,
+                   "be more verbose (show symbol address, etc)"),
        OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
                    "dump raw trace in ASCII"),
+       OPT_STRING('k', "vmlinux", &vmlinux, "file", "vmlinux pathname"),
+       OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
+                  "sort by key(s): pid, comm, dso, symbol. Default: pid,symbol"),
+       OPT_BOOLEAN('P', "full-paths", &full_paths,
+                   "Don't shorten the pathnames taking into account the cwd"),
        OPT_END()
 };
 
+static void setup_sorting(void)
+{
+       char *tmp, *tok, *str = strdup(sort_order);
+
+       for (tok = strtok_r(str, ", ", &tmp);
+                       tok; tok = strtok_r(NULL, ", ", &tmp)) {
+               if (sort_dimension__add(tok) < 0) {
+                       error("Unknown --sort key: `%s'", tok);
+                       usage_with_options(report_usage, options);
+               }
+       }
+
+       free(str);
+}
+
 int cmd_report(int argc, const char **argv, const char *prefix)
 {
-       elf_version(EV_CURRENT);
+       symbol__init();
 
        page_size = getpagesize();
 
        parse_options(argc, argv, options, report_usage, 0);
 
+       setup_sorting();
+
+       setup_pager();
+
        return __cmd_report();
 }