Commit a7d945bc authored by Namhyung Kim's avatar Namhyung Kim Committed by Jiri Olsa

perf report: Add -F option to specify output fields

The -F/--fields option is to allow user setup output field in any
order.  It can receive any sort keys and following (hpp) fields:

  overhead, overhead_sys, overhead_us, sample and period

If guest profiling is enabled, overhead_guest_{sys,us} will be
available too.

The output fields also affect sort order unless you give -s/--sort
option.  And any keys specified on -s option, will also be added to
the output field list automatically.

  $ perf report -F sym,sample,overhead
  ...
  #                     Symbol       Samples  Overhead
  # ..........................  ............  ........
  #
    [.] __cxa_atexit                       2     2.50%
    [.] __libc_csu_init                    4     5.00%
    [.] __new_exitfn                       3     3.75%
    [.] _dl_check_map_versions             1     1.25%
    [.] _dl_name_match_p                   4     5.00%
    [.] _dl_setup_hash                     1     1.25%
    [.] _dl_sysdep_start                   1     1.25%
    [.] _init                              5     6.25%
    [.] _setjmp                            6     7.50%
    [.] a                                  8    10.00%
    [.] b                                  8    10.00%
    [.] brk                                1     1.25%
    [.] c                                  8    10.00%

Note that, the example output above is captured after applying next
patch which fixes sort/comparing behavior.
Requested-by: default avatarIngo Molnar <mingo@kernel.org>
Signed-off-by: default avatarNamhyung Kim <namhyung@kernel.org>
Acked-by: default avatarIngo Molnar <mingo@kernel.org>
Link: http://lkml.kernel.org/r/1400480762-22852-12-git-send-email-namhyung@kernel.orgSigned-off-by: default avatarJiri Olsa <jolsa@kernel.org>
parent 22af969e
...@@ -107,6 +107,16 @@ OPTIONS ...@@ -107,6 +107,16 @@ OPTIONS
And default sort keys are changed to comm, dso_from, symbol_from, dso_to And default sort keys are changed to comm, dso_from, symbol_from, dso_to
and symbol_to, see '--branch-stack'. 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:: -p::
--parent=<regex>:: --parent=<regex>::
A regex filter to identify parent. The parent is a caller of this A regex filter to identify parent. The parent is a caller of this
......
...@@ -701,6 +701,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) ...@@ -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...]", OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
"sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..." "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..."
" Please refer the man page for the complete list."), " 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, OPT_BOOLEAN(0, "showcpuutilization", &symbol_conf.show_cpu_utilization,
"Show sample percentage for different cpu modes"), "Show sample percentage for different cpu modes"),
OPT_STRING('p', "parent", &parent_pattern, "regex", OPT_STRING('p', "parent", &parent_pattern, "regex",
...@@ -814,17 +816,14 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) ...@@ -814,17 +816,14 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
} }
if (setup_sorting() < 0) { if (setup_sorting() < 0) {
if (sort_order)
parse_options_usage(report_usage, options, "s", 1); parse_options_usage(report_usage, options, "s", 1);
if (field_order)
parse_options_usage(sort_order ? NULL : report_usage,
options, "F", 1);
goto error; 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. */ /* Force tty output for header output. */
if (report.header || report.header_only) if (report.header || report.header_only)
use_browser = 0; use_browser = 0;
......
...@@ -355,6 +355,12 @@ void perf_hpp__init(void) ...@@ -355,6 +355,12 @@ void perf_hpp__init(void)
INIT_LIST_HEAD(&fmt->sort_list); 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); perf_hpp__column_enable(PERF_HPP__OVERHEAD);
if (symbol_conf.show_cpu_utilization) { if (symbol_conf.show_cpu_utilization) {
...@@ -377,8 +383,6 @@ void perf_hpp__init(void) ...@@ -377,8 +383,6 @@ void perf_hpp__init(void)
list = &perf_hpp__format[PERF_HPP__OVERHEAD].sort_list; list = &perf_hpp__format[PERF_HPP__OVERHEAD].sort_list;
if (list_empty(list)) if (list_empty(list))
list_add(list, &perf_hpp__sort_list); list_add(list, &perf_hpp__sort_list);
perf_hpp__setup_output_field();
} }
void perf_hpp__column_register(struct perf_hpp_fmt *format) void perf_hpp__column_register(struct perf_hpp_fmt *format)
...@@ -403,8 +407,55 @@ void perf_hpp__setup_output_field(void) ...@@ -403,8 +407,55 @@ void perf_hpp__setup_output_field(void)
/* append sort keys to output field */ /* append sort keys to output field */
perf_hpp__for_each_sort_list(fmt) { perf_hpp__for_each_sort_list(fmt) {
if (list_empty(&fmt->list)) 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); 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;
} }
} }
......
...@@ -197,6 +197,10 @@ void perf_hpp__column_register(struct perf_hpp_fmt *format); ...@@ -197,6 +197,10 @@ void perf_hpp__column_register(struct perf_hpp_fmt *format);
void perf_hpp__column_enable(unsigned col); void perf_hpp__column_enable(unsigned col);
void perf_hpp__register_sort_field(struct perf_hpp_fmt *format); void perf_hpp__register_sort_field(struct perf_hpp_fmt *format);
void perf_hpp__setup_output_field(void); 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 u64 (*hpp_field_fn)(struct hist_entry *he);
typedef int (*hpp_callback_fn)(struct perf_hpp *hpp, bool front); typedef int (*hpp_callback_fn)(struct perf_hpp *hpp, bool front);
......
...@@ -13,6 +13,7 @@ const char default_mem_sort_order[] = "local_weight,mem,sym,dso,symbol_daddr,dso ...@@ -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_top_sort_order[] = "dso,symbol";
const char default_diff_sort_order[] = "dso,symbol"; const char default_diff_sort_order[] = "dso,symbol";
const char *sort_order; const char *sort_order;
const char *field_order;
regex_t ignore_callees_regex; regex_t ignore_callees_regex;
int have_ignore_callees = 0; int have_ignore_callees = 0;
int sort__need_collapse = 0; int sort__need_collapse = 0;
...@@ -1057,6 +1058,20 @@ struct hpp_sort_entry { ...@@ -1057,6 +1058,20 @@ struct hpp_sort_entry {
struct sort_entry *se; 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, static int __sort__hpp_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
struct perf_evsel *evsel) struct perf_evsel *evsel)
{ {
...@@ -1092,14 +1107,15 @@ static int __sort__hpp_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, ...@@ -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); 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; struct hpp_sort_entry *hse;
hse = malloc(sizeof(*hse)); hse = malloc(sizeof(*hse));
if (hse == NULL) { if (hse == NULL) {
pr_err("Memory allocation failed\n"); pr_err("Memory allocation failed\n");
return -1; return NULL;
} }
hse->se = sd->entry; hse->se = sd->entry;
...@@ -1115,16 +1131,42 @@ static int __sort_dimension__add_hpp(struct sort_dimension *sd) ...@@ -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.list);
INIT_LIST_HEAD(&hse->hpp.sort_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); perf_hpp__register_sort_field(&hse->hpp);
return 0; 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) static int __sort_dimension__add(struct sort_dimension *sd, enum sort_type idx)
{ {
if (sd->taken) if (sd->taken)
return 0; return 0;
if (__sort_dimension__add_hpp(sd) < 0) if (__sort_dimension__add_hpp_sort(sd) < 0)
return -1; return -1;
if (sd->entry->se_collapse) if (sd->entry->se_collapse)
...@@ -1149,6 +1191,28 @@ static int __hpp_dimension__add(struct hpp_dimension *hd) ...@@ -1149,6 +1191,28 @@ static int __hpp_dimension__add(struct hpp_dimension *hd)
return 0; 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) int sort_dimension__add(const char *tok)
{ {
unsigned int i; unsigned int i;
...@@ -1237,14 +1301,23 @@ static const char *get_default_sort_order(void) ...@@ -1237,14 +1301,23 @@ static const char *get_default_sort_order(void)
return default_sort_orders[sort__mode]; return default_sort_orders[sort__mode];
} }
int setup_sorting(void) static int __setup_sorting(void)
{ {
char *tmp, *tok, *str; char *tmp, *tok, *str;
const char *sort_keys = sort_order; const char *sort_keys = sort_order;
int ret = 0; 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(); sort_keys = get_default_sort_order();
}
str = strdup(sort_keys); str = strdup(sort_keys);
if (str == NULL) { if (str == NULL) {
...@@ -1331,3 +1404,129 @@ void sort__setup_elide(FILE *output) ...@@ -1331,3 +1404,129 @@ void sort__setup_elide(FILE *output)
list_for_each_entry(se, &hist_entry__sort_list, list) list_for_each_entry(se, &hist_entry__sort_list, list)
se->elide = false; 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;
}
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
extern regex_t parent_regex; extern regex_t parent_regex;
extern const char *sort_order; extern const char *sort_order;
extern const char *field_order;
extern const char default_parent_pattern[]; extern const char default_parent_pattern[];
extern const char *parent_pattern; extern const char *parent_pattern;
extern const char default_sort_order[]; extern const char default_sort_order[];
...@@ -191,6 +192,7 @@ extern struct sort_entry sort_thread; ...@@ -191,6 +192,7 @@ extern struct sort_entry sort_thread;
extern struct list_head hist_entry__sort_list; extern struct list_head hist_entry__sort_list;
int setup_sorting(void); int setup_sorting(void);
int setup_output_field(void);
extern int sort_dimension__add(const char *); extern int sort_dimension__add(const char *);
void sort__setup_elide(FILE *fp); void sort__setup_elide(FILE *fp);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment