diff options
Diffstat (limited to 'tools/perf/builtin-report.c')
-rw-r--r-- | tools/perf/builtin-report.c | 174 |
1 files changed, 142 insertions, 32 deletions
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 4e5cc266311e..740da43f313e 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c | |||
@@ -33,8 +33,10 @@ static char *vmlinux = NULL; | |||
33 | 33 | ||
34 | static char default_sort_order[] = "comm,dso"; | 34 | static char default_sort_order[] = "comm,dso"; |
35 | static char *sort_order = default_sort_order; | 35 | static char *sort_order = default_sort_order; |
36 | static char *dso_list_str, *comm_list_str, *sym_list_str; | 36 | static char *dso_list_str, *comm_list_str, *sym_list_str, |
37 | *col_width_list_str; | ||
37 | static struct strlist *dso_list, *comm_list, *sym_list; | 38 | static struct strlist *dso_list, *comm_list, *sym_list; |
39 | static char *field_sep; | ||
38 | 40 | ||
39 | static int input; | 41 | static int input; |
40 | static int show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV; | 42 | static int show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV; |
@@ -129,6 +131,33 @@ typedef union event_union { | |||
129 | struct read_event read; | 131 | struct read_event read; |
130 | } event_t; | 132 | } event_t; |
131 | 133 | ||
134 | static int repsep_fprintf(FILE *fp, const char *fmt, ...) | ||
135 | { | ||
136 | int n; | ||
137 | va_list ap; | ||
138 | |||
139 | va_start(ap, fmt); | ||
140 | if (!field_sep) | ||
141 | n = vfprintf(fp, fmt, ap); | ||
142 | else { | ||
143 | char *bf = NULL; | ||
144 | n = vasprintf(&bf, fmt, ap); | ||
145 | if (n > 0) { | ||
146 | char *sep = bf; | ||
147 | while (1) { | ||
148 | sep = strchr(sep, *field_sep); | ||
149 | if (sep == NULL) | ||
150 | break; | ||
151 | *sep = '.'; | ||
152 | } | ||
153 | } | ||
154 | fputs(bf, fp); | ||
155 | free(bf); | ||
156 | } | ||
157 | va_end(ap); | ||
158 | return n; | ||
159 | } | ||
160 | |||
132 | static LIST_HEAD(dsos); | 161 | static LIST_HEAD(dsos); |
133 | static struct dso *kernel_dso; | 162 | static struct dso *kernel_dso; |
134 | static struct dso *vdso; | 163 | static struct dso *vdso; |
@@ -360,12 +389,28 @@ static struct thread *thread__new(pid_t pid) | |||
360 | return self; | 389 | return self; |
361 | } | 390 | } |
362 | 391 | ||
392 | static unsigned int dsos__col_width, | ||
393 | comms__col_width, | ||
394 | threads__col_width; | ||
395 | |||
363 | static int thread__set_comm(struct thread *self, const char *comm) | 396 | static int thread__set_comm(struct thread *self, const char *comm) |
364 | { | 397 | { |
365 | if (self->comm) | 398 | if (self->comm) |
366 | free(self->comm); | 399 | free(self->comm); |
367 | self->comm = strdup(comm); | 400 | self->comm = strdup(comm); |
368 | return self->comm ? 0 : -ENOMEM; | 401 | if (!self->comm) |
402 | return -ENOMEM; | ||
403 | |||
404 | if (!col_width_list_str && !field_sep && | ||
405 | (!comm_list || strlist__has_entry(comm_list, comm))) { | ||
406 | unsigned int slen = strlen(comm); | ||
407 | if (slen > comms__col_width) { | ||
408 | comms__col_width = slen; | ||
409 | threads__col_width = slen + 6; | ||
410 | } | ||
411 | } | ||
412 | |||
413 | return 0; | ||
369 | } | 414 | } |
370 | 415 | ||
371 | static size_t thread__fprintf(struct thread *self, FILE *fp) | 416 | static size_t thread__fprintf(struct thread *self, FILE *fp) |
@@ -536,7 +581,8 @@ struct sort_entry { | |||
536 | 581 | ||
537 | int64_t (*cmp)(struct hist_entry *, struct hist_entry *); | 582 | int64_t (*cmp)(struct hist_entry *, struct hist_entry *); |
538 | int64_t (*collapse)(struct hist_entry *, struct hist_entry *); | 583 | int64_t (*collapse)(struct hist_entry *, struct hist_entry *); |
539 | size_t (*print)(FILE *fp, struct hist_entry *); | 584 | size_t (*print)(FILE *fp, struct hist_entry *, unsigned int width); |
585 | unsigned int *width; | ||
540 | }; | 586 | }; |
541 | 587 | ||
542 | static int64_t cmp_null(void *l, void *r) | 588 | 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 | } | 604 | } |
559 | 605 | ||
560 | static size_t | 606 | static size_t |
561 | sort__thread_print(FILE *fp, struct hist_entry *self) | 607 | sort__thread_print(FILE *fp, struct hist_entry *self, unsigned int width) |
562 | { | 608 | { |
563 | return fprintf(fp, "%16s:%5d", self->thread->comm ?: "", self->thread->pid); | 609 | return repsep_fprintf(fp, "%*s:%5d", width - 6, |
610 | self->thread->comm ?: "", self->thread->pid); | ||
564 | } | 611 | } |
565 | 612 | ||
566 | static struct sort_entry sort_thread = { | 613 | static struct sort_entry sort_thread = { |
567 | .header = " Command: Pid", | 614 | .header = "Command: Pid", |
568 | .cmp = sort__thread_cmp, | 615 | .cmp = sort__thread_cmp, |
569 | .print = sort__thread_print, | 616 | .print = sort__thread_print, |
617 | .width = &threads__col_width, | ||
570 | }; | 618 | }; |
571 | 619 | ||
572 | /* --sort comm */ | 620 | /* --sort comm */ |
@@ -590,16 +638,17 @@ sort__comm_collapse(struct hist_entry *left, struct hist_entry *right) | |||
590 | } | 638 | } |
591 | 639 | ||
592 | static size_t | 640 | static size_t |
593 | sort__comm_print(FILE *fp, struct hist_entry *self) | 641 | sort__comm_print(FILE *fp, struct hist_entry *self, unsigned int width) |
594 | { | 642 | { |
595 | return fprintf(fp, "%16s", self->thread->comm); | 643 | return repsep_fprintf(fp, "%*s", width, self->thread->comm); |
596 | } | 644 | } |
597 | 645 | ||
598 | static struct sort_entry sort_comm = { | 646 | static struct sort_entry sort_comm = { |
599 | .header = " Command", | 647 | .header = "Command", |
600 | .cmp = sort__comm_cmp, | 648 | .cmp = sort__comm_cmp, |
601 | .collapse = sort__comm_collapse, | 649 | .collapse = sort__comm_collapse, |
602 | .print = sort__comm_print, | 650 | .print = sort__comm_print, |
651 | .width = &comms__col_width, | ||
603 | }; | 652 | }; |
604 | 653 | ||
605 | /* --sort dso */ | 654 | /* --sort dso */ |
@@ -617,18 +666,19 @@ sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) | |||
617 | } | 666 | } |
618 | 667 | ||
619 | static size_t | 668 | static size_t |
620 | sort__dso_print(FILE *fp, struct hist_entry *self) | 669 | sort__dso_print(FILE *fp, struct hist_entry *self, unsigned int width) |
621 | { | 670 | { |
622 | if (self->dso) | 671 | if (self->dso) |
623 | return fprintf(fp, "%-25s", self->dso->name); | 672 | return repsep_fprintf(fp, "%-*s", width, self->dso->name); |
624 | 673 | ||
625 | return fprintf(fp, "%016llx ", (u64)self->ip); | 674 | return repsep_fprintf(fp, "%*llx", width, (u64)self->ip); |
626 | } | 675 | } |
627 | 676 | ||
628 | static struct sort_entry sort_dso = { | 677 | static struct sort_entry sort_dso = { |
629 | .header = "Shared Object ", | 678 | .header = "Shared Object", |
630 | .cmp = sort__dso_cmp, | 679 | .cmp = sort__dso_cmp, |
631 | .print = sort__dso_print, | 680 | .print = sort__dso_print, |
681 | .width = &dsos__col_width, | ||
632 | }; | 682 | }; |
633 | 683 | ||
634 | /* --sort symbol */ | 684 | /* --sort symbol */ |
@@ -648,22 +698,23 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) | |||
648 | } | 698 | } |
649 | 699 | ||
650 | static size_t | 700 | static size_t |
651 | sort__sym_print(FILE *fp, struct hist_entry *self) | 701 | sort__sym_print(FILE *fp, struct hist_entry *self, unsigned int width __used) |
652 | { | 702 | { |
653 | size_t ret = 0; | 703 | size_t ret = 0; |
654 | 704 | ||
655 | if (verbose) | 705 | if (verbose) |
656 | ret += fprintf(fp, "%#018llx ", (u64)self->ip); | 706 | ret += repsep_fprintf(fp, "%#018llx ", (u64)self->ip); |
657 | 707 | ||
658 | if (self->sym) { | 708 | if (self->sym) { |
659 | ret += fprintf(fp, "[%c] %s", | 709 | ret += repsep_fprintf(fp, "[%c] %s", |
660 | self->dso == kernel_dso ? 'k' : | 710 | self->dso == kernel_dso ? 'k' : |
661 | self->dso == hypervisor_dso ? 'h' : '.', self->sym->name); | 711 | self->dso == hypervisor_dso ? 'h' : '.', self->sym->name); |
662 | 712 | ||
663 | if (self->sym->module) | 713 | if (self->sym->module) |
664 | ret += fprintf(fp, "\t[%s]", self->sym->module->name); | 714 | ret += repsep_fprintf(fp, "\t[%s]", |
715 | self->sym->module->name); | ||
665 | } else { | 716 | } else { |
666 | ret += fprintf(fp, "%#016llx", (u64)self->ip); | 717 | ret += repsep_fprintf(fp, "%#016llx", (u64)self->ip); |
667 | } | 718 | } |
668 | 719 | ||
669 | return ret; | 720 | return ret; |
@@ -690,19 +741,19 @@ sort__parent_cmp(struct hist_entry *left, struct hist_entry *right) | |||
690 | } | 741 | } |
691 | 742 | ||
692 | static size_t | 743 | static size_t |
693 | sort__parent_print(FILE *fp, struct hist_entry *self) | 744 | sort__parent_print(FILE *fp, struct hist_entry *self, unsigned int width) |
694 | { | 745 | { |
695 | size_t ret = 0; | 746 | return repsep_fprintf(fp, "%-*s", width, |
696 | 747 | self->parent ? self->parent->name : "[other]"); | |
697 | ret += fprintf(fp, "%-20s", self->parent ? self->parent->name : "[other]"); | ||
698 | |||
699 | return ret; | ||
700 | } | 748 | } |
701 | 749 | ||
750 | static unsigned int parent_symbol__col_width; | ||
751 | |||
702 | static struct sort_entry sort_parent = { | 752 | static struct sort_entry sort_parent = { |
703 | .header = "Parent symbol ", | 753 | .header = "Parent symbol", |
704 | .cmp = sort__parent_cmp, | 754 | .cmp = sort__parent_cmp, |
705 | .print = sort__parent_print, | 755 | .print = sort__parent_print, |
756 | .width = &parent_symbol__col_width, | ||
706 | }; | 757 | }; |
707 | 758 | ||
708 | static int sort__need_collapse = 0; | 759 | static int sort__need_collapse = 0; |
@@ -967,17 +1018,18 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples) | |||
967 | return 0; | 1018 | return 0; |
968 | 1019 | ||
969 | if (total_samples) | 1020 | if (total_samples) |
970 | ret = percent_color_fprintf(fp, " %6.2f%%", | 1021 | ret = percent_color_fprintf(fp, |
971 | (self->count * 100.0) / total_samples); | 1022 | field_sep ? "%.2f" : " %6.2f%%", |
1023 | (self->count * 100.0) / total_samples); | ||
972 | else | 1024 | else |
973 | ret = fprintf(fp, "%12Ld ", self->count); | 1025 | ret = fprintf(fp, field_sep ? "%lld" : "%12lld ", self->count); |
974 | 1026 | ||
975 | list_for_each_entry(se, &hist_entry__sort_list, list) { | 1027 | list_for_each_entry(se, &hist_entry__sort_list, list) { |
976 | if (exclude_other && (se == &sort_parent)) | 1028 | if (exclude_other && (se == &sort_parent)) |
977 | continue; | 1029 | continue; |
978 | 1030 | ||
979 | fprintf(fp, " "); | 1031 | fprintf(fp, "%s", field_sep ?: " "); |
980 | ret += se->print(fp, self); | 1032 | ret += se->print(fp, self, se->width ? *se->width : 0); |
981 | } | 1033 | } |
982 | 1034 | ||
983 | ret += fprintf(fp, "\n"); | 1035 | ret += fprintf(fp, "\n"); |
@@ -992,6 +1044,18 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples) | |||
992 | * | 1044 | * |
993 | */ | 1045 | */ |
994 | 1046 | ||
1047 | static void dso__calc_col_width(struct dso *self) | ||
1048 | { | ||
1049 | if (!col_width_list_str && !field_sep && | ||
1050 | (!dso_list || strlist__has_entry(dso_list, self->name))) { | ||
1051 | unsigned int slen = strlen(self->name); | ||
1052 | if (slen > dsos__col_width) | ||
1053 | dsos__col_width = slen; | ||
1054 | } | ||
1055 | |||
1056 | self->slen_calculated = 1; | ||
1057 | } | ||
1058 | |||
995 | static struct symbol * | 1059 | static struct symbol * |
996 | resolve_symbol(struct thread *thread, struct map **mapp, | 1060 | resolve_symbol(struct thread *thread, struct map **mapp, |
997 | struct dso **dsop, u64 *ipp) | 1061 | struct dso **dsop, u64 *ipp) |
@@ -1011,6 +1075,14 @@ resolve_symbol(struct thread *thread, struct map **mapp, | |||
1011 | 1075 | ||
1012 | map = thread__find_map(thread, ip); | 1076 | map = thread__find_map(thread, ip); |
1013 | if (map != NULL) { | 1077 | if (map != NULL) { |
1078 | /* | ||
1079 | * We have to do this here as we may have a dso | ||
1080 | * with no symbol hit that has a name longer than | ||
1081 | * the ones with symbols sampled. | ||
1082 | */ | ||
1083 | if (!map->dso->slen_calculated) | ||
1084 | dso__calc_col_width(map->dso); | ||
1085 | |||
1014 | if (mapp) | 1086 | if (mapp) |
1015 | *mapp = map; | 1087 | *mapp = map; |
1016 | got_map: | 1088 | got_map: |
@@ -1282,6 +1354,8 @@ static size_t output__fprintf(FILE *fp, u64 total_samples) | |||
1282 | struct sort_entry *se; | 1354 | struct sort_entry *se; |
1283 | struct rb_node *nd; | 1355 | struct rb_node *nd; |
1284 | size_t ret = 0; | 1356 | size_t ret = 0; |
1357 | unsigned int width; | ||
1358 | char *col_width = col_width_list_str; | ||
1285 | 1359 | ||
1286 | fprintf(fp, "\n"); | 1360 | fprintf(fp, "\n"); |
1287 | fprintf(fp, "#\n"); | 1361 | fprintf(fp, "#\n"); |
@@ -1292,10 +1366,29 @@ static size_t output__fprintf(FILE *fp, u64 total_samples) | |||
1292 | list_for_each_entry(se, &hist_entry__sort_list, list) { | 1366 | list_for_each_entry(se, &hist_entry__sort_list, list) { |
1293 | if (exclude_other && (se == &sort_parent)) | 1367 | if (exclude_other && (se == &sort_parent)) |
1294 | continue; | 1368 | continue; |
1295 | fprintf(fp, " %s", se->header); | 1369 | if (field_sep) { |
1370 | fprintf(fp, "%c%s", *field_sep, se->header); | ||
1371 | continue; | ||
1372 | } | ||
1373 | width = strlen(se->header); | ||
1374 | if (se->width) { | ||
1375 | if (col_width_list_str) { | ||
1376 | if (col_width) { | ||
1377 | *se->width = atoi(col_width); | ||
1378 | col_width = strchr(col_width, ','); | ||
1379 | if (col_width) | ||
1380 | ++col_width; | ||
1381 | } | ||
1382 | } | ||
1383 | width = *se->width = max(*se->width, width); | ||
1384 | } | ||
1385 | fprintf(fp, " %*s", width, se->header); | ||
1296 | } | 1386 | } |
1297 | fprintf(fp, "\n"); | 1387 | fprintf(fp, "\n"); |
1298 | 1388 | ||
1389 | if (field_sep) | ||
1390 | goto print_entries; | ||
1391 | |||
1299 | fprintf(fp, "# ........"); | 1392 | fprintf(fp, "# ........"); |
1300 | list_for_each_entry(se, &hist_entry__sort_list, list) { | 1393 | list_for_each_entry(se, &hist_entry__sort_list, list) { |
1301 | unsigned int i; | 1394 | unsigned int i; |
@@ -1304,13 +1397,18 @@ static size_t output__fprintf(FILE *fp, u64 total_samples) | |||
1304 | continue; | 1397 | continue; |
1305 | 1398 | ||
1306 | fprintf(fp, " "); | 1399 | fprintf(fp, " "); |
1307 | for (i = 0; i < strlen(se->header); i++) | 1400 | if (se->width) |
1401 | width = *se->width; | ||
1402 | else | ||
1403 | width = strlen(se->header); | ||
1404 | for (i = 0; i < width; i++) | ||
1308 | fprintf(fp, "."); | 1405 | fprintf(fp, "."); |
1309 | } | 1406 | } |
1310 | fprintf(fp, "\n"); | 1407 | fprintf(fp, "\n"); |
1311 | 1408 | ||
1312 | fprintf(fp, "#\n"); | 1409 | fprintf(fp, "#\n"); |
1313 | 1410 | ||
1411 | print_entries: | ||
1314 | for (nd = rb_first(&output_hists); nd; nd = rb_next(nd)) { | 1412 | for (nd = rb_first(&output_hists); nd; nd = rb_next(nd)) { |
1315 | pos = rb_entry(nd, struct hist_entry, rb_node); | 1413 | pos = rb_entry(nd, struct hist_entry, rb_node); |
1316 | ret += hist_entry__fprintf(fp, pos, total_samples); | 1414 | ret += hist_entry__fprintf(fp, pos, total_samples); |
@@ -1900,6 +1998,12 @@ static const struct option options[] = { | |||
1900 | "only consider symbols in these comms"), | 1998 | "only consider symbols in these comms"), |
1901 | OPT_STRING('S', "symbols", &sym_list_str, "symbol[,symbol...]", | 1999 | OPT_STRING('S', "symbols", &sym_list_str, "symbol[,symbol...]", |
1902 | "only consider these symbols"), | 2000 | "only consider these symbols"), |
2001 | OPT_STRING('w', "column-widths", &col_width_list_str, | ||
2002 | "width[,width...]", | ||
2003 | "don't try to adjust column width, use these fixed values"), | ||
2004 | OPT_STRING('t', "field-separator", &field_sep, "separator", | ||
2005 | "separator for columns, no spaces will be added between " | ||
2006 | "columns '.' is reserved."), | ||
1903 | OPT_END() | 2007 | OPT_END() |
1904 | }; | 2008 | }; |
1905 | 2009 | ||
@@ -1956,6 +2060,12 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) | |||
1956 | setup_list(&comm_list, comm_list_str, "comm"); | 2060 | setup_list(&comm_list, comm_list_str, "comm"); |
1957 | setup_list(&sym_list, sym_list_str, "symbol"); | 2061 | setup_list(&sym_list, sym_list_str, "symbol"); |
1958 | 2062 | ||
2063 | if (field_sep && *field_sep == '.') { | ||
2064 | fputs("'.' is the only non valid --field-separator argument\n", | ||
2065 | stderr); | ||
2066 | exit(129); | ||
2067 | } | ||
2068 | |||
1959 | setup_pager(); | 2069 | setup_pager(); |
1960 | 2070 | ||
1961 | return __cmd_report(); | 2071 | return __cmd_report(); |