Newer
Older
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
}
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
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;
static void output__insert_entry(struct hist_entry *he)
struct rb_node **p = &output_hists.rb_node;
struct rb_node *parent = NULL;
struct hist_entry *iter;
if (callchain)
sort_chain_to_rbtree(&he->sorted_chain, &he->callchain);
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);
static void output__resort(void)
struct hist_entry *n;
tree = &collapse_hists;
next = rb_first(tree);
while (next) {
n = rb_entry(next, struct hist_entry, rb_node);
next = rb_next(&n->rb_node);
output__insert_entry(n);
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) {
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) {
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");
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
}
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)

Arnaldo Carvalho de Melo
committed
if (offset + head < stat.st_size)
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);
output__resort();
output__fprintf(stdout, total);

Arnaldo Carvalho de Melo
committed
return rc;
}
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_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"),
OPT_BOOLEAN('c', "callchain", &callchain, "Display callchains"),
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)
{

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");