Newer
Older
} 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>");
dprintf(" ...... map: %Lx -> %Lx\n", *ipp, ip);
*ipp = ip;
if (dsop)
*dsop = dso;
if (!dso)
return NULL;
got_dso:
return dso->find_symbol(dso, ip);
}
static int call__match(struct symbol *sym)

Ingo Molnar
committed
if (sym->name && !regexec(&parent_regex, sym->name, 0, NULL, 0))
static struct symbol **
resolve_callchain(struct thread *thread, struct map *map __used,
struct ip_callchain *chain, struct hist_entry *entry)
{
u64 context = PERF_CONTEXT_MAX;
struct symbol **syms;
unsigned int i;
if (callchain) {
syms = calloc(chain->nr, sizeof(*syms));
if (!syms) {
fprintf(stderr, "Can't allocate memory for symbols\n");
exit(-1);
}
}
for (i = 0; i < chain->nr; i++) {
u64 ip = chain->ips[i];
struct dso *dso = NULL;
struct symbol *sym;
if (ip >= PERF_CONTEXT_MAX) {
context = ip;
continue;
}
switch (context) {
case PERF_CONTEXT_HV:
dso = hypervisor_dso;
break;
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
case PERF_CONTEXT_KERNEL:
dso = kernel_dso;
break;
default:
break;
}
sym = resolve_symbol(thread, NULL, &dso, &ip);
if (sym) {
if (sort__has_parent && call__match(sym) &&
!entry->parent)
entry->parent = sym;
if (!callchain)
break;
syms[i] = sym;
}
}
return syms;
}
/*
* collect histogram counts
*/
static int
hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
struct symbol *sym, u64 ip, struct ip_callchain *chain,
char level, u64 count)

Arnaldo Carvalho de Melo
committed
{
struct rb_node **p = &hist.rb_node;
struct rb_node *parent = NULL;
struct hist_entry *he;
struct symbol **syms = NULL;
struct hist_entry entry = {
.thread = thread,
.map = map,
.dso = dso,
.sym = sym,
.ip = ip,
.level = level,
.count = count,
.sorted_chain = RB_ROOT
};
int cmp;
if ((sort__has_parent || callchain) && chain)
syms = resolve_callchain(thread, map, chain, &entry);
while (*p != NULL) {
parent = *p;
he = rb_entry(parent, struct hist_entry, rb_node);
cmp = hist_entry__cmp(&entry, he);
if (!cmp) {
he->count += count;
if (callchain) {
append_chain(&he->callchain, chain, syms);
free(syms);
}
return 0;
}
if (cmp < 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
he = malloc(sizeof(*he));
if (!he)
return -ENOMEM;
*he = entry;
if (callchain) {
callchain_init(&he->callchain);
append_chain(&he->callchain, chain, syms);
free(syms);
rb_link_node(&he->rb_node, parent, p);
rb_insert_color(&he->rb_node, &hist);
return 0;

Arnaldo Carvalho de Melo
committed
}
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
static void hist_entry__free(struct hist_entry *he)
{
free(he);
}
/*
* collapse the histogram
*/
static struct rb_root collapse_hists;
static void collapse__insert_entry(struct hist_entry *he)
{
struct rb_node **p = &collapse_hists.rb_node;
struct rb_node *parent = NULL;
struct hist_entry *iter;
int64_t cmp;
while (*p != NULL) {
parent = *p;
iter = rb_entry(parent, struct hist_entry, rb_node);
cmp = hist_entry__collapse(iter, he);
if (!cmp) {
iter->count += he->count;
hist_entry__free(he);
return;
}
if (cmp < 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
rb_link_node(&he->rb_node, parent, p);
rb_insert_color(&he->rb_node, &collapse_hists);
}
static void collapse__resort(void)
{
struct rb_node *next;
struct hist_entry *n;
if (!sort__need_collapse)
return;
next = rb_first(&hist);
while (next) {
n = rb_entry(next, struct hist_entry, rb_node);
next = rb_next(&n->rb_node);
rb_erase(&n->rb_node, &hist);
collapse__insert_entry(n);
}
}
/*
* reverse the map, sort on count.
*/
static struct rb_root output_hists;

Frederic Weisbecker
committed
static void output__insert_entry(struct hist_entry *he, u64 min_callchain_hits)
struct rb_node **p = &output_hists.rb_node;
struct rb_node *parent = NULL;
struct hist_entry *iter;
if (callchain) {
if (callchain_mode == FLAT)

Frederic Weisbecker
committed
sort_chain_flat(&he->sorted_chain, &he->callchain,
min_callchain_hits);
else if (callchain_mode == GRAPH)

Frederic Weisbecker
committed
sort_chain_graph(&he->sorted_chain, &he->callchain,
min_callchain_hits);
while (*p != NULL) {
parent = *p;
iter = rb_entry(parent, struct hist_entry, rb_node);
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);

Frederic Weisbecker
committed
static void output__resort(u64 total_samples)
struct hist_entry *n;

Frederic Weisbecker
committed
u64 min_callchain_hits;
min_callchain_hits = total_samples * (callchain_min_percent / 100);
tree = &collapse_hists;
next = rb_first(tree);
while (next) {
n = rb_entry(next, struct hist_entry, rb_node);
next = rb_next(&n->rb_node);

Frederic Weisbecker
committed
output__insert_entry(n, min_callchain_hits);
static size_t output__fprintf(FILE *fp, u64 total_samples)
struct hist_entry *pos;
struct sort_entry *se;
struct rb_node *nd;
size_t ret = 0;
fprintf(fp, "# (%Ld samples)\n", (u64)total_samples);
fprintf(fp, "#\n");
fprintf(fp, "# Overhead");
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (exclude_other && (se == &sort_parent))
continue;
fprintf(fp, " %s", se->header);
fprintf(fp, "\n");
fprintf(fp, "# ........");
list_for_each_entry(se, &hist_entry__sort_list, list) {
unsigned int i;
if (exclude_other && (se == &sort_parent))
continue;
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);
if (sort_order == default_sort_order &&
parent_pattern == default_parent_pattern) {
fprintf(fp, "#\n");
fprintf(fp, "# (For more details, try: perf report --sort comm,dso,symbol)\n");
fprintf(fp, "#\n");
}
return ret;
}
static void register_idle_thread(void)
{
struct thread *thread = threads__findnew(0);
if (thread == NULL ||
thread__set_comm(thread, "[idle]")) {
fprintf(stderr, "problem inserting idle task.\n");
exit(-1);
}
}
static unsigned long total = 0,
total_mmap = 0,
total_comm = 0,
total_fork = 0,
total_unknown = 0,
total_lost = 0;
static int validate_chain(struct ip_callchain *chain, event_t *event)
{
unsigned int chain_size;
chain_size = event->header.size;
chain_size -= (unsigned long)&event->ip.__more_data - (unsigned long)event;
if (chain->nr*sizeof(u64) > chain_size)
return -1;
return 0;
}
process_sample_event(event_t *event, unsigned long offset, unsigned long head)
{
char level;
int show = 0;
struct dso *dso = NULL;
struct thread *thread = threads__findnew(event->ip.pid);
u64 ip = event->ip.ip;
u64 period = 1;
void *more_data = event->ip.__more_data;
struct ip_callchain *chain = NULL;
if (sample_type & PERF_SAMPLE_PERIOD) {
period = *(u64 *)more_data;
more_data += sizeof(u64);
dprintf("%p [%p]: PERF_EVENT_SAMPLE (IP, %d): %d: %p period: %Ld\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->header.misc,
event->ip.pid,
(long long)period);
if (sample_type & PERF_SAMPLE_CALLCHAIN) {
unsigned int i;
chain = (void *)more_data;
dprintf("... chain: nr:%Lu\n", chain->nr);
if (validate_chain(chain, event) < 0) {
eprintf("call-chain problem with event, skipping it.\n");
return 0;
}
if (dump_trace) {
for (i = 0; i < chain->nr; i++)
dprintf("..... %2d: %016Lx\n", i, chain->ips[i]);
dprintf(" ... thread: %s:%d\n", thread->comm, thread->pid);
if (thread == NULL) {
eprintf("problem processing %d event, skipping it.\n",
event->header.type);
return -1;
}
if (comm_list && !strlist__has_entry(comm_list, thread->comm))
return 0;
cpumode = event->header.misc & PERF_EVENT_MISC_CPUMODE_MASK;
if (cpumode == PERF_EVENT_MISC_KERNEL) {
show = SHOW_KERNEL;
level = 'k';
dprintf(" ...... dso: %s\n", dso->name);
} else if (cpumode == PERF_EVENT_MISC_USER) {
show = SHOW_USER;
level = '.';
} else {
show = SHOW_HV;
level = 'H';
dprintf(" ...... dso: [hypervisor]\n");
}

Arnaldo Carvalho de Melo
committed
struct symbol *sym = resolve_symbol(thread, &map, &dso, &ip);

Arnaldo Carvalho de Melo
committed
if (dso_list && dso && dso->name && !strlist__has_entry(dso_list, dso->name))
return 0;
if (sym_list && sym && !strlist__has_entry(sym_list, sym->name))
return 0;
if (hist_entry__add(thread, map, dso, sym, ip, chain, level, period)) {
eprintf("problem incrementing symbol count, skipping event\n");
}

Arnaldo Carvalho de Melo
committed
}
total += period;

Arnaldo Carvalho de Melo
committed
static int
process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread = threads__findnew(event->mmap.pid);
struct map *map = map__new(&event->mmap);
dprintf("%p [%p]: PERF_EVENT_MMAP %d: [%p(%p) @ %p]: %s\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->mmap.pid,
(void *)(long)event->mmap.start,
(void *)(long)event->mmap.len,
(void *)(long)event->mmap.pgoff,
event->mmap.filename);
if (thread == NULL || map == NULL) {
dprintf("problem processing PERF_EVENT_MMAP, skipping event.\n");
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
}
thread__insert_map(thread, map);
total_mmap++;
return 0;
}
static int
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread = threads__findnew(event->comm.pid);
dprintf("%p [%p]: PERF_EVENT_COMM: %s:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->comm.comm, event->comm.pid);
if (thread == NULL ||
thread__set_comm(thread, event->comm.comm)) {
dprintf("problem processing PERF_EVENT_COMM, skipping event.\n");
return -1;

Arnaldo Carvalho de Melo
committed
}
total_comm++;
return 0;
}
static int
process_fork_event(event_t *event, unsigned long offset, unsigned long head)
{
struct thread *thread = threads__findnew(event->fork.pid);
struct thread *parent = threads__findnew(event->fork.ppid);
dprintf("%p [%p]: PERF_EVENT_FORK: %d:%d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->fork.pid, event->fork.ppid);
if (!thread || !parent || thread__fork(thread, parent)) {
dprintf("problem processing PERF_EVENT_FORK, skipping event.\n");
return -1;
}
total_fork++;
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)
{
dprintf("%p [%p]: PERF_EVENT_LOST: id:%Ld: lost:%Ld\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->lost.id,
event->lost.lost);
total_lost += event->lost.lost;
return 0;
}
static void trace_event(event_t *event)
{
unsigned char *raw_event = (void *)event;
char *color = PERF_COLOR_BLUE;
int i, j;
if (!dump_trace)
return;
dprintf(".");
cdprintf("\n. ... raw event: size %d bytes\n", event->header.size);
for (i = 0; i < event->header.size; i++) {
if ((i & 15) == 0) {
dprintf(".");
cdprintf(" %04x: ", i);
}
cdprintf(" %02x", raw_event[i]);
if (((i & 15) == 15) || i == event->header.size-1) {
for (j = 0; j < 15-(i & 15); j++)
for (j = 0; j < (i & 15); j++) {
if (isprint(raw_event[i-15+j]))
cdprintf("%c", raw_event[i-15+j]);
cdprintf("\n");
}
}
dprintf(".\n");
}
static int
process_read_event(event_t *event, unsigned long offset, unsigned long head)
{
dprintf("%p [%p]: PERF_EVENT_READ: %d %d %Lu\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->read.pid,
event->read.tid,
event->read.value);
return 0;
}
static int
process_event(event_t *event, unsigned long offset, unsigned long head)
{
switch (event->header.type) {
case PERF_EVENT_SAMPLE:
return process_sample_event(event, offset, head);
case PERF_EVENT_MMAP:
return process_mmap_event(event, offset, head);
case PERF_EVENT_COMM:
return process_comm_event(event, offset, 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);
case PERF_EVENT_READ:
return process_read_event(event, offset, head);
/*
* We dont process them right now but they are fine:
*/
case PERF_EVENT_THROTTLE:
case PERF_EVENT_UNTHROTTLE:
return 0;
default:
return -1;
}
return 0;
}
static struct perf_header *header;
static u64 perf_header__sample_type(void)
int i;
for (i = 0; i < header->attrs; i++) {
struct perf_header_attr *attr = header->attr[i];
if (!sample_type)
sample_type = attr->attr.sample_type;
else if (sample_type != attr->attr.sample_type)
die("non matching sample_type");
static int __cmd_report(void)
{
int ret, rc = EXIT_FAILURE;
struct stat stat;
event_t *event;
uint32_t size;
register_idle_thread();
input = open(input_name, O_RDONLY);
if (input < 0) {
fprintf(stderr, " failed to open file: %s", input_name);
if (!strcmp(input_name, "perf.data"))
fprintf(stderr, " (try 'perf record' first)");
fprintf(stderr, "\n");
exit(-1);
}
ret = fstat(input, &stat);
if (ret < 0) {
perror("failed to stat file");
exit(-1);
}
if (!stat.st_size) {
fprintf(stderr, "zero-sized file, nothing to do!\n");
exit(0);
}
header = perf_header__read(input);
head = header->data_offset;
sample_type = perf_header__sample_type();
if (sort__has_parent && !(sample_type & PERF_SAMPLE_CALLCHAIN)) {
fprintf(stderr, "selected --sort parent, but no callchain data\n");
exit(-1);
}
if (load_kernel() < 0) {
perror("failed to load kernel symbols");
return EXIT_FAILURE;
}
if (!full_paths) {
if (getcwd(__cwd, sizeof(__cwd)) == NULL) {
perror("failed to get the current directory");
return EXIT_FAILURE;
}
cwdlen = strlen(cwd);
} else {
cwd = NULL;
cwdlen = 0;
}
shift = page_size * (head / page_size);
offset += shift;
head -= shift;
remap:
buf = (char *)mmap(NULL, page_size * mmap_window, PROT_READ,
MAP_SHARED, input, offset);
if (buf == MAP_FAILED) {
perror("failed to mmap file");
exit(-1);
}
more:
event = (event_t *)(buf + head);
size = event->header.size;
if (!size)
size = 8;
if (head + event->header.size >= page_size * mmap_window) {
int ret;
shift = page_size * (head / page_size);
ret = munmap(buf, page_size * mmap_window);
assert(ret == 0);
offset += shift;
head -= shift;
goto remap;
}
size = event->header.size;
dprintf("\n%p [%p]: event: %d\n",
(void *)(offset + head),
(void *)(long)event->header.size,
event->header.type);
if (!size || process_event(event, offset, head) < 0) {
dprintf("%p [%p]: skipping unknown header type: %d\n",
(void *)(offset + head),
(void *)(long)(event->header.size),
event->header.type);
/*
* assume we lost track of the stream, check alignment, and
* increment a single u64 in the hope to catch on again 'soon'.
*/
if (unlikely(head & 7))
head &= ~7ULL;
size = 8;

Arnaldo Carvalho de Melo
committed
if (offset + head >= header->data_offset + header->data_size)
if (offset + head < (unsigned long)stat.st_size)

Arnaldo Carvalho de Melo
committed
goto more;

Arnaldo Carvalho de Melo
committed
rc = EXIT_SUCCESS;
close(input);
dprintf(" IP events: %10ld\n", total);
dprintf(" mmap events: %10ld\n", total_mmap);
dprintf(" comm events: %10ld\n", total_comm);
dprintf(" fork events: %10ld\n", total_fork);
dprintf(" lost events: %10ld\n", total_lost);
dprintf(" unknown events: %10ld\n", total_unknown);
if (verbose >= 3)
threads__fprintf(stdout);
dsos__fprintf(stdout);

Frederic Weisbecker
committed
output__resort(total);
output__fprintf(stdout, total);

Arnaldo Carvalho de Melo
committed
return rc;
}
static int
parse_callchain_opt(const struct option *opt __used, const char *arg,
int unset __used)
{

Frederic Weisbecker
committed
char *tok;
char *endptr;
callchain = 1;
if (!arg)
return 0;

Frederic Weisbecker
committed
tok = strtok((char *)arg, ",");
if (!tok)
return -1;
/* get the output mode */
if (!strncmp(tok, "graph", strlen(arg)))
callchain_mode = GRAPH;

Frederic Weisbecker
committed
else if (!strncmp(tok, "flat", strlen(arg)))
callchain_mode = FLAT;
else
return -1;

Frederic Weisbecker
committed
/* get the min percentage */
tok = strtok(NULL, ",");
if (!tok)
return 0;
callchain_min_percent = strtod(tok, &endptr);
if (tok == endptr)
return -1;
return 0;
}
static const char * const report_usage[] = {
"perf report [<options>] <command>",
NULL
};
static const struct option options[] = {
OPT_STRING('i', "input", &input_name, "file",
"input file name"),
OPT_BOOLEAN('v', "verbose", &verbose,
"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_BOOLEAN('m', "modules", &modules,
"load module symbols - WARNING: use only with -k and LIVE kernel"),
OPT_STRING('s', "sort", &sort_order, "key[,key2...]",

Ingo Molnar
committed
"sort by key(s): pid, comm, dso, symbol, parent"),
OPT_BOOLEAN('P', "full-paths", &full_paths,
"Don't shorten the pathnames taking into account the cwd"),

Ingo Molnar
committed
OPT_STRING('p', "parent", &parent_pattern, "regex",
"regex filter to identify parent, see: '--sort parent'"),
OPT_BOOLEAN('x', "exclude-other", &exclude_other,
"Only display entries with parent-match"),

Frederic Weisbecker
committed
OPT_CALLBACK_DEFAULT('c', "callchain", NULL, "output_type,min_percent",
"Display callchains using output_type and min percent threshold. "
"Default: flat,0", &parse_callchain_opt, "flat,100"),
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"),
static void setup_sorting(void)
{
char *tmp, *tok, *str = strdup(sort_order);
for (tok = strtok_r(str, ", ", &tmp);
tok; tok = strtok_r(NULL, ", ", &tmp)) {
if (sort_dimension__add(tok) < 0) {
error("Unknown --sort key: `%s'", tok);
usage_with_options(report_usage, options);
}
}
free(str);
}
static void setup_list(struct strlist **list, const char *list_str,
const char *list_name)
{
if (list_str) {
*list = strlist__new(true, list_str);
if (!*list) {
fprintf(stderr, "problems parsing %s list\n",
list_name);
exit(129);
}
}
}
int cmd_report(int argc, const char **argv, const char *prefix __used)

Arnaldo Carvalho de Melo
committed
symbol__init();
page_size = getpagesize();
argc = parse_options(argc, argv, options, report_usage, 0);
setup_sorting();
if (parent_pattern != default_parent_pattern)
sort_dimension__add("parent");
else
exclude_other = 0;
/*
* Any (unrecognized) arguments left?
*/
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");