|
@@ -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;
|
|
|
-
|
|
|
- if (dso)
|
|
|
- sym = dso->find_symbol(dso, ip);
|
|
|
+ struct symbol *sym = resolve_symbol(thread, &map, &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()
|
|
|
};
|
|
|
|