Commit e450f90e authored by Ingo Molnar's avatar Ingo Molnar

Merge tag 'perf-core-for-mingo' of...

Merge tag 'perf-core-for-mingo' of git://git.kernel.org/pub/scm/linux/kernel/git/jolsa/perf into perf/core

Pull perf/core improvements and fixes from Jiri Olsa:

  * Android related fixes for pager and map dso resolving (Michael Lentine)

  * Add -F option for specifying output fields (Namhyung Kim)
Signed-off-by: default avatarJiri Olsa <jolsa@kernel.org>
Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parents 6480c561 eca81836
......@@ -50,7 +50,8 @@ OPTIONS
-s::
--sort=::
Sort by key(s): pid, comm, dso, symbol.
Sort by key(s): pid, comm, dso, symbol, cpu, parent, srcline.
Please see description of --sort in the perf-report man page.
-t::
--field-separator=::
......@@ -202,4 +203,4 @@ If specified the 'Weighted diff' column is displayed with value 'd' computed as:
SEE ALSO
--------
linkperf:perf-record[1]
linkperf:perf-record[1], linkperf:perf-report[1]
......@@ -79,6 +79,15 @@ OPTIONS
abort cost. This is the global weight.
- local_weight: Local weight version of the weight above.
- transaction: Transaction abort flags.
- overhead: Overhead percentage of sample
- overhead_sys: Overhead percentage of sample running in system mode
- overhead_us: Overhead percentage of sample running in user mode
- overhead_guest_sys: Overhead percentage of sample running in system mode
on guest machine
- overhead_guest_us: Overhead percentage of sample running in user mode on
guest machine
- sample: Number of sample
- period: Raw number of event count of sample
By default, comm, dso and symbol keys are used.
(i.e. --sort comm,dso,symbol)
......@@ -98,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=<regex>::
A regex filter to identify parent. The parent is a caller of this
......
......@@ -113,7 +113,17 @@ Default is to monitor all CPUS.
-s::
--sort::
Sort by key(s): pid, comm, dso, symbol, parent, srcline, weight,
local_weight, abort, in_tx, transaction
local_weight, abort, in_tx, transaction, overhead, sample, period.
Please see description of --sort in the perf-report man page.
--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 --field will be appended
automatically.
-n::
--show-nr-samples::
......@@ -212,4 +222,4 @@ Pressing any unmapped key displays a menu, and prompts for input.
SEE ALSO
--------
linkperf:perf-stat[1], linkperf:perf-list[1]
linkperf:perf-stat[1], linkperf:perf-list[1], linkperf:perf-report[1]
......@@ -399,6 +399,7 @@ LIB_OBJS += $(OUTPUT)tests/pmu.o
LIB_OBJS += $(OUTPUT)tests/hists_common.o
LIB_OBJS += $(OUTPUT)tests/hists_link.o
LIB_OBJS += $(OUTPUT)tests/hists_filter.o
LIB_OBJS += $(OUTPUT)tests/hists_output.o
LIB_OBJS += $(OUTPUT)tests/python-use.o
LIB_OBJS += $(OUTPUT)tests/bp_signal.o
LIB_OBJS += $(OUTPUT)tests/bp_signal_overflow.o
......
......@@ -60,7 +60,6 @@ static int data__files_cnt;
#define data__for_each_file(i, d) data__for_each_file_start(i, d, 0)
#define data__for_each_file_new(i, d) data__for_each_file_start(i, d, 1)
static char diff__default_sort_order[] = "dso,symbol";
static bool force;
static bool show_period;
static bool show_formula;
......@@ -741,7 +740,8 @@ static const struct option options[] = {
OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
"only consider these symbols"),
OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
"sort by key(s): pid, comm, dso, symbol, parent"),
"sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..."
" Please refer the man page for the complete list."),
OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator",
"separator for columns, no spaces will be added between "
"columns '.' is reserved."),
......@@ -1141,7 +1141,6 @@ int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused)
{
perf_config(perf_default_config, NULL);
sort_order = diff__default_sort_order;
argc = parse_options(argc, argv, options, diff_usage, 0);
if (symbol__init() < 0)
......@@ -1152,6 +1151,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused)
ui_init();
sort__mode = SORT_MODE__DIFF;
if (setup_sorting() < 0)
usage_with_options(diff_usage, options);
......
......@@ -699,10 +699,10 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
OPT_BOOLEAN(0, "header-only", &report.header_only,
"Show only data header."),
OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
"sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline,"
" dso_to, dso_from, symbol_to, symbol_from, mispredict,"
" weight, local_weight, mem, symbol_daddr, dso_daddr, tlb, "
"snoop, locked, abort, in_tx, transaction"),
"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",
......@@ -807,39 +807,20 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
if (branch_mode == -1 && has_br_stack)
sort__mode = SORT_MODE__BRANCH;
/* sort__mode could be NORMAL if --no-branch-stack */
if (sort__mode == SORT_MODE__BRANCH) {
/*
* if no sort_order is provided, then specify
* branch-mode specific order
*/
if (sort_order == default_sort_order)
sort_order = "comm,dso_from,symbol_from,"
"dso_to,symbol_to";
}
if (report.mem_mode) {
if (sort__mode == SORT_MODE__BRANCH) {
pr_err("branch and mem mode incompatible\n");
goto error;
}
sort__mode = SORT_MODE__MEMORY;
/*
* if no sort_order is provided, then specify
* branch-mode specific order
*/
if (sort_order == default_sort_order)
sort_order = "local_weight,mem,sym,dso,symbol_daddr,dso_daddr,snoop,tlb,locked";
}
if (setup_sorting() < 0) {
if (sort_order)
parse_options_usage(report_usage, options, "s", 1);
goto error;
}
if (parent_pattern != default_parent_pattern) {
if (sort_dimension__add("parent") < 0)
if (field_order)
parse_options_usage(sort_order ? NULL : report_usage,
options, "F", 1);
goto error;
}
......@@ -849,10 +830,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
if (strcmp(input_name, "-") != 0)
setup_browser(true);
else {
else
use_browser = 0;
perf_hpp__init();
}
if (report.header || report.header_only) {
perf_session__fprintf_info(session, stdout,
......
......@@ -1083,8 +1083,10 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused)
OPT_INCR('v', "verbose", &verbose,
"be more verbose (show counter open errors, etc)"),
OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
"sort by key(s): pid, comm, dso, symbol, parent, weight, local_weight,"
" abort, in_tx, transaction"),
"sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..."
" Please refer the man page for the complete list."),
OPT_STRING(0, "fields", &field_order, "key[,keys...]",
"output field(s): overhead, period, sample plus all of sort keys"),
OPT_BOOLEAN('n', "show-nr-samples", &symbol_conf.show_nr_samples,
"Show a column with the number of samples"),
OPT_CALLBACK_NOOPT('g', NULL, &top.record_opts,
......@@ -1137,17 +1139,19 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused)
if (argc)
usage_with_options(top_usage, options);
if (sort_order == default_sort_order)
sort_order = "dso,symbol";
sort__mode = SORT_MODE__TOP;
/* display thread wants entries to be collapsed in a different tree */
sort__need_collapse = 1;
if (setup_sorting() < 0) {
if (sort_order)
parse_options_usage(top_usage, options, "s", 1);
if (field_order)
parse_options_usage(sort_order ? NULL : top_usage,
options, "fields", 0);
goto out_delete_evlist;
}
/* display thread wants entries to be collapsed in a different tree */
sort__need_collapse = 1;
if (top.use_stdio)
use_browser = 0;
else if (top.use_tui)
......
......@@ -135,6 +135,10 @@ static struct test {
.desc = "Test thread mg sharing",
.func = test__thread_mg_share,
},
{
.desc = "Test output sorting of hist entries",
.func = test__hists_output,
},
{
.func = NULL,
},
......
......@@ -146,3 +146,60 @@ struct machine *setup_fake_machine(struct machines *machines)
machine__delete(machine);
return NULL;
}
void print_hists_in(struct hists *hists)
{
int i = 0;
struct rb_root *root;
struct rb_node *node;
if (sort__need_collapse)
root = &hists->entries_collapsed;
else
root = hists->entries_in;
pr_info("----- %s --------\n", __func__);
node = rb_first(root);
while (node) {
struct hist_entry *he;
he = rb_entry(node, struct hist_entry, rb_node_in);
if (!he->filtered) {
pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n",
i, thread__comm_str(he->thread),
he->ms.map->dso->short_name,
he->ms.sym->name, he->stat.period);
}
i++;
node = rb_next(node);
}
}
void print_hists_out(struct hists *hists)
{
int i = 0;
struct rb_root *root;
struct rb_node *node;
root = &hists->entries;
pr_info("----- %s --------\n", __func__);
node = rb_first(root);
while (node) {
struct hist_entry *he;
he = rb_entry(node, struct hist_entry, rb_node);
if (!he->filtered) {
pr_info("%2d: entry: %8s:%5d [%-8s] %20s: period = %"PRIu64"\n",
i, thread__comm_str(he->thread), he->thread->tid,
he->ms.map->dso->short_name,
he->ms.sym->name, he->stat.period);
}
i++;
node = rb_next(node);
}
}
......@@ -41,4 +41,7 @@ struct machines;
*/
struct machine *setup_fake_machine(struct machines *machines);
void print_hists_in(struct hists *hists);
void print_hists_out(struct hists *hists);
#endif /* __PERF_TESTS__HISTS_COMMON_H__ */
......@@ -98,33 +98,6 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine)
return TEST_FAIL;
}
static void print_hists(struct hists *hists)
{
int i = 0;
struct rb_root *root;
struct rb_node *node;
root = &hists->entries;
pr_info("----- %s --------\n", __func__);
node = rb_first(root);
while (node) {
struct hist_entry *he;
he = rb_entry(node, struct hist_entry, rb_node);
if (!he->filtered) {
pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n",
i, thread__comm_str(he->thread),
he->ms.map->dso->short_name,
he->ms.sym->name, he->stat.period);
}
i++;
node = rb_next(node);
}
}
int test__hists_filter(void)
{
int err = TEST_FAIL;
......@@ -169,7 +142,7 @@ int test__hists_filter(void)
if (verbose > 2) {
pr_info("Normal histogram\n");
print_hists(hists);
print_hists_out(hists);
}
TEST_ASSERT_VAL("Invalid nr samples",
......@@ -193,7 +166,7 @@ int test__hists_filter(void)
if (verbose > 2) {
pr_info("Histogram for thread filter\n");
print_hists(hists);
print_hists_out(hists);
}
/* normal stats should be invariant */
......@@ -222,7 +195,7 @@ int test__hists_filter(void)
if (verbose > 2) {
pr_info("Histogram for dso filter\n");
print_hists(hists);
print_hists_out(hists);
}
/* normal stats should be invariant */
......@@ -257,7 +230,7 @@ int test__hists_filter(void)
if (verbose > 2) {
pr_info("Histogram for symbol filter\n");
print_hists(hists);
print_hists_out(hists);
}
/* normal stats should be invariant */
......@@ -284,7 +257,7 @@ int test__hists_filter(void)
if (verbose > 2) {
pr_info("Histogram for all filters\n");
print_hists(hists);
print_hists_out(hists);
}
/* normal stats should be invariant */
......@@ -310,6 +283,7 @@ int test__hists_filter(void)
out:
/* tear down everything */
perf_evlist__delete(evlist);
reset_output_field();
machines__exit(&machines);
return err;
......
......@@ -268,33 +268,6 @@ static int validate_link(struct hists *leader, struct hists *other)
return __validate_link(leader, 0) || __validate_link(other, 1);
}
static void print_hists(struct hists *hists)
{
int i = 0;
struct rb_root *root;
struct rb_node *node;
if (sort__need_collapse)
root = &hists->entries_collapsed;
else
root = hists->entries_in;
pr_info("----- %s --------\n", __func__);
node = rb_first(root);
while (node) {
struct hist_entry *he;
he = rb_entry(node, struct hist_entry, rb_node_in);
pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n",
i, thread__comm_str(he->thread), he->ms.map->dso->short_name,
he->ms.sym->name, he->stat.period);
i++;
node = rb_next(node);
}
}
int test__hists_link(void)
{
int err = -1;
......@@ -336,7 +309,7 @@ int test__hists_link(void)
hists__collapse_resort(&evsel->hists, NULL);
if (verbose > 2)
print_hists(&evsel->hists);
print_hists_in(&evsel->hists);
}
first = perf_evlist__first(evlist);
......@@ -359,6 +332,7 @@ int test__hists_link(void)
out:
/* tear down everything */
perf_evlist__delete(evlist);
reset_output_field();
machines__exit(&machines);
return err;
......
#include "perf.h"
#include "util/debug.h"
#include "util/symbol.h"
#include "util/sort.h"
#include "util/evsel.h"
#include "util/evlist.h"
#include "util/machine.h"
#include "util/thread.h"
#include "util/parse-events.h"
#include "tests/tests.h"
#include "tests/hists_common.h"
struct sample {
u32 cpu;
u32 pid;
u64 ip;
struct thread *thread;
struct map *map;
struct symbol *sym;
};
/* For the numbers, see hists_common.c */
static struct sample fake_samples[] = {
/* perf [kernel] schedule() */
{ .cpu = 0, .pid = 100, .ip = 0xf0000 + 700, },
/* perf [perf] main() */
{ .cpu = 1, .pid = 100, .ip = 0x40000 + 700, },
/* perf [perf] cmd_record() */
{ .cpu = 1, .pid = 100, .ip = 0x40000 + 900, },
/* perf [libc] malloc() */
{ .cpu = 1, .pid = 100, .ip = 0x50000 + 700, },
/* perf [libc] free() */
{ .cpu = 2, .pid = 100, .ip = 0x50000 + 800, },
/* perf [perf] main() */
{ .cpu = 2, .pid = 200, .ip = 0x40000 + 700, },
/* perf [kernel] page_fault() */
{ .cpu = 2, .pid = 200, .ip = 0xf0000 + 800, },
/* bash [bash] main() */
{ .cpu = 3, .pid = 300, .ip = 0x40000 + 700, },
/* bash [bash] xmalloc() */
{ .cpu = 0, .pid = 300, .ip = 0x40000 + 800, },
/* bash [kernel] page_fault() */
{ .cpu = 1, .pid = 300, .ip = 0xf0000 + 800, },
};
static int add_hist_entries(struct hists *hists, struct machine *machine)
{
struct addr_location al;
struct hist_entry *he;
struct perf_sample sample = { .period = 100, };
size_t i;
for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
const union perf_event event = {
.header = {
.misc = PERF_RECORD_MISC_USER,
},
};
sample.cpu = fake_samples[i].cpu;
sample.pid = fake_samples[i].pid;
sample.tid = fake_samples[i].pid;
sample.ip = fake_samples[i].ip;
if (perf_event__preprocess_sample(&event, machine, &al,
&sample) < 0)
goto out;
he = __hists__add_entry(hists, &al, NULL, NULL, NULL,
sample.period, 1, 0);
if (he == NULL)
goto out;
fake_samples[i].thread = al.thread;
fake_samples[i].map = al.map;
fake_samples[i].sym = al.sym;
}
return TEST_OK;
out:
pr_debug("Not enough memory for adding a hist entry\n");
return TEST_FAIL;
}
static void del_hist_entries(struct hists *hists)
{
struct hist_entry *he;
struct rb_root *root_in;
struct rb_root *root_out;
struct rb_node *node;
if (sort__need_collapse)
root_in = &hists->entries_collapsed;
else
root_in = hists->entries_in;
root_out = &hists->entries;
while (!RB_EMPTY_ROOT(root_out)) {
node = rb_first(root_out);
he = rb_entry(node, struct hist_entry, rb_node);
rb_erase(node, root_out);
rb_erase(&he->rb_node_in, root_in);
hist_entry__free(he);
}
}
typedef int (*test_fn_t)(struct perf_evsel *, struct machine *);
#define COMM(he) (thread__comm_str(he->thread))
#define DSO(he) (he->ms.map->dso->short_name)
#define SYM(he) (he->ms.sym->name)
#define CPU(he) (he->cpu)
#define PID(he) (he->thread->tid)
/* default sort keys (no field) */
static int test1(struct perf_evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = &evsel->hists;
struct hist_entry *he;
struct rb_root *root;
struct rb_node *node;
field_order = NULL;
sort_order = NULL; /* equivalent to sort_order = "comm,dso,sym" */
setup_sorting();
/*
* expected output:
*
* Overhead Command Shared Object Symbol
* ======== ======= ============= ==============
* 20.00% perf perf [.] main
* 10.00% bash [kernel] [k] page_fault
* 10.00% bash bash [.] main
* 10.00% bash bash [.] xmalloc
* 10.00% perf [kernel] [k] page_fault
* 10.00% perf [kernel] [k] schedule
* 10.00% perf libc [.] free
* 10.00% perf libc [.] malloc
* 10.00% perf perf [.] cmd_record
*/
err = add_hist_entries(hists, machine);
if (err < 0)
goto out;
hists__collapse_resort(hists, NULL);
hists__output_resort(hists);
if (verbose > 2) {
pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
print_hists_out(hists);
}
root = &evsel->hists.entries;
node = rb_first(root);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
!strcmp(SYM(he), "main") && he->stat.period == 200);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") &&
!strcmp(SYM(he), "page_fault") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
!strcmp(SYM(he), "main") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
!strcmp(SYM(he), "xmalloc") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
!strcmp(SYM(he), "page_fault") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
!strcmp(SYM(he), "schedule") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
!strcmp(SYM(he), "free") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
!strcmp(SYM(he), "malloc") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
!strcmp(SYM(he), "cmd_record") && he->stat.period == 100);
out:
del_hist_entries(hists);
reset_output_field();
return err;
}
/* mixed fields and sort keys */
static int test2(struct perf_evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = &evsel->hists;
struct hist_entry *he;
struct rb_root *root;
struct rb_node *node;
field_order = "overhead,cpu";
sort_order = "pid";
setup_sorting();
/*
* expected output:
*
* Overhead CPU Command: Pid
* ======== === =============
* 30.00% 1 perf : 100
* 10.00% 0 perf : 100
* 10.00% 2 perf : 100
* 20.00% 2 perf : 200
* 10.00% 0 bash : 300
* 10.00% 1 bash : 300
* 10.00% 3 bash : 300
*/
err = add_hist_entries(hists, machine);
if (err < 0)
goto out;
hists__collapse_resort(hists, NULL);
hists__output_resort(hists);
if (verbose > 2) {
pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
print_hists_out(hists);
}
root = &evsel->hists.entries;
node = rb_first(root);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 1 && PID(he) == 100 && he->stat.period == 300);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 0 && PID(he) == 100 && he->stat.period == 100);
out:
del_hist_entries(hists);
reset_output_field();
return err;
}
/* fields only (no sort key) */
static int test3(struct perf_evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = &evsel->hists;
struct hist_entry *he;
struct rb_root *root;
struct rb_node *node;
field_order = "comm,overhead,dso";
sort_order = NULL;
setup_sorting();
/*
* expected output:
*
* Command Overhead Shared Object
* ======= ======== =============
* bash 20.00% bash
* bash 10.00% [kernel]
* perf 30.00% perf
* perf 20.00% [kernel]
* perf 20.00% libc
*/
err = add_hist_entries(hists, machine);
if (err < 0)
goto out;
hists__collapse_resort(hists, NULL);
hists__output_resort(hists);
if (verbose > 2) {
pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
print_hists_out(hists);
}
root = &evsel->hists.entries;
node = rb_first(root);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
he->stat.period == 200);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") &&
he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
he->stat.period == 300);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
he->stat.period == 200);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
he->stat.period == 200);
out:
del_hist_entries(hists);
reset_output_field();
return err;
}
/* handle duplicate 'dso' field */
static int test4(struct perf_evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = &evsel->hists;
struct hist_entry *he;
struct rb_root *root;
struct rb_node *node;
field_order = "dso,sym,comm,overhead,dso";
sort_order = "sym";
setup_sorting();
/*
* expected output:
*
* Shared Object Symbol Command Overhead
* ============= ============== ======= ========
* perf [.] cmd_record perf 10.00%
* libc [.] free perf 10.00%
* bash [.] main bash 10.00%
* perf [.] main perf 20.00%
* libc [.] malloc perf 10.00%
* [kernel] [k] page_fault bash 10.00%
* [kernel] [k] page_fault perf 10.00%
* [kernel] [k] schedule perf 10.00%
* bash [.] xmalloc bash 10.00%
*/
err = add_hist_entries(hists, machine);
if (err < 0)
goto out;
hists__collapse_resort(hists, NULL);
hists__output_resort(hists);
if (verbose > 2) {
pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
print_hists_out(hists);
}
root = &evsel->hists.entries;
node = rb_first(root);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(DSO(he), "perf") && !strcmp(SYM(he), "cmd_record") &&
!strcmp(COMM(he), "perf") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(DSO(he), "libc") && !strcmp(SYM(he), "free") &&
!strcmp(COMM(he), "perf") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(DSO(he), "bash") && !strcmp(SYM(he), "main") &&
!strcmp(COMM(he), "bash") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(DSO(he), "perf") && !strcmp(SYM(he), "main") &&
!strcmp(COMM(he), "perf") && he->stat.period == 200);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(DSO(he), "libc") && !strcmp(SYM(he), "malloc") &&
!strcmp(COMM(he), "perf") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") &&
!strcmp(COMM(he), "bash") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") &&
!strcmp(COMM(he), "perf") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "schedule") &&
!strcmp(COMM(he), "perf") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
!strcmp(DSO(he), "bash") && !strcmp(SYM(he), "xmalloc") &&
!strcmp(COMM(he), "bash") && he->stat.period == 100);
out:
del_hist_entries(hists);
reset_output_field();
return err;
}
/* full sort keys w/o overhead field */
static int test5(struct perf_evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = &evsel->hists;
struct hist_entry *he;
struct rb_root *root;
struct rb_node *node;
field_order = "cpu,pid,comm,dso,sym";
sort_order = "dso,pid";
setup_sorting();
/*
* expected output:
*
* CPU Command: Pid Command Shared Object Symbol
* === ============= ======= ============= ==============
* 0 perf: 100 perf [kernel] [k] schedule
* 2 perf: 200 perf [kernel] [k] page_fault
* 1 bash: 300 bash [kernel] [k] page_fault
* 0 bash: 300 bash bash [.] xmalloc
* 3 bash: 300 bash bash [.] main
* 1 perf: 100 perf libc [.] malloc
* 2 perf: 100 perf libc [.] free
* 1 perf: 100 perf perf [.] cmd_record
* 1 perf: 100 perf perf [.] main
* 2 perf: 200 perf perf [.] main
*/
err = add_hist_entries(hists, machine);
if (err < 0)
goto out;
hists__collapse_resort(hists, NULL);
hists__output_resort(hists);
if (verbose > 2) {
pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
print_hists_out(hists);
}
root = &evsel->hists.entries;
node = rb_first(root);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 0 && PID(he) == 100 &&
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
!strcmp(SYM(he), "schedule") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 2 && PID(he) == 200 &&
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
!strcmp(SYM(he), "page_fault") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 1 && PID(he) == 300 &&
!strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") &&
!strcmp(SYM(he), "page_fault") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 0 && PID(he) == 300 &&
!strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
!strcmp(SYM(he), "xmalloc") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 3 && PID(he) == 300 &&
!strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
!strcmp(SYM(he), "main") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 1 && PID(he) == 100 &&
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
!strcmp(SYM(he), "malloc") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 2 && PID(he) == 100 &&
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
!strcmp(SYM(he), "free") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 1 && PID(he) == 100 &&
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
!strcmp(SYM(he), "cmd_record") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 1 && PID(he) == 100 &&
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
!strcmp(SYM(he), "main") && he->stat.period == 100);
node = rb_next(node);
he = rb_entry(node, struct hist_entry, rb_node);
TEST_ASSERT_VAL("Invalid hist entry",
CPU(he) == 2 && PID(he) == 200 &&
!strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
!strcmp(SYM(he), "main") && he->stat.period == 100);
out:
del_hist_entries(hists);
reset_output_field();
return err;
}
int test__hists_output(void)
{
int err = TEST_FAIL;
struct machines machines;
struct machine *machine;
struct perf_evsel *evsel;
struct perf_evlist *evlist = perf_evlist__new();
size_t i;
test_fn_t testcases[] = {
test1,
test2,
test3,
test4,
test5,
};
TEST_ASSERT_VAL("No memory", evlist);
err = parse_events(evlist, "cpu-clock");
if (err)
goto out;
machines__init(&machines);
/* setup threads/dso/map/symbols also */
machine = setup_fake_machine(&machines);
if (!machine)
goto out;
if (verbose > 1)
machine__fprintf(machine, stderr);
evsel = perf_evlist__first(evlist);
for (i = 0; i < ARRAY_SIZE(testcases); i++) {
err = testcases[i](evsel, machine);
if (err < 0)
break;
}
out:
/* tear down everything */
perf_evlist__delete(evlist);
machines__exit(&machines);
return err;
}
......@@ -44,6 +44,7 @@ int test__dwarf_unwind(void);
int test__hists_filter(void);
int test__mmap_thread_lookup(void);
int test__thread_mg_share(void);
int test__hists_output(void);
#if defined(__x86_64__) || defined(__i386__) || defined(__arm__)
#ifdef HAVE_DWARF_UNWIND_SUPPORT
......
......@@ -616,35 +616,6 @@ struct hpp_arg {
bool current_entry;
};
static int __hpp__overhead_callback(struct perf_hpp *hpp, bool front)
{
struct hpp_arg *arg = hpp->ptr;
if (arg->current_entry && arg->b->navkeypressed)
ui_browser__set_color(arg->b, HE_COLORSET_SELECTED);
else
ui_browser__set_color(arg->b, HE_COLORSET_NORMAL);
if (front) {
if (!symbol_conf.use_callchain)
return 0;
slsmg_printf("%c ", arg->folded_sign);
return 2;
}
return 0;
}
static int __hpp__color_callback(struct perf_hpp *hpp, bool front __maybe_unused)
{
struct hpp_arg *arg = hpp->ptr;
if (!arg->current_entry || !arg->b->navkeypressed)
ui_browser__set_color(arg->b, HE_COLORSET_NORMAL);
return 0;
}
static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
{
struct hpp_arg *arg = hpp->ptr;
......@@ -665,7 +636,7 @@ static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
return ret;
}
#define __HPP_COLOR_PERCENT_FN(_type, _field, _cb) \
#define __HPP_COLOR_PERCENT_FN(_type, _field) \
static u64 __hpp_get_##_field(struct hist_entry *he) \
{ \
return he->stat._field; \
......@@ -676,22 +647,20 @@ hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\
struct perf_hpp *hpp, \
struct hist_entry *he) \
{ \
return __hpp__fmt(hpp, he, __hpp_get_##_field, _cb, " %6.2f%%", \
return __hpp__fmt(hpp, he, __hpp_get_##_field, " %6.2f%%", \
__hpp__slsmg_color_printf, true); \
}
__HPP_COLOR_PERCENT_FN(overhead, period, __hpp__overhead_callback)
__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys, __hpp__color_callback)
__HPP_COLOR_PERCENT_FN(overhead_us, period_us, __hpp__color_callback)
__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys, __hpp__color_callback)
__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us, __hpp__color_callback)
__HPP_COLOR_PERCENT_FN(overhead, period)
__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys)
__HPP_COLOR_PERCENT_FN(overhead_us, period_us)
__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys)
__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
#undef __HPP_COLOR_PERCENT_FN
void hist_browser__init_hpp(void)
{
perf_hpp__init();
perf_hpp__format[PERF_HPP__OVERHEAD].color =
hist_browser__hpp_color_overhead;
perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
......@@ -742,11 +711,27 @@ static int hist_browser__show_entry(struct hist_browser *browser,
ui_browser__gotorc(&browser->b, row, 0);
perf_hpp__for_each_format(fmt) {
if (!first) {
slsmg_printf(" ");
if (perf_hpp__should_skip(fmt))
continue;
if (current_entry && browser->b.navkeypressed) {
ui_browser__set_color(&browser->b,
HE_COLORSET_SELECTED);
} else {
ui_browser__set_color(&browser->b,
HE_COLORSET_NORMAL);
}
if (first) {
if (symbol_conf.use_callchain) {
slsmg_printf("%c ", folded_sign);
width -= 2;
}
first = false;
} else {
slsmg_printf(" ");
width -= 2;
}
if (fmt->color) {
width -= fmt->color(fmt, &hpp, entry);
......@@ -760,8 +745,8 @@ static int hist_browser__show_entry(struct hist_browser *browser,
if (!browser->b.navkeypressed)
width += 1;
hist_entry__sort_snprintf(entry, s, sizeof(s), browser->hists);
slsmg_write_nstring(s, width);
slsmg_write_nstring("", width);
++row;
++printed;
} else
......@@ -830,10 +815,7 @@ static struct rb_node *hists__filter_entries(struct rb_node *nd,
if (total)
percent = h->stat.period * 100.0 / total;
if (percent < min_pcnt)
return NULL;
if (!h->filtered)
if (!h->filtered && percent >= min_pcnt)
return nd;
nd = rb_next(nd);
......@@ -1104,27 +1086,35 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser,
struct hist_entry *he, FILE *fp)
{
char s[8192];
double percent;
int printed = 0;
char folded_sign = ' ';
struct perf_hpp hpp = {
.buf = s,
.size = sizeof(s),
};
struct perf_hpp_fmt *fmt;
bool first = true;
int ret;
if (symbol_conf.use_callchain)
folded_sign = hist_entry__folded(he);
hist_entry__sort_snprintf(he, s, sizeof(s), browser->hists);
percent = (he->stat.period * 100.0) / browser->hists->stats.total_period;
if (symbol_conf.use_callchain)
printed += fprintf(fp, "%c ", folded_sign);
printed += fprintf(fp, " %5.2f%%", percent);
if (symbol_conf.show_nr_samples)
printed += fprintf(fp, " %11u", he->stat.nr_events);
perf_hpp__for_each_format(fmt) {
if (perf_hpp__should_skip(fmt))
continue;
if (symbol_conf.show_total_period)
printed += fprintf(fp, " %12" PRIu64, he->stat.period);
if (!first) {
ret = scnprintf(hpp.buf, hpp.size, " ");
advance_hpp(&hpp, ret);
} else
first = false;
ret = fmt->entry(fmt, &hpp, he);
advance_hpp(&hpp, ret);
}
printed += fprintf(fp, "%s\n", rtrim(s));
if (folded_sign == '-')
......
......@@ -43,7 +43,7 @@ static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,
struct perf_hpp *hpp, \
struct hist_entry *he) \
{ \
return __hpp__fmt(hpp, he, he_get_##_field, NULL, " %6.2f%%", \
return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \
__percent_color_snprintf, true); \
}
......@@ -58,8 +58,6 @@ __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
void perf_gtk__init_hpp(void)
{
perf_hpp__init();
perf_hpp__format[PERF_HPP__OVERHEAD].color =
perf_gtk__hpp_color_overhead;
perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
......@@ -153,7 +151,6 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
struct perf_hpp_fmt *fmt;
GType col_types[MAX_COLUMNS];
GtkCellRenderer *renderer;
struct sort_entry *se;
GtkTreeStore *store;
struct rb_node *nd;
GtkWidget *view;
......@@ -172,16 +169,6 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
perf_hpp__for_each_format(fmt)
col_types[nr_cols++] = G_TYPE_STRING;
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (se->elide)
continue;
if (se == &sort_sym)
sym_col = nr_cols;
col_types[nr_cols++] = G_TYPE_STRING;
}
store = gtk_tree_store_newv(nr_cols, col_types);
view = gtk_tree_view_new();
......@@ -191,6 +178,9 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
col_idx = 0;
perf_hpp__for_each_format(fmt) {
if (perf_hpp__should_skip(fmt))
continue;
fmt->header(fmt, &hpp, hists_to_evsel(hists));
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
......@@ -199,16 +189,6 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
col_idx++, NULL);
}
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (se->elide)
continue;
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
-1, se->se_header,
renderer, "text",
col_idx++, NULL);
}
for (col_idx = 0; col_idx < nr_cols; col_idx++) {
GtkTreeViewColumn *column;
......@@ -245,6 +225,9 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
col_idx = 0;
perf_hpp__for_each_format(fmt) {
if (perf_hpp__should_skip(fmt))
continue;
if (fmt->color)
fmt->color(fmt, &hpp, h);
else
......@@ -253,16 +236,6 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
gtk_tree_store_set(store, &iter, col_idx++, s, -1);
}
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (se->elide)
continue;
se->se_snprintf(h, s, ARRAY_SIZE(s),
hists__col_len(hists, se->se_width_idx));
gtk_tree_store_set(store, &iter, col_idx++, s, -1);
}
if (symbol_conf.use_callchain && sort__has_sym) {
if (callchain_param.mode == CHAIN_GRAPH_REL)
total = h->stat.period;
......
......@@ -16,20 +16,15 @@
})
int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
hpp_field_fn get_field, hpp_callback_fn callback,
const char *fmt, hpp_snprint_fn print_fn, bool fmt_percent)
hpp_field_fn get_field, const char *fmt,
hpp_snprint_fn print_fn, bool fmt_percent)
{
int ret = 0;
int ret;
struct hists *hists = he->hists;
struct perf_evsel *evsel = hists_to_evsel(hists);
char *buf = hpp->buf;
size_t size = hpp->size;
if (callback) {
ret = callback(hpp, true);
advance_hpp(hpp, ret);
}
if (fmt_percent) {
double percent = 0.0;
u64 total = hists__total_period(hists);
......@@ -37,9 +32,9 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
if (total)
percent = 100.0 * get_field(he) / total;
ret += hpp__call_print_fn(hpp, print_fn, fmt, percent);
ret = hpp__call_print_fn(hpp, print_fn, fmt, percent);
} else
ret += hpp__call_print_fn(hpp, print_fn, fmt, get_field(he));
ret = hpp__call_print_fn(hpp, print_fn, fmt, get_field(he));
if (perf_evsel__is_group_event(evsel)) {
int prev_idx, idx_delta;
......@@ -99,13 +94,6 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
}
}
if (callback) {
int __ret = callback(hpp, false);
advance_hpp(hpp, __ret);
ret += __ret;
}
/*
* Restore original buf and size as it's where caller expects
* the result will be saved.
......@@ -116,6 +104,62 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
return ret;
}
static int field_cmp(u64 field_a, u64 field_b)
{
if (field_a > field_b)
return 1;
if (field_a < field_b)
return -1;
return 0;
}
static int __hpp__sort(struct hist_entry *a, struct hist_entry *b,
hpp_field_fn get_field)
{
s64 ret;
int i, nr_members;
struct perf_evsel *evsel;
struct hist_entry *pair;
u64 *fields_a, *fields_b;
ret = field_cmp(get_field(a), get_field(b));
if (ret || !symbol_conf.event_group)
return ret;
evsel = hists_to_evsel(a->hists);
if (!perf_evsel__is_group_event(evsel))
return ret;
nr_members = evsel->nr_members;
fields_a = calloc(sizeof(*fields_a), nr_members);
fields_b = calloc(sizeof(*fields_b), nr_members);
if (!fields_a || !fields_b)
goto out;
list_for_each_entry(pair, &a->pairs.head, pairs.node) {
evsel = hists_to_evsel(pair->hists);
fields_a[perf_evsel__group_idx(evsel)] = get_field(pair);
}
list_for_each_entry(pair, &b->pairs.head, pairs.node) {
evsel = hists_to_evsel(pair->hists);
fields_b[perf_evsel__group_idx(evsel)] = get_field(pair);
}
for (i = 1; i < nr_members; i++) {
ret = field_cmp(fields_a[i], fields_b[i]);
if (ret)
break;
}
out:
free(fields_a);
free(fields_b);
return ret;
}
#define __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
static int hpp__header_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
struct perf_hpp *hpp, \
......@@ -179,7 +223,7 @@ static u64 he_get_##_field(struct hist_entry *he) \
static int hpp__color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
struct perf_hpp *hpp, struct hist_entry *he) \
{ \
return __hpp__fmt(hpp, he, he_get_##_field, NULL, " %6.2f%%", \
return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \
hpp_color_scnprintf, true); \
}
......@@ -188,10 +232,16 @@ static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \
struct perf_hpp *hpp, struct hist_entry *he) \
{ \
const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%"; \
return __hpp__fmt(hpp, he, he_get_##_field, NULL, fmt, \
return __hpp__fmt(hpp, he, he_get_##_field, fmt, \
hpp_entry_scnprintf, true); \
}
#define __HPP_SORT_FN(_type, _field) \
static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \
{ \
return __hpp__sort(a, b, he_get_##_field); \
}
#define __HPP_ENTRY_RAW_FN(_type, _field) \
static u64 he_get_raw_##_field(struct hist_entry *he) \
{ \
......@@ -202,20 +252,29 @@ static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \
struct perf_hpp *hpp, struct hist_entry *he) \
{ \
const char *fmt = symbol_conf.field_sep ? " %"PRIu64 : " %11"PRIu64; \
return __hpp__fmt(hpp, he, he_get_raw_##_field, NULL, fmt, \
return __hpp__fmt(hpp, he, he_get_raw_##_field, fmt, \
hpp_entry_scnprintf, false); \
}
#define __HPP_SORT_RAW_FN(_type, _field) \
static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \
{ \
return __hpp__sort(a, b, he_get_raw_##_field); \
}
#define HPP_PERCENT_FNS(_type, _str, _field, _min_width, _unit_width) \
__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
__HPP_WIDTH_FN(_type, _min_width, _unit_width) \
__HPP_COLOR_PERCENT_FN(_type, _field) \
__HPP_ENTRY_PERCENT_FN(_type, _field)
__HPP_ENTRY_PERCENT_FN(_type, _field) \
__HPP_SORT_FN(_type, _field)
#define HPP_RAW_FNS(_type, _str, _field, _min_width, _unit_width) \
__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
__HPP_WIDTH_FN(_type, _min_width, _unit_width) \
__HPP_ENTRY_RAW_FN(_type, _field)
__HPP_ENTRY_RAW_FN(_type, _field) \
__HPP_SORT_RAW_FN(_type, _field)
HPP_PERCENT_FNS(overhead, "Overhead", period, 8, 8)
......@@ -227,19 +286,31 @@ HPP_PERCENT_FNS(overhead_guest_us, "guest usr", period_guest_us, 9, 8)
HPP_RAW_FNS(samples, "Samples", nr_events, 12, 12)
HPP_RAW_FNS(period, "Period", period, 12, 12)
static int64_t hpp__nop_cmp(struct hist_entry *a __maybe_unused,
struct hist_entry *b __maybe_unused)
{
return 0;
}
#define HPP__COLOR_PRINT_FNS(_name) \
{ \
.header = hpp__header_ ## _name, \
.width = hpp__width_ ## _name, \
.color = hpp__color_ ## _name, \
.entry = hpp__entry_ ## _name \
.entry = hpp__entry_ ## _name, \
.cmp = hpp__nop_cmp, \
.collapse = hpp__nop_cmp, \
.sort = hpp__sort_ ## _name, \
}
#define HPP__PRINT_FNS(_name) \
{ \
.header = hpp__header_ ## _name, \
.width = hpp__width_ ## _name, \
.entry = hpp__entry_ ## _name \
.entry = hpp__entry_ ## _name, \
.cmp = hpp__nop_cmp, \
.collapse = hpp__nop_cmp, \
.sort = hpp__sort_ ## _name, \
}
struct perf_hpp_fmt perf_hpp__format[] = {
......@@ -253,6 +324,7 @@ struct perf_hpp_fmt perf_hpp__format[] = {
};
LIST_HEAD(perf_hpp__list);
LIST_HEAD(perf_hpp__sort_list);
#undef HPP__COLOR_PRINT_FNS
......@@ -270,6 +342,25 @@ LIST_HEAD(perf_hpp__list);
void perf_hpp__init(void)
{
struct list_head *list;
int i;
for (i = 0; i < PERF_HPP__MAX_INDEX; i++) {
struct perf_hpp_fmt *fmt = &perf_hpp__format[i];
INIT_LIST_HEAD(&fmt->list);
/* sort_list may be linked by setup_sorting() */
if (fmt->sort_list.next == NULL)
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) {
......@@ -287,6 +378,11 @@ void perf_hpp__init(void)
if (symbol_conf.show_total_period)
perf_hpp__column_enable(PERF_HPP__PERIOD);
/* prepend overhead field for backward compatiblity. */
list = &perf_hpp__format[PERF_HPP__OVERHEAD].sort_list;
if (list_empty(list))
list_add(list, &perf_hpp__sort_list);
}
void perf_hpp__column_register(struct perf_hpp_fmt *format)
......@@ -294,29 +390,90 @@ void perf_hpp__column_register(struct perf_hpp_fmt *format)
list_add_tail(&format->list, &perf_hpp__list);
}
void perf_hpp__register_sort_field(struct perf_hpp_fmt *format)
{
list_add_tail(&format->sort_list, &perf_hpp__sort_list);
}
void perf_hpp__column_enable(unsigned col)
{
BUG_ON(col >= PERF_HPP__MAX_INDEX);
perf_hpp__column_register(&perf_hpp__format[col]);
}
int hist_entry__sort_snprintf(struct hist_entry *he, char *s, size_t size,
struct hists *hists)
void perf_hpp__setup_output_field(void)
{
const char *sep = symbol_conf.field_sep;
struct sort_entry *se;
int ret = 0;
struct perf_hpp_fmt *fmt;
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (se->elide)
/* append sort keys to output field */
perf_hpp__for_each_sort_list(fmt) {
if (!list_empty(&fmt->list))
continue;
ret += scnprintf(s + ret, size - ret, "%s", sep ?: " ");
ret += se->se_snprintf(he, s + ret, size - ret,
hists__col_len(hists, se->se_width_idx));
/*
* 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;
}
}
return ret;
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;
}
}
void perf_hpp__reset_output_field(void)
{
struct perf_hpp_fmt *fmt, *tmp;
/* reset output fields */
perf_hpp__for_each_format_safe(fmt, tmp) {
list_del_init(&fmt->list);
list_del_init(&fmt->sort_list);
}
/* reset sort keys */
perf_hpp__for_each_sort_list_safe(fmt, tmp) {
list_del_init(&fmt->list);
list_del_init(&fmt->sort_list);
}
}
/*
......@@ -325,22 +482,23 @@ int hist_entry__sort_snprintf(struct hist_entry *he, char *s, size_t size,
unsigned int hists__sort_list_width(struct hists *hists)
{
struct perf_hpp_fmt *fmt;
struct sort_entry *se;
int i = 0, ret = 0;
int ret = 0;
bool first = true;
struct perf_hpp dummy_hpp;
perf_hpp__for_each_format(fmt) {
if (i)
if (perf_hpp__should_skip(fmt))
continue;
if (first)
first = false;
else
ret += 2;
ret += fmt->width(fmt, &dummy_hpp, hists_to_evsel(hists));
}
list_for_each_entry(se, &hist_entry__sort_list, list)
if (!se->elide)
ret += 2 + hists__col_len(hists, se->se_width_idx);
if (verbose) /* Addr + origin */
if (verbose && sort__has_sym) /* Addr + origin */
ret += 3 + BITS_PER_LONG / 4;
return ret;
......
......@@ -86,8 +86,6 @@ void setup_browser(bool fallback_to_pager)
use_browser = 0;
if (fallback_to_pager)
setup_pager();
perf_hpp__init();
break;
}
}
......
......@@ -183,7 +183,8 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root,
* the symbol. No need to print it otherwise it appears as
* displayed twice.
*/
if (!i++ && sort__first_dimension == SORT_SYM)
if (!i++ && field_order == NULL &&
sort_order && !prefixcmp(sort_order, "sym"))
continue;
if (!printed) {
ret += callchain__fprintf_left_margin(fp, left_margin);
......@@ -296,18 +297,24 @@ static size_t hist_entry__callchain_fprintf(struct hist_entry *he,
int left_margin = 0;
u64 total_period = hists->stats.total_period;
if (sort__first_dimension == SORT_COMM) {
struct sort_entry *se = list_first_entry(&hist_entry__sort_list,
typeof(*se), list);
left_margin = hists__col_len(hists, se->se_width_idx);
if (field_order == NULL && (sort_order == NULL ||
!prefixcmp(sort_order, "comm"))) {
struct perf_hpp_fmt *fmt;
perf_hpp__for_each_format(fmt) {
if (!perf_hpp__is_sort_entry(fmt))
continue;
/* must be 'comm' sort entry */
left_margin = fmt->width(fmt, NULL, hists_to_evsel(hists));
left_margin -= thread__comm_len(he->thread);
break;
}
}
return hist_entry_callchain__fprintf(he, total_period, left_margin, fp);
}
static int hist_entry__period_snprintf(struct perf_hpp *hpp,
struct hist_entry *he)
static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp)
{
const char *sep = symbol_conf.field_sep;
struct perf_hpp_fmt *fmt;
......@@ -319,6 +326,9 @@ static int hist_entry__period_snprintf(struct perf_hpp *hpp,
return 0;
perf_hpp__for_each_format(fmt) {
if (perf_hpp__should_skip(fmt))
continue;
/*
* If there's no field_sep, we still need
* to display initial ' '.
......@@ -353,8 +363,7 @@ static int hist_entry__fprintf(struct hist_entry *he, size_t size,
if (size == 0 || size > bfsz)
size = hpp.size = bfsz;
ret = hist_entry__period_snprintf(&hpp, he);
hist_entry__sort_snprintf(he, bf + ret, size - ret, hists);
hist_entry__snprintf(he, &hpp);
ret = fprintf(fp, "%s\n", bf);
......@@ -368,12 +377,10 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
int max_cols, float min_pcnt, FILE *fp)
{
struct perf_hpp_fmt *fmt;
struct sort_entry *se;
struct rb_node *nd;
size_t ret = 0;
unsigned int width;
const char *sep = symbol_conf.field_sep;
const char *col_width = symbol_conf.col_width_list_str;
int nr_rows = 0;
char bf[96];
struct perf_hpp dummy_hpp = {
......@@ -386,12 +393,19 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
init_rem_hits();
perf_hpp__for_each_format(fmt)
perf_hpp__reset_width(fmt, hists);
if (!show_header)
goto print_entries;
fprintf(fp, "# ");
perf_hpp__for_each_format(fmt) {
if (perf_hpp__should_skip(fmt))
continue;
if (!first)
fprintf(fp, "%s", sep ?: " ");
else
......@@ -401,28 +415,6 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
fprintf(fp, "%s", bf);
}
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (se->elide)
continue;
if (sep) {
fprintf(fp, "%c%s", *sep, se->se_header);
continue;
}
width = strlen(se->se_header);
if (symbol_conf.col_width_list_str) {
if (col_width) {
hists__set_col_len(hists, se->se_width_idx,
atoi(col_width));
col_width = strchr(col_width, ',');
if (col_width)
++col_width;
}
}
if (!hists__new_col_len(hists, se->se_width_idx, width))
width = hists__col_len(hists, se->se_width_idx);
fprintf(fp, " %*s", width, se->se_header);
}
fprintf(fp, "\n");
if (max_rows && ++nr_rows >= max_rows)
goto out;
......@@ -437,6 +429,9 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
perf_hpp__for_each_format(fmt) {
unsigned int i;
if (perf_hpp__should_skip(fmt))
continue;
if (!first)
fprintf(fp, "%s", sep ?: " ");
else
......@@ -447,20 +442,6 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
fprintf(fp, ".");
}
list_for_each_entry(se, &hist_entry__sort_list, list) {
unsigned int i;
if (se->elide)
continue;
fprintf(fp, " ");
width = hists__col_len(hists, se->se_width_idx);
if (width == 0)
width = strlen(se->se_header);
for (i = 0; i < width; i++)
fprintf(fp, ".");
}
fprintf(fp, "\n");
if (max_rows && ++nr_rows >= max_rows)
goto out;
......
......@@ -432,11 +432,14 @@ struct hist_entry *__hists__add_entry(struct hists *hists,
int64_t
hist_entry__cmp(struct hist_entry *left, struct hist_entry *right)
{
struct sort_entry *se;
struct perf_hpp_fmt *fmt;
int64_t cmp = 0;
list_for_each_entry(se, &hist_entry__sort_list, list) {
cmp = se->se_cmp(left, right);
perf_hpp__for_each_sort_list(fmt) {
if (perf_hpp__should_skip(fmt))
continue;
cmp = fmt->cmp(left, right);
if (cmp)
break;
}
......@@ -447,15 +450,14 @@ hist_entry__cmp(struct hist_entry *left, struct hist_entry *right)
int64_t
hist_entry__collapse(struct hist_entry *left, struct hist_entry *right)
{
struct sort_entry *se;
struct perf_hpp_fmt *fmt;
int64_t cmp = 0;
list_for_each_entry(se, &hist_entry__sort_list, list) {
int64_t (*f)(struct hist_entry *, struct hist_entry *);
f = se->se_collapse ?: se->se_cmp;
perf_hpp__for_each_sort_list(fmt) {
if (perf_hpp__should_skip(fmt))
continue;
cmp = f(left, right);
cmp = fmt->collapse(left, right);
if (cmp)
break;
}
......@@ -568,64 +570,21 @@ void hists__collapse_resort(struct hists *hists, struct ui_progress *prog)
}
}
/*
* reverse the map, sort on period.
*/
static int period_cmp(u64 period_a, u64 period_b)
static int hist_entry__sort(struct hist_entry *a, struct hist_entry *b)
{
if (period_a > period_b)
return 1;
if (period_a < period_b)
return -1;
return 0;
}
static int hist_entry__sort_on_period(struct hist_entry *a,
struct hist_entry *b)
{
int ret;
int i, nr_members;
struct perf_evsel *evsel;
struct hist_entry *pair;
u64 *periods_a, *periods_b;
ret = period_cmp(a->stat.period, b->stat.period);
if (ret || !symbol_conf.event_group)
return ret;
evsel = hists_to_evsel(a->hists);
nr_members = evsel->nr_members;
if (nr_members <= 1)
return ret;
periods_a = zalloc(sizeof(periods_a) * nr_members);
periods_b = zalloc(sizeof(periods_b) * nr_members);
if (!periods_a || !periods_b)
goto out;
list_for_each_entry(pair, &a->pairs.head, pairs.node) {
evsel = hists_to_evsel(pair->hists);
periods_a[perf_evsel__group_idx(evsel)] = pair->stat.period;
}
struct perf_hpp_fmt *fmt;
int64_t cmp = 0;
list_for_each_entry(pair, &b->pairs.head, pairs.node) {
evsel = hists_to_evsel(pair->hists);
periods_b[perf_evsel__group_idx(evsel)] = pair->stat.period;
}
perf_hpp__for_each_sort_list(fmt) {
if (perf_hpp__should_skip(fmt))
continue;
for (i = 1; i < nr_members; i++) {
ret = period_cmp(periods_a[i], periods_b[i]);
if (ret)
cmp = fmt->sort(a, b);
if (cmp)
break;
}
out:
free(periods_a);
free(periods_b);
return ret;
return cmp;
}
static void hists__reset_filter_stats(struct hists *hists)
......@@ -673,7 +632,7 @@ static void __hists__insert_output_entry(struct rb_root *entries,
parent = *p;
iter = rb_entry(parent, struct hist_entry, rb_node);
if (hist_entry__sort_on_period(he, iter) > 0)
if (hist_entry__sort(he, iter) > 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
......
......@@ -160,15 +160,29 @@ struct perf_hpp_fmt {
struct hist_entry *he);
int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
struct hist_entry *he);
int64_t (*cmp)(struct hist_entry *a, struct hist_entry *b);
int64_t (*collapse)(struct hist_entry *a, struct hist_entry *b);
int64_t (*sort)(struct hist_entry *a, struct hist_entry *b);
struct list_head list;
struct list_head sort_list;
};
extern struct list_head perf_hpp__list;
extern struct list_head perf_hpp__sort_list;
#define perf_hpp__for_each_format(format) \
list_for_each_entry(format, &perf_hpp__list, list)
#define perf_hpp__for_each_format_safe(format, tmp) \
list_for_each_entry_safe(format, tmp, &perf_hpp__list, list)
#define perf_hpp__for_each_sort_list(format) \
list_for_each_entry(format, &perf_hpp__sort_list, sort_list)
#define perf_hpp__for_each_sort_list_safe(format, tmp) \
list_for_each_entry_safe(format, tmp, &perf_hpp__sort_list, sort_list)
extern struct perf_hpp_fmt perf_hpp__format[];
enum {
......@@ -187,14 +201,23 @@ enum {
void perf_hpp__init(void);
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__reset_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);
bool perf_hpp__should_skip(struct perf_hpp_fmt *format);
void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists);
typedef u64 (*hpp_field_fn)(struct hist_entry *he);
typedef int (*hpp_callback_fn)(struct perf_hpp *hpp, bool front);
typedef int (*hpp_snprint_fn)(struct perf_hpp *hpp, const char *fmt, ...);
int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
hpp_field_fn get_field, hpp_callback_fn callback,
const char *fmt, hpp_snprint_fn print_fn, bool fmt_percent);
hpp_field_fn get_field, const char *fmt,
hpp_snprint_fn print_fn, bool fmt_percent);
static inline void advance_hpp(struct perf_hpp *hpp, int inc)
{
......
......@@ -32,6 +32,93 @@ static inline int is_no_dso_memory(const char *filename)
!strcmp(filename, "[heap]");
}
static inline int is_android_lib(const char *filename)
{
return !strncmp(filename, "/data/app-lib", 13) ||
!strncmp(filename, "/system/lib", 11);
}
static inline bool replace_android_lib(const char *filename, char *newfilename)
{
const char *libname;
char *app_abi;
size_t app_abi_length, new_length;
size_t lib_length = 0;
libname = strrchr(filename, '/');
if (libname)
lib_length = strlen(libname);
app_abi = getenv("APP_ABI");
if (!app_abi)
return false;
app_abi_length = strlen(app_abi);
if (!strncmp(filename, "/data/app-lib", 13)) {
char *apk_path;
if (!app_abi_length)
return false;
new_length = 7 + app_abi_length + lib_length;
apk_path = getenv("APK_PATH");
if (apk_path) {
new_length += strlen(apk_path) + 1;
if (new_length > PATH_MAX)
return false;
snprintf(newfilename, new_length,
"%s/libs/%s/%s", apk_path, app_abi, libname);
} else {
if (new_length > PATH_MAX)
return false;
snprintf(newfilename, new_length,
"libs/%s/%s", app_abi, libname);
}
return true;
}
if (!strncmp(filename, "/system/lib/", 11)) {
char *ndk, *app;
const char *arch;
size_t ndk_length;
size_t app_length;
ndk = getenv("NDK_ROOT");
app = getenv("APP_PLATFORM");
if (!(ndk && app))
return false;
ndk_length = strlen(ndk);
app_length = strlen(app);
if (!(ndk_length && app_length && app_abi_length))
return false;
arch = !strncmp(app_abi, "arm", 3) ? "arm" :
!strncmp(app_abi, "mips", 4) ? "mips" :
!strncmp(app_abi, "x86", 3) ? "x86" : NULL;
if (!arch)
return false;
new_length = 27 + ndk_length +
app_length + lib_length
+ strlen(arch);
if (new_length > PATH_MAX)
return false;
snprintf(newfilename, new_length,
"%s/platforms/%s/arch-%s/usr/lib/%s",
ndk, app, arch, libname);
return true;
}
return false;
}
void map__init(struct map *map, enum map_type type,
u64 start, u64 end, u64 pgoff, struct dso *dso)
{
......@@ -59,8 +146,9 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len,
if (map != NULL) {
char newfilename[PATH_MAX];
struct dso *dso;
int anon, no_dso, vdso;
int anon, no_dso, vdso, android;
android = is_android_lib(filename);
anon = is_anon_memory(filename);
vdso = is_vdso_map(filename);
no_dso = is_no_dso_memory(filename);
......@@ -75,6 +163,11 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len,
filename = newfilename;
}
if (android) {
if (replace_android_lib(filename, newfilename))
filename = newfilename;
}
if (vdso) {
pgoff = 0;
dso = vdso__dso_findnew(dsos__list);
......
......@@ -57,13 +57,13 @@ void setup_pager(void)
}
if (!pager)
pager = getenv("PAGER");
if (!pager) {
if (!access("/usr/bin/pager", X_OK))
if (!(pager || access("/usr/bin/pager", X_OK)))
pager = "/usr/bin/pager";
}
if (!(pager || access("/usr/bin/less", X_OK)))
pager = "/usr/bin/less";
if (!pager)
pager = "less";
else if (!*pager || !strcmp(pager, "cat"))
pager = "cat";
if (!*pager || !strcmp(pager, "cat"))
return;
spawned_pager = 1; /* means we are emitting to terminal */
......
......@@ -2,12 +2,18 @@
#include "hist.h"
#include "comm.h"
#include "symbol.h"
#include "evsel.h"
regex_t parent_regex;
const char default_parent_pattern[] = "^sys_|^do_page_fault";
const char *parent_pattern = default_parent_pattern;
const char default_sort_order[] = "comm,dso,symbol";
const char *sort_order = default_sort_order;
const char default_branch_sort_order[] = "comm,dso_from,symbol_from,dso_to,symbol_to";
const char default_mem_sort_order[] = "local_weight,mem,sym,dso,symbol_daddr,dso_daddr,snoop,tlb,locked";
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;
......@@ -16,9 +22,6 @@ int sort__has_sym = 0;
int sort__has_dso = 0;
enum sort_mode sort__mode = SORT_MODE__NORMAL;
enum sort_type sort__first_dimension;
LIST_HEAD(hist_entry__sort_list);
static int repsep_snprintf(char *bf, size_t size, const char *fmt, ...)
{
......@@ -93,6 +96,12 @@ sort__comm_collapse(struct hist_entry *left, struct hist_entry *right)
return comm__str(right->comm) - comm__str(left->comm);
}
static int64_t
sort__comm_sort(struct hist_entry *left, struct hist_entry *right)
{
return strcmp(comm__str(right->comm), comm__str(left->comm));
}
static int hist_entry__comm_snprintf(struct hist_entry *he, char *bf,
size_t size, unsigned int width)
{
......@@ -103,6 +112,7 @@ struct sort_entry sort_comm = {
.se_header = "Command",
.se_cmp = sort__comm_cmp,
.se_collapse = sort__comm_collapse,
.se_sort = sort__comm_sort,
.se_snprintf = hist_entry__comm_snprintf,
.se_width_idx = HISTC_COMM,
};
......@@ -116,7 +126,7 @@ static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r)
const char *dso_name_l, *dso_name_r;
if (!dso_l || !dso_r)
return cmp_null(dso_l, dso_r);
return cmp_null(dso_r, dso_l);
if (verbose) {
dso_name_l = dso_l->long_name;
......@@ -132,7 +142,7 @@ static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r)
static int64_t
sort__dso_cmp(struct hist_entry *left, struct hist_entry *right)
{
return _sort__dso_cmp(left->ms.map, right->ms.map);
return _sort__dso_cmp(right->ms.map, left->ms.map);
}
static int _hist_entry__dso_snprintf(struct map *map, char *bf,
......@@ -204,6 +214,15 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
return _sort__sym_cmp(left->ms.sym, right->ms.sym);
}
static int64_t
sort__sym_sort(struct hist_entry *left, struct hist_entry *right)
{
if (!left->ms.sym || !right->ms.sym)
return cmp_null(left->ms.sym, right->ms.sym);
return strcmp(right->ms.sym->name, left->ms.sym->name);
}
static int _hist_entry__sym_snprintf(struct map *map, struct symbol *sym,
u64 ip, char level, char *bf, size_t size,
unsigned int width)
......@@ -250,6 +269,7 @@ static int hist_entry__sym_snprintf(struct hist_entry *he, char *bf,
struct sort_entry sort_sym = {
.se_header = "Symbol",
.se_cmp = sort__sym_cmp,
.se_sort = sort__sym_sort,
.se_snprintf = hist_entry__sym_snprintf,
.se_width_idx = HISTC_SYMBOL,
};
......@@ -277,7 +297,7 @@ sort__srcline_cmp(struct hist_entry *left, struct hist_entry *right)
map__rip_2objdump(map, right->ip));
}
}
return strcmp(left->srcline, right->srcline);
return strcmp(right->srcline, left->srcline);
}
static int hist_entry__srcline_snprintf(struct hist_entry *he, char *bf,
......@@ -305,7 +325,7 @@ sort__parent_cmp(struct hist_entry *left, struct hist_entry *right)
if (!sym_l || !sym_r)
return cmp_null(sym_l, sym_r);
return strcmp(sym_l->name, sym_r->name);
return strcmp(sym_r->name, sym_l->name);
}
static int hist_entry__parent_snprintf(struct hist_entry *he, char *bf,
......@@ -1027,19 +1047,192 @@ static struct sort_dimension memory_sort_dimensions[] = {
#undef DIM
static void __sort_dimension__add(struct sort_dimension *sd, enum sort_type idx)
struct hpp_dimension {
const char *name;
struct perf_hpp_fmt *fmt;
int taken;
};
#define DIM(d, n) { .name = n, .fmt = &perf_hpp__format[d], }
static struct hpp_dimension hpp_sort_dimensions[] = {
DIM(PERF_HPP__OVERHEAD, "overhead"),
DIM(PERF_HPP__OVERHEAD_SYS, "overhead_sys"),
DIM(PERF_HPP__OVERHEAD_US, "overhead_us"),
DIM(PERF_HPP__OVERHEAD_GUEST_SYS, "overhead_guest_sys"),
DIM(PERF_HPP__OVERHEAD_GUEST_US, "overhead_guest_us"),
DIM(PERF_HPP__SAMPLES, "sample"),
DIM(PERF_HPP__PERIOD, "period"),
};
#undef DIM
struct hpp_sort_entry {
struct perf_hpp_fmt hpp;
struct sort_entry *se;
};
bool perf_hpp__same_sort_entry(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
{
if (sd->taken)
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;
}
void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists)
{
struct hpp_sort_entry *hse;
if (!perf_hpp__is_sort_entry(fmt))
return;
hse = container_of(fmt, struct hpp_sort_entry, hpp);
hists__new_col_len(hists, hse->se->se_width_idx,
strlen(hse->se->se_header));
}
static int __sort__hpp_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
struct perf_evsel *evsel)
{
struct hpp_sort_entry *hse;
size_t len;
hse = container_of(fmt, struct hpp_sort_entry, hpp);
len = hists__col_len(&evsel->hists, hse->se->se_width_idx);
return scnprintf(hpp->buf, hpp->size, "%*s", len, hse->se->se_header);
}
static int __sort__hpp_width(struct perf_hpp_fmt *fmt,
struct perf_hpp *hpp __maybe_unused,
struct perf_evsel *evsel)
{
struct hpp_sort_entry *hse;
hse = container_of(fmt, struct hpp_sort_entry, hpp);
return hists__col_len(&evsel->hists, hse->se->se_width_idx);
}
static int __sort__hpp_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
struct hist_entry *he)
{
struct hpp_sort_entry *hse;
size_t len;
hse = container_of(fmt, struct hpp_sort_entry, hpp);
len = hists__col_len(he->hists, hse->se->se_width_idx);
return hse->se->se_snprintf(he, hpp->buf, hpp->size, len);
}
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 NULL;
}
hse->se = sd->entry;
hse->hpp.header = __sort__hpp_header;
hse->hpp.width = __sort__hpp_width;
hse->hpp.entry = __sort__hpp_entry;
hse->hpp.color = NULL;
hse->hpp.cmp = sd->entry->se_cmp;
hse->hpp.collapse = sd->entry->se_collapse ? : sd->entry->se_cmp;
hse->hpp.sort = sd->entry->se_sort ? : hse->hpp.collapse;
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)
{
if (sd->taken)
return 0;
if (__sort_dimension__add_hpp_sort(sd) < 0)
return -1;
if (sd->entry->se_collapse)
sort__need_collapse = 1;
if (list_empty(&hist_entry__sort_list))
sort__first_dimension = idx;
sd->taken = 1;
return 0;
}
static int __hpp_dimension__add(struct hpp_dimension *hd)
{
if (!hd->taken) {
hd->taken = 1;
perf_hpp__register_sort_field(hd->fmt);
}
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;
list_add_tail(&sd->entry->list, &hist_entry__sort_list);
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)
......@@ -1068,8 +1261,16 @@ int sort_dimension__add(const char *tok)
sort__has_dso = 1;
}
__sort_dimension__add(sd, i);
return 0;
return __sort_dimension__add(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(hd);
}
for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++) {
......@@ -1084,7 +1285,7 @@ int sort_dimension__add(const char *tok)
if (sd->entry == &sort_sym_from || sd->entry == &sort_sym_to)
sort__has_sym = 1;
__sort_dimension__add(sd, i + __SORT_BRANCH_STACK);
__sort_dimension__add(sd);
return 0;
}
......@@ -1100,18 +1301,47 @@ int sort_dimension__add(const char *tok)
if (sd->entry == &sort_mem_daddr_sym)
sort__has_sym = 1;
__sort_dimension__add(sd, i + __SORT_MEMORY_MODE);
__sort_dimension__add(sd);
return 0;
}
return -ESRCH;
}
int setup_sorting(void)
static const char *get_default_sort_order(void)
{
char *tmp, *tok, *str = strdup(sort_order);
const char *default_sort_orders[] = {
default_sort_order,
default_branch_sort_order,
default_mem_sort_order,
default_top_sort_order,
default_diff_sort_order,
};
BUG_ON(sort__mode >= ARRAY_SIZE(default_sort_orders));
return default_sort_orders[sort__mode];
}
static int __setup_sorting(void)
{
char *tmp, *tok, *str;
const char *sort_keys = sort_order;
int ret = 0;
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) {
error("Not enough memory to setup sort keys");
return -ENOMEM;
......@@ -1133,6 +1363,17 @@ int setup_sorting(void)
return ret;
}
bool perf_hpp__should_skip(struct perf_hpp_fmt *format)
{
if (perf_hpp__is_sort_entry(format)) {
struct hpp_sort_entry *hse;
hse = container_of(format, struct hpp_sort_entry, hpp);
return hse->se->elide;
}
return false;
}
static void sort_entry__setup_elide(struct sort_entry *se,
struct strlist *list,
const char *list_name, FILE *fp)
......@@ -1147,7 +1388,8 @@ static void sort_entry__setup_elide(struct sort_entry *se,
void sort__setup_elide(FILE *output)
{
struct sort_entry *se;
struct perf_hpp_fmt *fmt;
struct hpp_sort_entry *hse;
sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list,
"dso", output);
......@@ -1188,11 +1430,157 @@ void sort__setup_elide(FILE *output)
* It makes no sense to elide all of sort entries.
* Just revert them to show up again.
*/
list_for_each_entry(se, &hist_entry__sort_list, list) {
if (!se->elide)
perf_hpp__for_each_format(fmt) {
if (!perf_hpp__is_sort_entry(fmt))
continue;
hse = container_of(fmt, struct hpp_sort_entry, hpp);
if (!hse->se->elide)
return;
}
list_for_each_entry(se, &hist_entry__sort_list, list)
se->elide = false;
perf_hpp__for_each_format(fmt) {
if (!perf_hpp__is_sort_entry(fmt))
continue;
hse = container_of(fmt, struct hpp_sort_entry, hpp);
hse->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;
}
void reset_output_field(void)
{
sort__need_collapse = 0;
sort__has_parent = 0;
sort__has_sym = 0;
sort__has_dso = 0;
reset_dimensions();
perf_hpp__reset_output_field();
}
......@@ -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[];
......@@ -133,6 +134,8 @@ enum sort_mode {
SORT_MODE__NORMAL,
SORT_MODE__BRANCH,
SORT_MODE__MEMORY,
SORT_MODE__TOP,
SORT_MODE__DIFF,
};
enum sort_type {
......@@ -179,6 +182,7 @@ struct sort_entry {
int64_t (*se_cmp)(struct hist_entry *, struct hist_entry *);
int64_t (*se_collapse)(struct hist_entry *, struct hist_entry *);
int64_t (*se_sort)(struct hist_entry *, struct hist_entry *);
int (*se_snprintf)(struct hist_entry *he, char *bf, size_t size,
unsigned int width);
u8 se_width_idx;
......@@ -189,6 +193,8 @@ extern struct sort_entry sort_thread;
extern struct list_head hist_entry__sort_list;
int setup_sorting(void);
int setup_output_field(void);
void reset_output_field(void);
extern int sort_dimension__add(const char *);
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