Merge branch 'for-linus' of git://git.infradead.org/users/eparis/notify
[pandora-kernel.git] / tools / perf / builtin-report.c
index 8bd5865..b20a4b6 100644 (file)
@@ -33,8 +33,10 @@ static char          *vmlinux = NULL;
 
 static char            default_sort_order[] = "comm,dso";
 static char            *sort_order = default_sort_order;
-static char            *dso_list_str, *comm_list_str, *sym_list_str;
+static char            *dso_list_str, *comm_list_str, *sym_list_str,
+                       *col_width_list_str;
 static struct strlist  *dso_list, *comm_list, *sym_list;
+static char            *field_sep;
 
 static int             input;
 static int             show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV;
@@ -49,6 +51,7 @@ static int            verbose;
 static int             modules;
 
 static int             full_paths;
+static int             show_nr_samples;
 
 static unsigned long   page_size;
 static unsigned long   mmap_window = 32;
@@ -59,10 +62,15 @@ static regex_t              parent_regex;
 
 static int             exclude_other = 1;
 
-static char            callchain_default_opt[] = "graph,0.5";
+static char            callchain_default_opt[] = "fractal,0.5";
+
 static int             callchain;
-static enum chain_mode callchain_mode;
-static double          callchain_min_percent = 0.5;
+
+static
+struct callchain_param callchain_param = {
+       .mode   = CHAIN_GRAPH_ABS,
+       .min_percent = 0.5
+};
 
 static u64             sample_type;
 
@@ -93,13 +101,6 @@ struct fork_event {
        u32 pid, ppid;
 };
 
-struct period_event {
-       struct perf_event_header header;
-       u64 time;
-       u64 id;
-       u64 sample_period;
-};
-
 struct lost_event {
        struct perf_event_header header;
        u64 id;
@@ -119,11 +120,37 @@ typedef union event_union {
        struct mmap_event               mmap;
        struct comm_event               comm;
        struct fork_event               fork;
-       struct period_event             period;
        struct lost_event               lost;
        struct read_event               read;
 } event_t;
 
+static int repsep_fprintf(FILE *fp, const char *fmt, ...)
+{
+       int n;
+       va_list ap;
+
+       va_start(ap, fmt);
+       if (!field_sep)
+               n = vfprintf(fp, fmt, ap);
+       else {
+               char *bf = NULL;
+               n = vasprintf(&bf, fmt, ap);
+               if (n > 0) {
+                       char *sep = bf;
+                       while (1) {
+                               sep = strchr(sep, *field_sep);
+                               if (sep == NULL)
+                                       break;
+                               *sep = '.';
+                       }
+               }
+               fputs(bf, fp);
+               free(bf);
+       }
+       va_end(ap);
+       return n;
+}
+
 static LIST_HEAD(dsos);
 static struct dso *kernel_dso;
 static struct dso *vdso;
@@ -355,12 +382,28 @@ static struct thread *thread__new(pid_t pid)
        return self;
 }
 
+static unsigned int dsos__col_width,
+                   comms__col_width,
+                   threads__col_width;
+
 static int thread__set_comm(struct thread *self, const char *comm)
 {
        if (self->comm)
                free(self->comm);
        self->comm = strdup(comm);
-       return self->comm ? 0 : -ENOMEM;
+       if (!self->comm)
+               return -ENOMEM;
+
+       if (!col_width_list_str && !field_sep &&
+           (!comm_list || strlist__has_entry(comm_list, comm))) {
+               unsigned int slen = strlen(comm);
+               if (slen > comms__col_width) {
+                       comms__col_width = slen;
+                       threads__col_width = slen + 6;
+               }
+       }
+
+       return 0;
 }
 
 static size_t thread__fprintf(struct thread *self, FILE *fp)
@@ -531,7 +574,9 @@ struct sort_entry {
 
        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 *);
+       size_t  (*print)(FILE *fp, struct hist_entry *, unsigned int width);
+       unsigned int *width;
+       bool    elide;
 };
 
 static int64_t cmp_null(void *l, void *r)
@@ -553,15 +598,17 @@ sort__thread_cmp(struct hist_entry *left, struct hist_entry *right)
 }
 
 static size_t
-sort__thread_print(FILE *fp, struct hist_entry *self)
+sort__thread_print(FILE *fp, struct hist_entry *self, unsigned int width)
 {
-       return fprintf(fp, "%16s:%5d", self->thread->comm ?: "", self->thread->pid);
+       return repsep_fprintf(fp, "%*s:%5d", width - 6,
+                             self->thread->comm ?: "", self->thread->pid);
 }
 
 static struct sort_entry sort_thread = {
-       .header = "         Command:  Pid",
+       .header = "Command:  Pid",
        .cmp    = sort__thread_cmp,
        .print  = sort__thread_print,
+       .width  = &threads__col_width,
 };
 
 /* --sort comm */
@@ -585,16 +632,17 @@ sort__comm_collapse(struct hist_entry *left, struct hist_entry *right)
 }
 
 static size_t
-sort__comm_print(FILE *fp, struct hist_entry *self)
+sort__comm_print(FILE *fp, struct hist_entry *self, unsigned int width)
 {
-       return fprintf(fp, "%16s", self->thread->comm);
+       return repsep_fprintf(fp, "%*s", width, self->thread->comm);
 }
 
 static struct sort_entry sort_comm = {
-       .header         = "         Command",
+       .header         = "Command",
        .cmp            = sort__comm_cmp,
        .collapse       = sort__comm_collapse,
        .print          = sort__comm_print,
+       .width          = &comms__col_width,
 };
 
 /* --sort dso */
@@ -612,18 +660,19 @@ sort__dso_cmp(struct hist_entry *left, struct hist_entry *right)
 }
 
 static size_t
-sort__dso_print(FILE *fp, struct hist_entry *self)
+sort__dso_print(FILE *fp, struct hist_entry *self, unsigned int width)
 {
        if (self->dso)
-               return fprintf(fp, "%-25s", self->dso->name);
+               return repsep_fprintf(fp, "%-*s", width, self->dso->name);
 
-       return fprintf(fp, "%016llx         ", (u64)self->ip);
+       return repsep_fprintf(fp, "%*llx", width, (u64)self->ip);
 }
 
 static struct sort_entry sort_dso = {
-       .header = "Shared Object            ",
+       .header = "Shared Object",
        .cmp    = sort__dso_cmp,
        .print  = sort__dso_print,
+       .width  = &dsos__col_width,
 };
 
 /* --sort symbol */
@@ -643,22 +692,22 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
 }
 
 static size_t
-sort__sym_print(FILE *fp, struct hist_entry *self)
+sort__sym_print(FILE *fp, struct hist_entry *self, unsigned int width __used)
 {
        size_t ret = 0;
 
        if (verbose)
-               ret += fprintf(fp, "%#018llx  ", (u64)self->ip);
+               ret += repsep_fprintf(fp, "%#018llx  ", (u64)self->ip);
 
+       ret += repsep_fprintf(fp, "[%c] ", self->level);
        if (self->sym) {
-               ret += fprintf(fp, "[%c] %s",
-                       self->dso == kernel_dso ? 'k' :
-                       self->dso == hypervisor_dso ? 'h' : '.', self->sym->name);
+               ret += repsep_fprintf(fp, "%s", self->sym->name);
 
                if (self->sym->module)
-                       ret += fprintf(fp, "\t[%s]", self->sym->module->name);
+                       ret += repsep_fprintf(fp, "\t[%s]",
+                                            self->sym->module->name);
        } else {
-               ret += fprintf(fp, "%#016llx", (u64)self->ip);
+               ret += repsep_fprintf(fp, "%#016llx", (u64)self->ip);
        }
 
        return ret;
@@ -685,19 +734,19 @@ sort__parent_cmp(struct hist_entry *left, struct hist_entry *right)
 }
 
 static size_t
-sort__parent_print(FILE *fp, struct hist_entry *self)
+sort__parent_print(FILE *fp, struct hist_entry *self, unsigned int width)
 {
-       size_t ret = 0;
-
-       ret += fprintf(fp, "%-20s", self->parent ? self->parent->name : "[other]");
-
-       return ret;
+       return repsep_fprintf(fp, "%-*s", width,
+                             self->parent ? self->parent->name : "[other]");
 }
 
+static unsigned int parent_symbol__col_width;
+
 static struct sort_entry sort_parent = {
-       .header = "Parent symbol       ",
+       .header = "Parent symbol",
        .cmp    = sort__parent_cmp,
        .print  = sort__parent_print,
+       .width  = &parent_symbol__col_width,
 };
 
 static int sort__need_collapse = 0;
@@ -846,9 +895,15 @@ callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
        struct callchain_node *child;
        struct callchain_list *chain;
        int new_depth_mask = depth_mask;
+       u64 new_total;
        size_t ret = 0;
        int i;
 
+       if (callchain_param.mode == CHAIN_GRAPH_REL)
+               new_total = self->cumul_hit;
+       else
+               new_total = total_samples;
+
        node = rb_first(&self->rb_root);
        while (node) {
                child = rb_entry(node, struct callchain_node, rb_node);
@@ -873,10 +928,10 @@ callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
                                continue;
                        ret += ipchain__fprintf_graph(fp, chain, depth,
                                                      new_depth_mask, i++,
-                                                     total_samples,
+                                                     new_total,
                                                      child->cumul_hit);
                }
-               ret += callchain__fprintf_graph(fp, child, total_samples,
+               ret += callchain__fprintf_graph(fp, child, new_total,
                                                depth + 1,
                                                new_depth_mask | (1 << depth));
                node = next;
@@ -925,13 +980,18 @@ hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self,
 
                chain = rb_entry(rb_node, struct callchain_node, rb_node);
                percent = chain->hit * 100.0 / total_samples;
-               if (callchain_mode == FLAT) {
+               switch (callchain_param.mode) {
+               case CHAIN_FLAT:
                        ret += percent_color_fprintf(fp, "           %6.2f%%\n",
                                                     percent);
                        ret += callchain__fprintf_flat(fp, chain, total_samples);
-               } else if (callchain_mode == GRAPH) {
+                       break;
+               case CHAIN_GRAPH_ABS: /* Falldown */
+               case CHAIN_GRAPH_REL:
                        ret += callchain__fprintf_graph(fp, chain,
                                                        total_samples, 1, 1);
+               default:
+                       break;
                }
                ret += fprintf(fp, "\n");
                rb_node = rb_next(rb_node);
@@ -951,17 +1011,25 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples)
                return 0;
 
        if (total_samples)
-               ret = percent_color_fprintf(fp, "   %6.2f%%",
-                               (self->count * 100.0) / total_samples);
+               ret = percent_color_fprintf(fp,
+                                           field_sep ? "%.2f" : "   %6.2f%%",
+                                       (self->count * 100.0) / total_samples);
        else
-               ret = fprintf(fp, "%12Ld ", self->count);
+               ret = fprintf(fp, field_sep ? "%lld" : "%12lld ", self->count);
+
+       if (show_nr_samples) {
+               if (field_sep)
+                       fprintf(fp, "%c%lld", *field_sep, self->count);
+               else
+                       fprintf(fp, "%11lld", self->count);
+       }
 
        list_for_each_entry(se, &hist_entry__sort_list, list) {
-               if (exclude_other && (se == &sort_parent))
+               if (se->elide)
                        continue;
 
-               fprintf(fp, "  ");
-               ret += se->print(fp, self);
+               fprintf(fp, "%s", field_sep ?: "  ");
+               ret += se->print(fp, self, se->width ? *se->width : 0);
        }
 
        ret += fprintf(fp, "\n");
@@ -976,6 +1044,18 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples)
  *
  */
 
+static void dso__calc_col_width(struct dso *self)
+{
+       if (!col_width_list_str && !field_sep &&
+           (!dso_list || strlist__has_entry(dso_list, self->name))) {
+               unsigned int slen = strlen(self->name);
+               if (slen > dsos__col_width)
+                       dsos__col_width = slen;
+       }
+
+       self->slen_calculated = 1;
+}
+
 static struct symbol *
 resolve_symbol(struct thread *thread, struct map **mapp,
               struct dso **dsop, u64 *ipp)
@@ -995,6 +1075,14 @@ resolve_symbol(struct thread *thread, struct map **mapp,
 
        map = thread__find_map(thread, ip);
        if (map != NULL) {
+               /*
+                * We have to do this here as we may have a dso
+                * with no symbol hit that has a name longer than
+                * the ones with symbols sampled.
+                */
+               if (!sort_dso.elide && !map->dso->slen_calculated)
+                       dso__calc_col_width(map->dso);
+
                if (mapp)
                        *mapp = map;
 got_map:
@@ -1219,14 +1307,9 @@ static void output__insert_entry(struct hist_entry *he, u64 min_callchain_hits)
        struct rb_node *parent = NULL;
        struct hist_entry *iter;
 
-       if (callchain) {
-               if (callchain_mode == FLAT)
-                       sort_chain_flat(&he->sorted_chain, &he->callchain,
-                                       min_callchain_hits);
-               else if (callchain_mode == GRAPH)
-                       sort_chain_graph(&he->sorted_chain, &he->callchain,
-                                        min_callchain_hits);
-       }
+       if (callchain)
+               callchain_param.sort(&he->sorted_chain, &he->callchain,
+                                     min_callchain_hits, &callchain_param);
 
        while (*p != NULL) {
                parent = *p;
@@ -1249,7 +1332,7 @@ static void output__resort(u64 total_samples)
        struct rb_root *tree = &hist;
        u64 min_callchain_hits;
 
-       min_callchain_hits = total_samples * (callchain_min_percent / 100);
+       min_callchain_hits = total_samples * (callchain_param.min_percent / 100);
 
        if (sort__need_collapse)
                tree = &collapse_hists;
@@ -1271,35 +1354,67 @@ static size_t output__fprintf(FILE *fp, u64 total_samples)
        struct sort_entry *se;
        struct rb_node *nd;
        size_t ret = 0;
+       unsigned int width;
+       char *col_width = col_width_list_str;
 
-       fprintf(fp, "\n");
-       fprintf(fp, "#\n");
-       fprintf(fp, "# (%Ld samples)\n", (u64)total_samples);
+       fprintf(fp, "# Samples: %Ld\n", (u64)total_samples);
        fprintf(fp, "#\n");
 
        fprintf(fp, "# Overhead");
+       if (show_nr_samples) {
+               if (field_sep)
+                       fprintf(fp, "%cSamples", *field_sep);
+               else
+                       fputs("  Samples  ", fp);
+       }
        list_for_each_entry(se, &hist_entry__sort_list, list) {
-               if (exclude_other && (se == &sort_parent))
+               if (se->elide)
+                       continue;
+               if (field_sep) {
+                       fprintf(fp, "%c%s", *field_sep, se->header);
                        continue;
-               fprintf(fp, "  %s", se->header);
+               }
+               width = strlen(se->header);
+               if (se->width) {
+                       if (col_width_list_str) {
+                               if (col_width) {
+                                       *se->width = atoi(col_width);
+                                       col_width = strchr(col_width, ',');
+                                       if (col_width)
+                                               ++col_width;
+                               }
+                       }
+                       width = *se->width = max(*se->width, width);
+               }
+               fprintf(fp, "  %*s", width, se->header);
        }
        fprintf(fp, "\n");
 
+       if (field_sep)
+               goto print_entries;
+
        fprintf(fp, "# ........");
+       if (show_nr_samples)
+               fprintf(fp, " ..........");
        list_for_each_entry(se, &hist_entry__sort_list, list) {
                unsigned int i;
 
-               if (exclude_other && (se == &sort_parent))
+               if (se->elide)
                        continue;
 
                fprintf(fp, "  ");
-               for (i = 0; i < strlen(se->header); i++)
+               if (se->width)
+                       width = *se->width;
+               else
+                       width = strlen(se->header);
+               for (i = 0; i < width; i++)
                        fprintf(fp, ".");
        }
        fprintf(fp, "\n");
 
        fprintf(fp, "#\n");
 
+print_entries:
        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);
@@ -1512,19 +1627,6 @@ process_fork_event(event_t *event, unsigned long offset, unsigned long head)
        return 0;
 }
 
-static int
-process_period_event(event_t *event, unsigned long offset, unsigned long head)
-{
-       dprintf("%p [%p]: PERF_EVENT_PERIOD: time:%Ld, id:%Ld: period:%Ld\n",
-               (void *)(offset + head),
-               (void *)(long)(event->header.size),
-               event->period.time,
-               event->period.id,
-               event->period.sample_period);
-
-       return 0;
-}
-
 static int
 process_lost_event(event_t *event, unsigned long offset, unsigned long head)
 {
@@ -1606,9 +1708,6 @@ process_event(event_t *event, unsigned long offset, unsigned long head)
        case PERF_EVENT_FORK:
                return process_fork_event(event, offset, head);
 
-       case PERF_EVENT_PERIOD:
-               return process_period_event(event, offset, head);
-
        case PERF_EVENT_LOST:
                return process_lost_event(event, offset, head);
 
@@ -1829,22 +1928,31 @@ parse_callchain_opt(const struct option *opt __used, const char *arg,
 
        /* get the output mode */
        if (!strncmp(tok, "graph", strlen(arg)))
-               callchain_mode = GRAPH;
+               callchain_param.mode = CHAIN_GRAPH_ABS;
 
        else if (!strncmp(tok, "flat", strlen(arg)))
-               callchain_mode = FLAT;
+               callchain_param.mode = CHAIN_FLAT;
+
+       else if (!strncmp(tok, "fractal", strlen(arg)))
+               callchain_param.mode = CHAIN_GRAPH_REL;
+
        else
                return -1;
 
        /* get the min percentage */
        tok = strtok(NULL, ",");
        if (!tok)
-               return 0;
+               goto setup;
 
-       callchain_min_percent = strtod(tok, &endptr);
+       callchain_param.min_percent = strtod(tok, &endptr);
        if (tok == endptr)
                return -1;
 
+setup:
+       if (register_callchain_param(&callchain_param) < 0) {
+               fprintf(stderr, "Can't register callchain params\n");
+               return -1;
+       }
        return 0;
 }
 
@@ -1863,6 +1971,8 @@ static const struct option options[] = {
        OPT_STRING('k', "vmlinux", &vmlinux, "file", "vmlinux pathname"),
        OPT_BOOLEAN('m', "modules", &modules,
                    "load module symbols - WARNING: use only with -k and LIVE kernel"),
+       OPT_BOOLEAN('n', "show-nr-samples", &show_nr_samples,
+                   "Show a column with the number of samples"),
        OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
                   "sort by key(s): pid, comm, dso, symbol, parent"),
        OPT_BOOLEAN('P', "full-paths", &full_paths,
@@ -1871,15 +1981,21 @@ static const struct option options[] = {
                   "regex filter to identify parent, see: '--sort parent'"),
        OPT_BOOLEAN('x', "exclude-other", &exclude_other,
                    "Only display entries with parent-match"),
-       OPT_CALLBACK_DEFAULT('c', "callchain", NULL, "output_type,min_percent",
+       OPT_CALLBACK_DEFAULT('g', "call-graph", NULL, "output_type,min_percent",
                     "Display callchains using output_type and min percent threshold. "
-                    "Default: flat,0", &parse_callchain_opt, callchain_default_opt),
+                    "Default: fractal,0.5", &parse_callchain_opt, callchain_default_opt),
        OPT_STRING('d', "dsos", &dso_list_str, "dso[,dso...]",
                   "only consider symbols in these dsos"),
        OPT_STRING('C', "comms", &comm_list_str, "comm[,comm...]",
                   "only consider symbols in these comms"),
        OPT_STRING('S', "symbols", &sym_list_str, "symbol[,symbol...]",
                   "only consider these symbols"),
+       OPT_STRING('w', "column-widths", &col_width_list_str,
+                  "width[,width...]",
+                  "don't try to adjust column width, use these fixed values"),
+       OPT_STRING('t', "field-separator", &field_sep, "separator",
+                  "separator for columns, no spaces will be added between "
+                  "columns '.' is reserved."),
        OPT_END()
 };
 
@@ -1899,7 +2015,8 @@ static void setup_sorting(void)
 }
 
 static void setup_list(struct strlist **list, const char *list_str,
-                      const char *list_name)
+                      struct sort_entry *se, const char *list_name,
+                      FILE *fp)
 {
        if (list_str) {
                *list = strlist__new(true, list_str);
@@ -1908,6 +2025,11 @@ static void setup_list(struct strlist **list, const char *list_str,
                                list_name);
                        exit(129);
                }
+               if (strlist__nr_entries(*list) == 1) {
+                       fprintf(fp, "# %s: %s\n", list_name,
+                               strlist__entry(*list, 0)->s);
+                       se->elide = true;
+               }
        }
 }
 
@@ -1921,9 +2043,10 @@ int cmd_report(int argc, const char **argv, const char *prefix __used)
 
        setup_sorting();
 
-       if (parent_pattern != default_parent_pattern)
+       if (parent_pattern != default_parent_pattern) {
                sort_dimension__add("parent");
-       else
+               sort_parent.elide = 1;
+       } else
                exclude_other = 0;
 
        /*
@@ -1932,11 +2055,17 @@ int cmd_report(int argc, const char **argv, const char *prefix __used)
        if (argc)
                usage_with_options(report_usage, options);
 
-       setup_list(&dso_list, dso_list_str, "dso");
-       setup_list(&comm_list, comm_list_str, "comm");
-       setup_list(&sym_list, sym_list_str, "symbol");
-
        setup_pager();
 
+       setup_list(&dso_list, dso_list_str, &sort_dso, "dso", stdout);
+       setup_list(&comm_list, comm_list_str, &sort_comm, "comm", stdout);
+       setup_list(&sym_list, sym_list_str, &sort_sym, "symbol", stdout);
+
+       if (field_sep && *field_sep == '.') {
+               fputs("'.' is the only non valid --field-separator argument\n",
+                     stderr);
+               exit(129);
+       }
+
        return __cmd_report();
 }