perf report: Add --sort <call> --call <$regex>
Implement sorting by callchain symbols, --sort <call>. It will create a new column which will show a match to --call $regex or "[unmatched]". Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Mike Galbraith <efault@gmx.de> Cc: Paul Mackerras <paulus@samba.org> Cc: Arnaldo Carvalho de Melo <acme@redhat.com> LKML-Reference: <new-submission> Signed-off-by: Ingo Molnar <mingo@elte.hu>
This commit is contained in:
parent
a3d06cc6aa
commit
6e7d6fdcbe
1 changed files with 158 additions and 51 deletions
|
@ -40,11 +40,13 @@ static int dump_trace = 0;
|
|||
|
||||
static int verbose;
|
||||
static int full_paths;
|
||||
static int collapse_syscalls;
|
||||
|
||||
static unsigned long page_size;
|
||||
static unsigned long mmap_window = 32;
|
||||
|
||||
static char *call = "^sys_";
|
||||
static regex_t call_regex;
|
||||
|
||||
struct ip_chain_event {
|
||||
__u16 nr;
|
||||
__u16 hv;
|
||||
|
@ -463,6 +465,7 @@ struct hist_entry {
|
|||
struct map *map;
|
||||
struct dso *dso;
|
||||
struct symbol *sym;
|
||||
struct symbol *call;
|
||||
__u64 ip;
|
||||
char level;
|
||||
|
||||
|
@ -483,6 +486,16 @@ struct sort_entry {
|
|||
size_t (*print)(FILE *fp, struct hist_entry *);
|
||||
};
|
||||
|
||||
static int64_t cmp_null(void *l, void *r)
|
||||
{
|
||||
if (!l && !r)
|
||||
return 0;
|
||||
else if (!l)
|
||||
return -1;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* --sort pid */
|
||||
|
||||
static int64_t
|
||||
|
@ -517,14 +530,8 @@ sort__comm_collapse(struct hist_entry *left, struct hist_entry *right)
|
|||
char *comm_l = left->thread->comm;
|
||||
char *comm_r = right->thread->comm;
|
||||
|
||||
if (!comm_l || !comm_r) {
|
||||
if (!comm_l && !comm_r)
|
||||
return 0;
|
||||
else if (!comm_l)
|
||||
return -1;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
if (!comm_l || !comm_r)
|
||||
return cmp_null(comm_l, comm_r);
|
||||
|
||||
return strcmp(comm_l, comm_r);
|
||||
}
|
||||
|
@ -550,14 +557,8 @@ 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;
|
||||
}
|
||||
if (!dso_l || !dso_r)
|
||||
return cmp_null(dso_l, dso_r);
|
||||
|
||||
return strcmp(dso_l->name, dso_r->name);
|
||||
}
|
||||
|
@ -617,7 +618,38 @@ static struct sort_entry sort_sym = {
|
|||
.print = sort__sym_print,
|
||||
};
|
||||
|
||||
/* --sort call */
|
||||
|
||||
static int64_t
|
||||
sort__call_cmp(struct hist_entry *left, struct hist_entry *right)
|
||||
{
|
||||
struct symbol *sym_l = left->call;
|
||||
struct symbol *sym_r = right->call;
|
||||
|
||||
if (!sym_l || !sym_r)
|
||||
return cmp_null(sym_l, sym_r);
|
||||
|
||||
return strcmp(sym_l->name, sym_r->name);
|
||||
}
|
||||
|
||||
static size_t
|
||||
sort__call_print(FILE *fp, struct hist_entry *self)
|
||||
{
|
||||
size_t ret = 0;
|
||||
|
||||
ret += fprintf(fp, "%-20s", self->call ? self->call->name : "[unmatched]");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sort_entry sort_call = {
|
||||
.header = "Callchain symbol ",
|
||||
.cmp = sort__call_cmp,
|
||||
.print = sort__call_print,
|
||||
};
|
||||
|
||||
static int sort__need_collapse = 0;
|
||||
static int sort__has_call = 0;
|
||||
|
||||
struct sort_dimension {
|
||||
char *name;
|
||||
|
@ -630,6 +662,7 @@ static struct sort_dimension sort_dimensions[] = {
|
|||
{ .name = "comm", .entry = &sort_comm, },
|
||||
{ .name = "dso", .entry = &sort_dso, },
|
||||
{ .name = "symbol", .entry = &sort_sym, },
|
||||
{ .name = "call", .entry = &sort_call, },
|
||||
};
|
||||
|
||||
static LIST_HEAD(hist_entry__sort_list);
|
||||
|
@ -650,6 +683,18 @@ static int sort_dimension__add(char *tok)
|
|||
if (sd->entry->collapse)
|
||||
sort__need_collapse = 1;
|
||||
|
||||
if (sd->entry == &sort_call) {
|
||||
int ret = regcomp(&call_regex, call, REG_EXTENDED);
|
||||
if (ret) {
|
||||
char err[BUFSIZ];
|
||||
|
||||
regerror(ret, &call_regex, err, sizeof(err));
|
||||
fprintf(stderr, "Invalid regex: %s\n%s", call, err);
|
||||
exit(-1);
|
||||
}
|
||||
sort__has_call = 1;
|
||||
}
|
||||
|
||||
list_add_tail(&sd->entry->list, &hist_entry__sort_list);
|
||||
sd->taken = 1;
|
||||
|
||||
|
@ -730,13 +775,76 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, __u64 total_samples)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
static struct symbol *
|
||||
resolve_symbol(struct thread *thread, struct map **mapp,
|
||||
struct dso **dsop, __u64 *ipp)
|
||||
{
|
||||
struct dso *dso = dsop ? *dsop : NULL;
|
||||
struct map *map = mapp ? *mapp : NULL;
|
||||
uint64_t ip = *ipp;
|
||||
|
||||
if (!thread)
|
||||
return NULL;
|
||||
|
||||
if (dso)
|
||||
goto got_dso;
|
||||
|
||||
if (map)
|
||||
goto got_map;
|
||||
|
||||
map = thread__find_map(thread, ip);
|
||||
if (map != NULL) {
|
||||
if (mapp)
|
||||
*mapp = map;
|
||||
got_map:
|
||||
ip = map->map_ip(map, ip);
|
||||
*ipp = ip;
|
||||
|
||||
dso = map->dso;
|
||||
} else {
|
||||
/*
|
||||
* If this is outside of all known maps,
|
||||
* and is a negative address, try to look it
|
||||
* up in the kernel dso, as it might be a
|
||||
* vsyscall (which executes in user-mode):
|
||||
*/
|
||||
if ((long long)ip < 0)
|
||||
dso = kernel_dso;
|
||||
}
|
||||
dprintf(" ...... dso: %s\n", dso ? dso->name : "<not found>");
|
||||
|
||||
if (dsop)
|
||||
*dsop = dso;
|
||||
|
||||
if (!dso)
|
||||
return NULL;
|
||||
got_dso:
|
||||
return dso->find_symbol(dso, ip);
|
||||
}
|
||||
|
||||
static struct symbol *call__match(struct symbol *sym)
|
||||
{
|
||||
if (!sym)
|
||||
return NULL;
|
||||
|
||||
if (sym->name && !regexec(&call_regex, sym->name, 0, NULL, 0))
|
||||
return sym;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* collect histogram counts
|
||||
*/
|
||||
|
||||
static int
|
||||
hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
|
||||
struct symbol *sym, __u64 ip, char level, __u64 count)
|
||||
struct symbol *sym, __u64 ip, struct ip_chain_event *chain,
|
||||
char level, __u64 count)
|
||||
{
|
||||
struct rb_node **p = &hist.rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
|
@ -752,6 +860,33 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
|
|||
};
|
||||
int cmp;
|
||||
|
||||
if (sort__has_call && chain) {
|
||||
int i, nr = chain->hv;
|
||||
struct symbol *sym;
|
||||
struct dso *dso;
|
||||
__u64 ip;
|
||||
|
||||
for (i = 0; i < chain->kernel; i++) {
|
||||
ip = chain->ips[nr + i];
|
||||
dso = kernel_dso;
|
||||
sym = resolve_symbol(thread, NULL, &dso, &ip);
|
||||
entry.call = call__match(sym);
|
||||
if (entry.call)
|
||||
goto got_call;
|
||||
}
|
||||
nr += i;
|
||||
|
||||
for (i = 0; i < chain->user; i++) {
|
||||
ip = chain->ips[nr + i];
|
||||
sym = resolve_symbol(thread, NULL, NULL, &ip);
|
||||
entry.call = call__match(sym);
|
||||
if (entry.call)
|
||||
goto got_call;
|
||||
}
|
||||
nr += i;
|
||||
}
|
||||
got_call:
|
||||
|
||||
while (*p != NULL) {
|
||||
parent = *p;
|
||||
he = rb_entry(parent, struct hist_entry, rb_node);
|
||||
|
@ -955,7 +1090,7 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head)
|
|||
__u64 period = 1;
|
||||
struct map *map = NULL;
|
||||
void *more_data = event->ip.__more_data;
|
||||
struct ip_chain_event *chain;
|
||||
struct ip_chain_event *chain = NULL;
|
||||
|
||||
if (event->header.type & PERF_SAMPLE_PERIOD) {
|
||||
period = *(__u64 *)more_data;
|
||||
|
@ -984,15 +1119,6 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head)
|
|||
for (i = 0; i < chain->nr; i++)
|
||||
dprintf("..... %2d: %016Lx\n", i, chain->ips[i]);
|
||||
}
|
||||
if (collapse_syscalls) {
|
||||
/*
|
||||
* Find the all-but-last kernel entry
|
||||
* amongst the call-chains - to get
|
||||
* to the level of system calls:
|
||||
*/
|
||||
if (chain->kernel >= 2)
|
||||
ip = chain->ips[chain->kernel-2];
|
||||
}
|
||||
}
|
||||
|
||||
dprintf(" ... thread: %s:%d\n", thread->comm, thread->pid);
|
||||
|
@ -1016,22 +1142,6 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head)
|
|||
show = SHOW_USER;
|
||||
level = '.';
|
||||
|
||||
map = thread__find_map(thread, ip);
|
||||
if (map != NULL) {
|
||||
ip = map->map_ip(map, ip);
|
||||
dso = map->dso;
|
||||
} else {
|
||||
/*
|
||||
* If this is outside of all known maps,
|
||||
* and is a negative address, try to look it
|
||||
* up in the kernel dso, as it might be a
|
||||
* vsyscall (which executes in user-mode):
|
||||
*/
|
||||
if ((long long)ip < 0)
|
||||
dso = kernel_dso;
|
||||
}
|
||||
dprintf(" ...... dso: %s\n", dso ? dso->name : "<not found>");
|
||||
|
||||
} else {
|
||||
show = SHOW_HV;
|
||||
level = 'H';
|
||||
|
@ -1039,12 +1149,9 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head)
|
|||
}
|
||||
|
||||
if (show & show_mask) {
|
||||
struct symbol *sym = NULL;
|
||||
struct symbol *sym = resolve_symbol(thread, &map, &dso, &ip);
|
||||
|
||||
if (dso)
|
||||
sym = dso->find_symbol(dso, ip);
|
||||
|
||||
if (hist_entry__add(thread, map, dso, sym, ip, level, period)) {
|
||||
if (hist_entry__add(thread, map, dso, sym, ip, chain, level, period)) {
|
||||
fprintf(stderr,
|
||||
"problem incrementing symbol count, skipping event\n");
|
||||
return -1;
|
||||
|
@ -1353,8 +1460,8 @@ static const struct option options[] = {
|
|||
"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_BOOLEAN('S', "syscalls", &collapse_syscalls,
|
||||
"show per syscall summary overhead, using call graph"),
|
||||
OPT_STRING('c', "call", &call, "regex",
|
||||
"regex to use for --sort call"),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue