diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 9babe915b6c496e49f921d5ae683e78ef7a3bed2..a1b5185402d5d2721a9c5aefe7fd9c26630d1a32 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -107,6 +107,16 @@ OPTIONS And default sort keys are changed to comm, dso_from, symbol_from, dso_to and symbol_to, see '--branch-stack'. +-F:: +--fields=:: + Specify output field - multiple keys can be specified in CSV format. + Following fields are available: + overhead, overhead_sys, overhead_us, sample and period. + Also it can contain any sort key(s). + + By default, every sort keys not specified in -F will be appended + automatically. + -p:: --parent=:: A regex filter to identify parent. The parent is a caller of this diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index c4dab7acbdbbe6a35599c565e40e98d15c3db7ff..bc0eec1ce4beaba37509f731c79f8e1855796c08 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -701,6 +701,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) OPT_STRING('s', "sort", &sort_order, "key[,key2...]", "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..." " Please refer the man page for the complete list."), + OPT_STRING('F', "fields", &field_order, "key[,keys...]", + "output field(s): overhead, period, sample plus all of sort keys"), OPT_BOOLEAN(0, "showcpuutilization", &symbol_conf.show_cpu_utilization, "Show sample percentage for different cpu modes"), OPT_STRING('p', "parent", &parent_pattern, "regex", @@ -814,17 +816,14 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) } if (setup_sorting() < 0) { - parse_options_usage(report_usage, options, "s", 1); + if (sort_order) + parse_options_usage(report_usage, options, "s", 1); + if (field_order) + parse_options_usage(sort_order ? NULL : report_usage, + options, "F", 1); goto error; } - if (parent_pattern != default_parent_pattern) { - if (sort_dimension__add("parent") < 0) - goto error; - } - - perf_hpp__init(); - /* Force tty output for header output. */ if (report.header || report.header_only) use_browser = 0; diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c index 24116a48298f814cdf115cffdc72b1155cff8dcb..b114c6668865e95a98763f757faab364512f6e44 100644 --- a/tools/perf/ui/hist.c +++ b/tools/perf/ui/hist.c @@ -355,6 +355,12 @@ void perf_hpp__init(void) INIT_LIST_HEAD(&fmt->sort_list); } + /* + * If user specified field order, no need to setup default fields. + */ + if (field_order) + return; + perf_hpp__column_enable(PERF_HPP__OVERHEAD); if (symbol_conf.show_cpu_utilization) { @@ -377,8 +383,6 @@ void perf_hpp__init(void) list = &perf_hpp__format[PERF_HPP__OVERHEAD].sort_list; if (list_empty(list)) list_add(list, &perf_hpp__sort_list); - - perf_hpp__setup_output_field(); } void perf_hpp__column_register(struct perf_hpp_fmt *format) @@ -403,8 +407,55 @@ void perf_hpp__setup_output_field(void) /* append sort keys to output field */ perf_hpp__for_each_sort_list(fmt) { - if (list_empty(&fmt->list)) - perf_hpp__column_register(fmt); + if (!list_empty(&fmt->list)) + continue; + + /* + * sort entry fields are dynamically created, + * so they can share a same sort key even though + * the list is empty. + */ + if (perf_hpp__is_sort_entry(fmt)) { + struct perf_hpp_fmt *pos; + + perf_hpp__for_each_format(pos) { + if (perf_hpp__same_sort_entry(pos, fmt)) + goto next; + } + } + + perf_hpp__column_register(fmt); +next: + continue; + } +} + +void perf_hpp__append_sort_keys(void) +{ + struct perf_hpp_fmt *fmt; + + /* append output fields to sort keys */ + perf_hpp__for_each_format(fmt) { + if (!list_empty(&fmt->sort_list)) + continue; + + /* + * sort entry fields are dynamically created, + * so they can share a same sort key even though + * the list is empty. + */ + if (perf_hpp__is_sort_entry(fmt)) { + struct perf_hpp_fmt *pos; + + perf_hpp__for_each_sort_list(pos) { + if (perf_hpp__same_sort_entry(pos, fmt)) + goto next; + } + } + + perf_hpp__register_sort_field(fmt); +next: + continue; } } diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 76bb72e4302b03f41d11aef084c7cc1e4c99da84..f3713b79742d08d46d70831a8a8ffc937e044ab1 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -197,6 +197,10 @@ void perf_hpp__column_register(struct perf_hpp_fmt *format); void perf_hpp__column_enable(unsigned col); void perf_hpp__register_sort_field(struct perf_hpp_fmt *format); void perf_hpp__setup_output_field(void); +void perf_hpp__append_sort_keys(void); + +bool perf_hpp__is_sort_entry(struct perf_hpp_fmt *format); +bool perf_hpp__same_sort_entry(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b); typedef u64 (*hpp_field_fn)(struct hist_entry *he); typedef int (*hpp_callback_fn)(struct perf_hpp *hpp, bool front); diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index d64c1e58f1b1a54705ef28270dd3b5ff5b0c2936..b748b02fcb78ae32815b595dc1fa7c414e4f8441 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -13,6 +13,7 @@ const char default_mem_sort_order[] = "local_weight,mem,sym,dso,symbol_daddr,dso const char default_top_sort_order[] = "dso,symbol"; const char default_diff_sort_order[] = "dso,symbol"; const char *sort_order; +const char *field_order; regex_t ignore_callees_regex; int have_ignore_callees = 0; int sort__need_collapse = 0; @@ -1057,6 +1058,20 @@ struct hpp_sort_entry { struct sort_entry *se; }; +bool perf_hpp__same_sort_entry(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) +{ + struct hpp_sort_entry *hse_a; + struct hpp_sort_entry *hse_b; + + if (!perf_hpp__is_sort_entry(a) || !perf_hpp__is_sort_entry(b)) + return false; + + hse_a = container_of(a, struct hpp_sort_entry, hpp); + hse_b = container_of(b, struct hpp_sort_entry, hpp); + + return hse_a->se == hse_b->se; +} + static int __sort__hpp_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct perf_evsel *evsel) { @@ -1092,14 +1107,15 @@ static int __sort__hpp_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, return hse->se->se_snprintf(he, hpp->buf, hpp->size, len); } -static int __sort_dimension__add_hpp(struct sort_dimension *sd) +static struct hpp_sort_entry * +__sort_dimension__alloc_hpp(struct sort_dimension *sd) { struct hpp_sort_entry *hse; hse = malloc(sizeof(*hse)); if (hse == NULL) { pr_err("Memory allocation failed\n"); - return -1; + return NULL; } hse->se = sd->entry; @@ -1115,16 +1131,42 @@ static int __sort_dimension__add_hpp(struct sort_dimension *sd) INIT_LIST_HEAD(&hse->hpp.list); INIT_LIST_HEAD(&hse->hpp.sort_list); + return hse; +} + +bool perf_hpp__is_sort_entry(struct perf_hpp_fmt *format) +{ + return format->header == __sort__hpp_header; +} + +static int __sort_dimension__add_hpp_sort(struct sort_dimension *sd) +{ + struct hpp_sort_entry *hse = __sort_dimension__alloc_hpp(sd); + + if (hse == NULL) + return -1; + perf_hpp__register_sort_field(&hse->hpp); return 0; } +static int __sort_dimension__add_hpp_output(struct sort_dimension *sd) +{ + struct hpp_sort_entry *hse = __sort_dimension__alloc_hpp(sd); + + if (hse == NULL) + return -1; + + perf_hpp__column_register(&hse->hpp); + return 0; +} + static int __sort_dimension__add(struct sort_dimension *sd, enum sort_type idx) { if (sd->taken) return 0; - if (__sort_dimension__add_hpp(sd) < 0) + if (__sort_dimension__add_hpp_sort(sd) < 0) return -1; if (sd->entry->se_collapse) @@ -1149,6 +1191,28 @@ static int __hpp_dimension__add(struct hpp_dimension *hd) return 0; } +static int __sort_dimension__add_output(struct sort_dimension *sd) +{ + if (sd->taken) + return 0; + + if (__sort_dimension__add_hpp_output(sd) < 0) + return -1; + + sd->taken = 1; + return 0; +} + +static int __hpp_dimension__add_output(struct hpp_dimension *hd) +{ + if (!hd->taken) { + hd->taken = 1; + + perf_hpp__column_register(hd->fmt); + } + return 0; +} + int sort_dimension__add(const char *tok) { unsigned int i; @@ -1237,14 +1301,23 @@ static const char *get_default_sort_order(void) return default_sort_orders[sort__mode]; } -int setup_sorting(void) +static int __setup_sorting(void) { char *tmp, *tok, *str; const char *sort_keys = sort_order; int ret = 0; - if (sort_keys == NULL) + if (sort_keys == NULL) { + if (field_order) { + /* + * If user specified field order but no sort order, + * we'll honor it and not add default sort orders. + */ + return 0; + } + sort_keys = get_default_sort_order(); + } str = strdup(sort_keys); if (str == NULL) { @@ -1331,3 +1404,129 @@ void sort__setup_elide(FILE *output) list_for_each_entry(se, &hist_entry__sort_list, list) se->elide = false; } + +static int output_field_add(char *tok) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(common_sort_dimensions); i++) { + struct sort_dimension *sd = &common_sort_dimensions[i]; + + if (strncasecmp(tok, sd->name, strlen(tok))) + continue; + + return __sort_dimension__add_output(sd); + } + + for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) { + struct hpp_dimension *hd = &hpp_sort_dimensions[i]; + + if (strncasecmp(tok, hd->name, strlen(tok))) + continue; + + return __hpp_dimension__add_output(hd); + } + + for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++) { + struct sort_dimension *sd = &bstack_sort_dimensions[i]; + + if (strncasecmp(tok, sd->name, strlen(tok))) + continue; + + return __sort_dimension__add_output(sd); + } + + for (i = 0; i < ARRAY_SIZE(memory_sort_dimensions); i++) { + struct sort_dimension *sd = &memory_sort_dimensions[i]; + + if (strncasecmp(tok, sd->name, strlen(tok))) + continue; + + return __sort_dimension__add_output(sd); + } + + return -ESRCH; +} + +static void reset_dimensions(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(common_sort_dimensions); i++) + common_sort_dimensions[i].taken = 0; + + for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) + hpp_sort_dimensions[i].taken = 0; + + for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++) + bstack_sort_dimensions[i].taken = 0; + + for (i = 0; i < ARRAY_SIZE(memory_sort_dimensions); i++) + memory_sort_dimensions[i].taken = 0; +} + +static int __setup_output_field(void) +{ + char *tmp, *tok, *str; + int ret = 0; + + if (field_order == NULL) + return 0; + + reset_dimensions(); + + str = strdup(field_order); + if (str == NULL) { + error("Not enough memory to setup output fields"); + return -ENOMEM; + } + + for (tok = strtok_r(str, ", ", &tmp); + tok; tok = strtok_r(NULL, ", ", &tmp)) { + ret = output_field_add(tok); + if (ret == -EINVAL) { + error("Invalid --fields key: `%s'", tok); + break; + } else if (ret == -ESRCH) { + error("Unknown --fields key: `%s'", tok); + break; + } + } + + free(str); + return ret; +} + +int setup_sorting(void) +{ + int err; + + err = __setup_sorting(); + if (err < 0) + return err; + + if (parent_pattern != default_parent_pattern) { + err = sort_dimension__add("parent"); + if (err < 0) + return err; + } + + reset_dimensions(); + + /* + * perf diff doesn't use default hpp output fields. + */ + if (sort__mode != SORT_MODE__DIFF) + perf_hpp__init(); + + err = __setup_output_field(); + if (err < 0) + return err; + + /* copy sort keys to output fields */ + perf_hpp__setup_output_field(); + /* and then copy output fields to sort keys */ + perf_hpp__append_sort_keys(); + + return 0; +} diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 1a7295255dc96286e334f81bf2ec9a7ebded3d72..89e5057ca378bc7252412c497aafbf2b62053be4 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -25,6 +25,7 @@ extern regex_t parent_regex; extern const char *sort_order; +extern const char *field_order; extern const char default_parent_pattern[]; extern const char *parent_pattern; extern const char default_sort_order[]; @@ -191,6 +192,7 @@ extern struct sort_entry sort_thread; extern struct list_head hist_entry__sort_list; int setup_sorting(void); +int setup_output_field(void); extern int sort_dimension__add(const char *); void sort__setup_elide(FILE *fp);