perf_counter tools: report: Implement header output for --sort variants
[deliverable/linux.git] / Documentation / perf_counter / builtin-report.c
index 9aef7c54483ec27896a7c1a51b592ae32f3a69c4..506cde437b785197e9c2ffe6fd091fbb69ac5e16 100644 (file)
@@ -4,7 +4,6 @@
 #include <libelf.h>
 #include <gelf.h>
 #include <elf.h>
-#include <ctype.h>
 
 #include "util/list.h"
 #include "util/cache.h"
@@ -20,6 +19,8 @@
 #define SHOW_HV                4
 
 static char            const *input_name = "perf.data";
+static char            *vmlinux = NULL;
+static char            *sort_order = "pid,symbol";
 static int             input;
 static int             show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV;
 
@@ -191,7 +192,8 @@ static inline int elf_sym__is_function(const GElf_Sym *sym)
 {
        return elf_sym__type(sym) == STT_FUNC &&
               sym->st_name != 0 &&
-              sym->st_shndx != SHN_UNDEF;
+              sym->st_shndx != SHN_UNDEF &&
+              sym->st_size != 0;
 }
 
 static inline const char *elf_sym__name(const GElf_Sym *sym,
@@ -223,11 +225,11 @@ static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep,
        return sec;
 }
 
-static int dso__load(struct dso *self)
+static int dso__load_sym(struct dso *self, int fd, char *name)
 {
        Elf_Data *symstrs;
        uint32_t nr_syms;
-       int fd, err = -1;
+       int err = -1;
        uint32_t index;
        GElf_Ehdr ehdr;
        GElf_Shdr shdr;
@@ -235,16 +237,12 @@ static int dso__load(struct dso *self)
        GElf_Sym sym;
        Elf_Scn *sec;
        Elf *elf;
-
-
-       fd = open(self->name, O_RDONLY);
-       if (fd == -1)
-               return -1;
+       int nr = 0;
 
        elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
        if (elf == NULL) {
                fprintf(stderr, "%s: cannot read %s ELF file.\n",
-                       __func__, self->name);
+                       __func__, name);
                goto out_close;
        }
 
@@ -293,16 +291,63 @@ static int dso__load(struct dso *self)
                        goto out_elf_end;
 
                dso__insert_symbol(self, f);
+
+               nr++;
        }
 
-       err = 0;
+       err = nr;
 out_elf_end:
        elf_end(elf);
 out_close:
-       close(fd);
        return err;
 }
 
+static int dso__load(struct dso *self)
+{
+       int size = strlen(self->name) + sizeof("/usr/lib/debug%s.debug");
+       char *name = malloc(size);
+       int variant = 0;
+       int ret = -1;
+       int fd;
+
+       if (!name)
+               return -1;
+
+more:
+       do {
+               switch (variant) {
+               case 0: /* Fedora */
+                       snprintf(name, size, "/usr/lib/debug%s.debug", self->name);
+                       break;
+               case 1: /* Ubuntu */
+                       snprintf(name, size, "/usr/lib/debug%s", self->name);
+                       break;
+               case 2: /* Sane people */
+                       snprintf(name, size, "%s", self->name);
+                       break;
+
+               default:
+                       goto out;
+               }
+               variant++;
+
+               fd = open(name, O_RDONLY);
+       } while (fd < 0);
+
+       ret = dso__load_sym(self, fd, name);
+       close(fd);
+
+       /*
+        * Some people seem to have debuginfo files _WITHOUT_ debug info!?!?
+        */
+       if (!ret)
+               goto more;
+
+out:
+       free(name);
+       return ret;
+}
+
 static size_t dso__fprintf(struct dso *self, FILE *fp)
 {
        size_t ret = fprintf(fp, "dso: %s\n", self->name);
@@ -337,11 +382,23 @@ static struct dso *dsos__find(const char *name)
 static struct dso *dsos__findnew(const char *name)
 {
        struct dso *dso = dsos__find(name);
+       int nr;
 
        if (dso == NULL) {
                dso = dso__new(name);
-               if (dso != NULL && dso__load(dso) < 0)
+               if (!dso)
+                       goto out_delete_dso;
+
+               nr = dso__load(dso);
+               if (nr < 0) {
+                       fprintf(stderr, "Failed to open: %s\n", name);
                        goto out_delete_dso;
+               }
+               if (!nr) {
+                       fprintf(stderr,
+               "Failed to find debug symbols for: %s, maybe install a debug package?\n",
+                                       name);
+               }
 
                dsos__add(dso);
        }
@@ -477,6 +534,39 @@ out_delete_dso:
        return -1;
 }
 
+static int load_kernel(void)
+{
+       int fd, nr;
+
+       if (!vmlinux)
+               goto kallsyms;
+
+       fd = open(vmlinux, O_RDONLY);
+       if (fd < 0)
+               goto kallsyms;
+
+       kernel_dso = dso__new("[kernel]");
+       if (!kernel_dso)
+               goto fail_open;
+
+       nr = dso__load_sym(kernel_dso, fd, vmlinux);
+
+       if (nr <= 0)
+               goto fail_load;
+
+       dsos__add(kernel_dso);
+       close(fd);
+
+       return 0;
+
+fail_load:
+       dso__delete(kernel_dso);
+fail_open:
+       close(fd);
+kallsyms:
+       return load_kallsyms();
+}
+
 struct map {
        struct list_head node;
        uint64_t         start;
@@ -506,86 +596,13 @@ out_delete:
 
 struct thread;
 
-static const char *thread__name(struct thread *self, char *bf, size_t size);
-
-struct symhist {
-       struct rb_node   rb_node;
-       struct dso       *dso;
-       struct symbol    *sym;
-       struct thread    *thread;
-       uint64_t         ip;
-       uint32_t         count;
-       char             level;
-};
-
-static struct symhist *symhist__new(struct symbol *sym, uint64_t ip,
-                                   struct thread *thread, struct dso *dso,
-                                   char level)
-{
-       struct symhist *self = malloc(sizeof(*self));
-
-       if (self != NULL) {
-               self->sym    = sym;
-               self->thread = thread;
-               self->ip     = ip;
-               self->dso    = dso;
-               self->level  = level;
-               self->count  = 1;
-       }
-
-       return self;
-}
-
-static void symhist__inc(struct symhist *self)
-{
-       ++self->count;
-}
-
-static size_t
-symhist__fprintf(struct symhist *self, uint64_t total_samples, FILE *fp)
-{
-       char bf[32];
-       size_t ret;
-
-       if (total_samples)
-               ret = fprintf(fp, "%5.2f", (self->count * 100.0) / total_samples);
-       else
-               ret = fprintf(fp, "%12d", self->count);
-
-       ret += fprintf(fp, "%14s [%c] ",
-                      thread__name(self->thread, bf, sizeof(bf)),
-                      self->level);
-
-       if (verbose)
-               ret += fprintf(fp, "%#018llx ", (unsigned long long)self->ip);
-
-       if (self->level != '.')
-               ret += fprintf(fp, "%s\n",
-                              self->sym ? self->sym->name : "<unknown>");
-       else
-               ret += fprintf(fp, "%s: %s\n",
-                              self->dso ? self->dso->name : "<unknown>",
-                              self->sym ? self->sym->name : "<unknown>");
-       return ret;
-}
-
 struct thread {
        struct rb_node   rb_node;
        struct list_head maps;
-       struct rb_root   symhists;
        pid_t            pid;
        char             *comm;
 };
 
-static const char *thread__name(struct thread *self, char *bf, size_t size)
-{
-       if (self->comm)
-               return self->comm;
-
-       snprintf(bf, sizeof(bf), ":%u", self->pid);
-       return bf;
-}
-
 static struct thread *thread__new(pid_t pid)
 {
        struct thread *self = malloc(sizeof(*self));
@@ -594,67 +611,17 @@ static struct thread *thread__new(pid_t pid)
                self->pid = pid;
                self->comm = NULL;
                INIT_LIST_HEAD(&self->maps);
-               self->symhists = RB_ROOT;
        }
 
        return self;
 }
 
-static int thread__symbol_incnew(struct thread *self, struct symbol *sym,
-                                uint64_t ip, struct dso *dso, char level)
-{
-       struct rb_node **p = &self->symhists.rb_node;
-       struct rb_node *parent = NULL;
-       struct symhist *sh;
-
-       while (*p != NULL) {
-               uint64_t start;
-
-               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 */
-               start = !sh->sym ? sh->ip : sh->sym->start;
-
-               if (ip < start)
-                       p = &(*p)->rb_left;
-               else
-                       p = &(*p)->rb_right;
-       }
-
-       sh = symhist__new(sym, ip, self, 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;
-}
-
 static int thread__set_comm(struct thread *self, const char *comm)
 {
        self->comm = strdup(comm);
        return self->comm ? 0 : -ENOMEM;
 }
 
-static size_t thread__fprintf(struct thread *self, FILE *fp)
-{
-       int ret = fprintf(fp, "thread: %d %s\n", self->pid, self->comm);
-       struct rb_node *nd;
-
-       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, 0, fp);
-       }
-
-       return ret;
-}
-
 static struct rb_root threads;
 
 static struct thread *threads__findnew(pid_t pid)
@@ -703,70 +670,357 @@ static struct map *thread__find_map(struct thread *self, uint64_t ip)
        return NULL;
 }
 
-static void threads__fprintf(FILE *fp)
+/*
+ * 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 *);
+       size_t  (*print)(FILE *fp, struct hist_entry *);
+};
+
+static int64_t
+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)
 {
-       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);
+       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)
+{
+       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 ?: "<unknown>");
+}
+
+static struct sort_entry sort_comm = {
+       .header = "         Command",
+       .cmp    = sort__comm_cmp,
+       .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)
+{
+       return fprintf(fp, " %64s", self->dso ? self->dso->name : "<unknown>");
+}
+
+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", (unsigned long long)self->ip);
+
+       ret += fprintf(fp, " %s: %s",
+                       self->dso ? self->dso->name : "<unknown>",
+                       self->sym ? self->sym->name : "<unknown>");
+
+       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 (strcmp(tok, sd->name))
+                       continue;
+
+               list_add_tail(&sd->entry->list, &hist_entry__sort_list);
+               sd->taken = 1;
+               return 0;
        }
+
+       return -ESRCH;
+}
+
+static void setup_sorting(void)
+{
+       char *tmp, *tok, *str = strdup(sort_order);
+
+       for (tok = strtok_r(str, ", ", &tmp);
+                       tok; tok = strtok_r(NULL, ", ", &tmp))
+               sort_dimension__add(tok);
+
+       free(str);
+}
+
+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 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) {
+               ret = fprintf(fp, "    %5.2f%%",
+                               (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;
 }
 
-static struct rb_root global_symhists;
+/*
+ * collect histogram counts
+ */
 
-static void threads__insert_symhist(struct symhist *sh)
+static int
+hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
+               struct symbol *sym, uint64_t ip, char level)
 {
-       struct rb_node **p = &global_symhists.rb_node;
+       struct rb_node **p = &hist.rb_node;
        struct rb_node *parent = NULL;
-       struct symhist *iter;
+       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;
-               iter = rb_entry(parent, struct symhist, rb_node);
+               he = rb_entry(parent, struct hist_entry, rb_node);
+
+               cmp = hist_entry__cmp(&entry, he);
+
+               if (!cmp) {
+                       he->count++;
+                       return 0;
+               }
 
-               /* Reverse order */
-               if (sh->count > iter->count)
+               if (cmp < 0)
                        p = &(*p)->rb_left;
                else
                        p = &(*p)->rb_right;
        }
 
-       rb_link_node(&sh->rb_node, parent, p);
-       rb_insert_color(&sh->rb_node, &global_symhists);
+       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 void threads__sort_symhists(void)
+/*
+ * reverse the map, sort on count.
+ */
+
+static struct rb_root output_hists;
+
+static void output__insert_entry(struct hist_entry *he)
 {
-       struct rb_node *nd;
+       struct rb_node **p = &output_hists.rb_node;
+       struct rb_node *parent = NULL;
+       struct hist_entry *iter;
 
-       for (nd = rb_first(&threads); nd; nd = rb_next(nd)) {
-               struct thread *thread = rb_entry(nd, struct thread, rb_node);
-               struct rb_node *next = rb_first(&thread->symhists);
+       while (*p != NULL) {
+               parent = *p;
+               iter = rb_entry(parent, struct hist_entry, rb_node);
 
-               while (next) {
-                       struct symhist *n = rb_entry(next, struct symhist,
-                                                    rb_node);
-                       next = rb_next(&n->rb_node);
-                       rb_erase(&n->rb_node, &thread->symhists);
-                       threads__insert_symhist(n);
-               }
+               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 rb_node *next = rb_first(&hist);
+       struct hist_entry *n;
+
+       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 threads__symhists_fprintf(uint64_t total_samples, FILE *fp)
+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;
 
-       for (nd = rb_first(&global_symhists); nd; nd = rb_next(nd)) {
-               struct symhist *pos = rb_entry(nd, struct symhist, rb_node);
-               ret += symhist__fprintf(pos, total_samples, 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); 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 int __cmd_report(void)
 {
        unsigned long offset = 0;
@@ -795,7 +1049,7 @@ static int __cmd_report(void)
                exit(0);
        }
 
-       if (load_kallsyms() < 0) {
+       if (load_kernel() < 0) {
                perror("failed to open kallsyms");
                return EXIT_FAILURE;
        }
@@ -837,6 +1091,7 @@ more:
                struct dso *dso = NULL;
                struct thread *thread = threads__findnew(event->ip.pid);
                uint64_t ip = event->ip.ip;
+               struct map *map = NULL;
 
                if (dump_trace) {
                        fprintf(stderr, "%p [%p]: PERF_EVENT (IP, %d): %d: %p\n",
@@ -848,17 +1103,18 @@ more:
                }
 
                if (thread == NULL) {
-                       fprintf(stderr, "problem processing %d event, bailing out\n",
+                       fprintf(stderr, "problem processing %d event, skipping it.\n",
                                event->header.type);
-                       goto done;
+                       goto broken_event;
                }
 
                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) {
-                       struct map *map;
 
                        show = SHOW_USER;
                        level = '.';
@@ -868,6 +1124,7 @@ more:
                                dso = map->dso;
                                ip -= map->start + map->pgoff;
                        }
+
                } else {
                        show = SHOW_HV;
                        level = 'H';
@@ -876,9 +1133,10 @@ more:
                if (show & show_mask) {
                        struct symbol *sym = dso__find_symbol(dso, ip);
 
-                       if (thread__symbol_incnew(thread, sym, ip, dso, level)) {
-                               fprintf(stderr, "problem incrementing symbol count, bailing out\n");
-                               goto done;
+                       if (hist_entry__add(thread, map, dso, sym, ip, level)) {
+                               fprintf(stderr,
+               "problem incrementing symbol count, skipping event\n");
+                               goto broken_event;
                        }
                }
                total++;
@@ -897,8 +1155,8 @@ more:
                                event->mmap.filename);
                }
                if (thread == NULL || map == NULL) {
-                       fprintf(stderr, "problem processing PERF_EVENT_MMAP, bailing out\n");
-                       goto done;
+                       fprintf(stderr, "problem processing PERF_EVENT_MMAP, skipping event.\n");
+                       goto broken_event;
                }
                thread__insert_map(thread, map);
                total_mmap++;
@@ -915,18 +1173,20 @@ more:
                }
                if (thread == NULL ||
                    thread__set_comm(thread, event->comm.comm)) {
-                       fprintf(stderr, "problem processing PERF_EVENT_COMM, bailing out\n");
-                       goto done;
+                       fprintf(stderr, "problem processing PERF_EVENT_COMM, skipping event.\n");
+                       goto broken_event;
                }
                total_comm++;
                break;
        }
        default: {
 broken_event:
-               fprintf(stderr, "%p [%p]: skipping unknown header type: %d\n",
-                       (void *)(offset + head),
-                       (void *)(long)(event->header.size),
-                       event->header.type);
+               if (dump_trace)
+                       fprintf(stderr, "%p [%p]: skipping unknown header type: %d\n",
+                                       (void *)(offset + head),
+                                       (void *)(long)(event->header.size),
+                                       event->header.type);
+
                total_unknown++;
 
                /*
@@ -947,7 +1207,6 @@ broken_event:
                goto more;
 
        rc = EXIT_SUCCESS;
-done:
        close(input);
 
        if (dump_trace) {
@@ -959,13 +1218,11 @@ done:
                return 0;
        }
 
-       if (verbose >= 2) {
+       if (verbose >= 2)
                dsos__fprintf(stdout);
-               threads__fprintf(stdout);
-       }
 
-       threads__sort_symhists();
-       threads__symhists_fprintf(total, stdout);
+       output__resort();
+       output__fprintf(stdout, total);
 
        return rc;
 }
@@ -982,6 +1239,8 @@ static const struct option options[] = {
                    "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, "foo", "bar"),
        OPT_END()
 };
 
@@ -993,6 +1252,8 @@ int cmd_report(int argc, const char **argv, const char *prefix)
 
        parse_options(argc, argv, options, report_usage, 0);
 
+       setup_sorting();
+
        setup_pager();
 
        return __cmd_report();
This page took 0.035247 seconds and 5 git commands to generate.