Commit 52d422de authored by Arnaldo Carvalho de Melo's avatar Arnaldo Carvalho de Melo Committed by Ingo Molnar

perf report: Adjust column width to the values sampled

Auto-adjust column width of perf report output to the
longest occuring string length.

Example:

[acme@doppio pahole]$  perf report --sort comm,dso,symbol | head -13

    12.79%   pahole  /usr/lib64/libdw-0.141.so    [.] __libdw_find_attr
     8.90%   pahole  /lib64/libc-2.10.1.so        [.] _int_malloc
     8.68%   pahole  /usr/lib64/libdw-0.141.so    [.] __libdw_form_val_len
     8.15%   pahole  /lib64/libc-2.10.1.so        [.] __GI_strcmp
     6.80%   pahole  /lib64/libc-2.10.1.so        [.] __tsearch
     5.54%   pahole  ./build/libdwarves.so.1.0.0  [.] tag__recode_dwarf_type
[acme@doppio pahole]$

[acme@doppio pahole]$  perf report --sort comm,dso,symbol -d /lib64/libc-2.10.1.so | head -10

    21.92%   pahole  /lib64/libc-2.10.1.so  [.] _int_malloc
    20.08%   pahole  /lib64/libc-2.10.1.so  [.] __GI_strcmp
    16.75%   pahole  /lib64/libc-2.10.1.so  [.] __tsearch
[acme@doppio pahole]$

Also add these extra options to control the new behaviour:

  -w, --field-width

Force each column width to the provided list, for large terminal
readability.

  -t, --field-separator:

Use a special separator character and don't pad with spaces, replacing
all occurances of this separator in symbol names (and other output) with
a '.' character, that thus it's the only non valid separator.
Signed-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Paul Mackerras <paulus@samba.org>
LKML-Reference: <20090711014728.GH3452@ghostprotocols.net>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
parent 71a851b4
...@@ -33,6 +33,18 @@ OPTIONS ...@@ -33,6 +33,18 @@ OPTIONS
Only consider these symbols. CSV that understands Only consider these symbols. CSV that understands
file://filename entries. file://filename entries.
-w::
--field-width=::
Force each column width to the provided list, for large terminal
readability.
-t::
--field-separator=::
Use a special separator character and don't pad with spaces, replacing
all occurances of this separator in symbol names (and other output)
with a '.' character, that thus it's the only non valid separator.
SEE ALSO SEE ALSO
-------- --------
linkperf:perf-stat[1] linkperf:perf-stat[1]
...@@ -33,8 +33,10 @@ static char *vmlinux = NULL; ...@@ -33,8 +33,10 @@ static char *vmlinux = NULL;
static char default_sort_order[] = "comm,dso"; static char default_sort_order[] = "comm,dso";
static char *sort_order = default_sort_order; 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 struct strlist *dso_list, *comm_list, *sym_list;
static char *field_sep;
static int input; static int input;
static int show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV; static int show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV;
...@@ -129,6 +131,33 @@ typedef union event_union { ...@@ -129,6 +131,33 @@ typedef union event_union {
struct read_event read; struct read_event read;
} event_t; } 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 LIST_HEAD(dsos);
static struct dso *kernel_dso; static struct dso *kernel_dso;
static struct dso *vdso; static struct dso *vdso;
...@@ -360,12 +389,28 @@ static struct thread *thread__new(pid_t pid) ...@@ -360,12 +389,28 @@ static struct thread *thread__new(pid_t pid)
return self; 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) static int thread__set_comm(struct thread *self, const char *comm)
{ {
if (self->comm) if (self->comm)
free(self->comm); free(self->comm);
self->comm = strdup(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) static size_t thread__fprintf(struct thread *self, FILE *fp)
...@@ -536,7 +581,8 @@ struct sort_entry { ...@@ -536,7 +581,8 @@ struct sort_entry {
int64_t (*cmp)(struct hist_entry *, struct hist_entry *); int64_t (*cmp)(struct hist_entry *, struct hist_entry *);
int64_t (*collapse)(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;
}; };
static int64_t cmp_null(void *l, void *r) static int64_t cmp_null(void *l, void *r)
...@@ -558,15 +604,17 @@ sort__thread_cmp(struct hist_entry *left, struct hist_entry *right) ...@@ -558,15 +604,17 @@ sort__thread_cmp(struct hist_entry *left, struct hist_entry *right)
} }
static size_t 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 = { static struct sort_entry sort_thread = {
.header = " Command: Pid", .header = "Command: Pid",
.cmp = sort__thread_cmp, .cmp = sort__thread_cmp,
.print = sort__thread_print, .print = sort__thread_print,
.width = &threads__col_width,
}; };
/* --sort comm */ /* --sort comm */
...@@ -590,16 +638,17 @@ sort__comm_collapse(struct hist_entry *left, struct hist_entry *right) ...@@ -590,16 +638,17 @@ sort__comm_collapse(struct hist_entry *left, struct hist_entry *right)
} }
static size_t 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 = { static struct sort_entry sort_comm = {
.header = " Command", .header = "Command",
.cmp = sort__comm_cmp, .cmp = sort__comm_cmp,
.collapse = sort__comm_collapse, .collapse = sort__comm_collapse,
.print = sort__comm_print, .print = sort__comm_print,
.width = &comms__col_width,
}; };
/* --sort dso */ /* --sort dso */
...@@ -617,18 +666,19 @@ sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) ...@@ -617,18 +666,19 @@ sort__dso_cmp(struct hist_entry *left, struct hist_entry *right)
} }
static size_t 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) 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 = { static struct sort_entry sort_dso = {
.header = "Shared Object ", .header = "Shared Object",
.cmp = sort__dso_cmp, .cmp = sort__dso_cmp,
.print = sort__dso_print, .print = sort__dso_print,
.width = &dsos__col_width,
}; };
/* --sort symbol */ /* --sort symbol */
...@@ -648,22 +698,23 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) ...@@ -648,22 +698,23 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
} }
static size_t 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; size_t ret = 0;
if (verbose) if (verbose)
ret += fprintf(fp, "%#018llx ", (u64)self->ip); ret += repsep_fprintf(fp, "%#018llx ", (u64)self->ip);
if (self->sym) { if (self->sym) {
ret += fprintf(fp, "[%c] %s", ret += repsep_fprintf(fp, "[%c] %s",
self->dso == kernel_dso ? 'k' : self->dso == kernel_dso ? 'k' :
self->dso == hypervisor_dso ? 'h' : '.', self->sym->name); self->dso == hypervisor_dso ? 'h' : '.', self->sym->name);
if (self->sym->module) if (self->sym->module)
ret += fprintf(fp, "\t[%s]", self->sym->module->name); ret += repsep_fprintf(fp, "\t[%s]",
self->sym->module->name);
} else { } else {
ret += fprintf(fp, "%#016llx", (u64)self->ip); ret += repsep_fprintf(fp, "%#016llx", (u64)self->ip);
} }
return ret; return ret;
...@@ -690,19 +741,19 @@ sort__parent_cmp(struct hist_entry *left, struct hist_entry *right) ...@@ -690,19 +741,19 @@ sort__parent_cmp(struct hist_entry *left, struct hist_entry *right)
} }
static size_t 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; return repsep_fprintf(fp, "%-*s", width,
self->parent ? self->parent->name : "[other]");
ret += fprintf(fp, "%-20s", self->parent ? self->parent->name : "[other]");
return ret;
} }
static unsigned int parent_symbol__col_width;
static struct sort_entry sort_parent = { static struct sort_entry sort_parent = {
.header = "Parent symbol ", .header = "Parent symbol",
.cmp = sort__parent_cmp, .cmp = sort__parent_cmp,
.print = sort__parent_print, .print = sort__parent_print,
.width = &parent_symbol__col_width,
}; };
static int sort__need_collapse = 0; static int sort__need_collapse = 0;
...@@ -967,17 +1018,18 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples) ...@@ -967,17 +1018,18 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples)
return 0; return 0;
if (total_samples) if (total_samples)
ret = percent_color_fprintf(fp, " %6.2f%%", ret = percent_color_fprintf(fp,
(self->count * 100.0) / total_samples); field_sep ? "%.2f" : " %6.2f%%",
(self->count * 100.0) / total_samples);
else else
ret = fprintf(fp, "%12Ld ", self->count); ret = fprintf(fp, field_sep ? "%lld" : "%12lld ", self->count);
list_for_each_entry(se, &hist_entry__sort_list, list) { list_for_each_entry(se, &hist_entry__sort_list, list) {
if (exclude_other && (se == &sort_parent)) if (exclude_other && (se == &sort_parent))
continue; continue;
fprintf(fp, " "); fprintf(fp, "%s", field_sep ?: " ");
ret += se->print(fp, self); ret += se->print(fp, self, se->width ? *se->width : 0);
} }
ret += fprintf(fp, "\n"); ret += fprintf(fp, "\n");
...@@ -992,6 +1044,18 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples) ...@@ -992,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 * static struct symbol *
resolve_symbol(struct thread *thread, struct map **mapp, resolve_symbol(struct thread *thread, struct map **mapp,
struct dso **dsop, u64 *ipp) struct dso **dsop, u64 *ipp)
...@@ -1011,6 +1075,14 @@ resolve_symbol(struct thread *thread, struct map **mapp, ...@@ -1011,6 +1075,14 @@ resolve_symbol(struct thread *thread, struct map **mapp,
map = thread__find_map(thread, ip); map = thread__find_map(thread, ip);
if (map != NULL) { 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 (!map->dso->slen_calculated)
dso__calc_col_width(map->dso);
if (mapp) if (mapp)
*mapp = map; *mapp = map;
got_map: got_map:
...@@ -1282,6 +1354,8 @@ static size_t output__fprintf(FILE *fp, u64 total_samples) ...@@ -1282,6 +1354,8 @@ static size_t output__fprintf(FILE *fp, u64 total_samples)
struct sort_entry *se; struct sort_entry *se;
struct rb_node *nd; struct rb_node *nd;
size_t ret = 0; size_t ret = 0;
unsigned int width;
char *col_width = col_width_list_str;
fprintf(fp, "\n"); fprintf(fp, "\n");
fprintf(fp, "#\n"); fprintf(fp, "#\n");
...@@ -1292,10 +1366,29 @@ static size_t output__fprintf(FILE *fp, u64 total_samples) ...@@ -1292,10 +1366,29 @@ static size_t output__fprintf(FILE *fp, u64 total_samples)
list_for_each_entry(se, &hist_entry__sort_list, list) { list_for_each_entry(se, &hist_entry__sort_list, list) {
if (exclude_other && (se == &sort_parent)) if (exclude_other && (se == &sort_parent))
continue; continue;
fprintf(fp, " %s", se->header); if (field_sep) {
fprintf(fp, "%c%s", *field_sep, se->header);
continue;
}
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"); fprintf(fp, "\n");
if (field_sep)
goto print_entries;
fprintf(fp, "# ........"); fprintf(fp, "# ........");
list_for_each_entry(se, &hist_entry__sort_list, list) { list_for_each_entry(se, &hist_entry__sort_list, list) {
unsigned int i; unsigned int i;
...@@ -1304,13 +1397,18 @@ static size_t output__fprintf(FILE *fp, u64 total_samples) ...@@ -1304,13 +1397,18 @@ static size_t output__fprintf(FILE *fp, u64 total_samples)
continue; continue;
fprintf(fp, " "); 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, ".");
} }
fprintf(fp, "\n"); fprintf(fp, "\n");
fprintf(fp, "#\n"); fprintf(fp, "#\n");
print_entries:
for (nd = rb_first(&output_hists); nd; nd = rb_next(nd)) { for (nd = rb_first(&output_hists); nd; nd = rb_next(nd)) {
pos = rb_entry(nd, struct hist_entry, rb_node); pos = rb_entry(nd, struct hist_entry, rb_node);
ret += hist_entry__fprintf(fp, pos, total_samples); ret += hist_entry__fprintf(fp, pos, total_samples);
...@@ -1900,6 +1998,12 @@ static const struct option options[] = { ...@@ -1900,6 +1998,12 @@ static const struct option options[] = {
"only consider symbols in these comms"), "only consider symbols in these comms"),
OPT_STRING('S', "symbols", &sym_list_str, "symbol[,symbol...]", OPT_STRING('S', "symbols", &sym_list_str, "symbol[,symbol...]",
"only consider these symbols"), "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() OPT_END()
}; };
...@@ -1956,6 +2060,12 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) ...@@ -1956,6 +2060,12 @@ int cmd_report(int argc, const char **argv, const char *prefix __used)
setup_list(&comm_list, comm_list_str, "comm"); setup_list(&comm_list, comm_list_str, "comm");
setup_list(&sym_list, sym_list_str, "symbol"); setup_list(&sym_list, sym_list_str, "symbol");
if (field_sep && *field_sep == '.') {
fputs("'.' is the only non valid --field-separator argument\n",
stderr);
exit(129);
}
setup_pager(); setup_pager();
return __cmd_report(); return __cmd_report();
......
...@@ -18,4 +18,12 @@ ...@@ -18,4 +18,12 @@
(type *)((char *)__mptr - offsetof(type, member)); }) (type *)((char *)__mptr - offsetof(type, member)); })
#endif #endif
#ifndef max
#define max(x, y) ({ \
typeof(x) _max1 = (x); \
typeof(y) _max2 = (y); \
(void) (&_max1 == &_max2); \
_max1 > _max2 ? _max1 : _max2; })
#endif
#endif #endif
...@@ -65,6 +65,7 @@ struct dso *dso__new(const char *name, unsigned int sym_priv_size) ...@@ -65,6 +65,7 @@ struct dso *dso__new(const char *name, unsigned int sym_priv_size)
self->syms = RB_ROOT; self->syms = RB_ROOT;
self->sym_priv_size = sym_priv_size; self->sym_priv_size = sym_priv_size;
self->find_symbol = dso__find_symbol; self->find_symbol = dso__find_symbol;
self->slen_calculated = 0;
} }
return self; return self;
......
...@@ -25,6 +25,7 @@ struct dso { ...@@ -25,6 +25,7 @@ struct dso {
struct symbol *(*find_symbol)(struct dso *, u64 ip); struct symbol *(*find_symbol)(struct dso *, u64 ip);
unsigned int sym_priv_size; unsigned int sym_priv_size;
unsigned char adjust_symbols; unsigned char adjust_symbols;
unsigned char slen_calculated;
char name[0]; char name[0];
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment