mirror of
https://github.com/torvalds/linux.git
synced 2024-12-26 12:52:30 +00:00
b50311dc2a
This patch adds support for taken branch sampling, i.e, the PERF_SAMPLE_BRANCH_STACK feature to perf report. In other words, to display histograms based on taken branches rather than executed instructions addresses. The new option is called -b and it takes no argument. To generate meaningful output, the perf.data must have been obtained using perf record -b xxx ... where xxx is a branch filter option. The output shows symbols, modules, sorted by 'who branches where' the most often. The percentages reported in the first column refer to the total number of branches captured and not the usual number of samples. Here is a quick example. Here branchy is simple test program which looks as follows: void f2(void) {} void f3(void) {} void f1(unsigned long n) { if (n & 1UL) f2(); else f3(); } int main(void) { unsigned long i; for (i=0; i < N; i++) f1(i); return 0; } Here is the output captured on Nehalem, if we are only interested in user level function calls. $ perf record -b any_call,u -e cycles:u branchy $ perf report -b --sort=symbol 52.34% [.] main [.] f1 24.04% [.] f1 [.] f3 23.60% [.] f1 [.] f2 0.01% [k] _IO_new_file_xsputn [k] _IO_file_overflow 0.01% [k] _IO_vfprintf_internal [k] _IO_new_file_xsputn 0.01% [k] _IO_vfprintf_internal [k] strchrnul 0.01% [k] __printf [k] _IO_vfprintf_internal 0.01% [k] main [k] __printf About half (52%) of the call branches captured are from main() -> f1(). The second half (24%+23%) is split in two equal shares between f1() -> f2(), f1() ->f3(). The output is as expected given the code. It should be noted, that using -b in perf record does not eliminate information in the perf.data file. Consequently, a typical profile can also be obtained by perf report by simply not using its -b option. It is possible to sort on branch related columns: - dso_from, symbol_from - dso_to, symbol_to - mispredict Signed-off-by: Roberto Agostino Vitillo <ravitillo@lbl.gov> Signed-off-by: Stephane Eranian <eranian@google.com> Cc: peterz@infradead.org Cc: acme@redhat.com Cc: robert.richter@amd.com Cc: ming.m.lin@intel.com Cc: andi@firstfloor.org Cc: asharma@fb.com Cc: vweaver1@eecs.utk.edu Cc: khandual@linux.vnet.ibm.com Cc: dsahern@gmail.com Link: http://lkml.kernel.org/r/1328826068-11713-14-git-send-email-eranian@google.com Signed-off-by: Ingo Molnar <mingo@elte.hu>
690 lines
19 KiB
C
690 lines
19 KiB
C
/*
|
|
* builtin-report.c
|
|
*
|
|
* Builtin report command: Analyze the perf.data input file,
|
|
* look up and read DSOs and symbol information and display
|
|
* a histogram of results, along various sorting keys.
|
|
*/
|
|
#include "builtin.h"
|
|
|
|
#include "util/util.h"
|
|
|
|
#include "util/annotate.h"
|
|
#include "util/color.h"
|
|
#include <linux/list.h>
|
|
#include "util/cache.h"
|
|
#include <linux/rbtree.h>
|
|
#include "util/symbol.h"
|
|
#include "util/callchain.h"
|
|
#include "util/strlist.h"
|
|
#include "util/values.h"
|
|
|
|
#include "perf.h"
|
|
#include "util/debug.h"
|
|
#include "util/evlist.h"
|
|
#include "util/evsel.h"
|
|
#include "util/header.h"
|
|
#include "util/session.h"
|
|
#include "util/tool.h"
|
|
|
|
#include "util/parse-options.h"
|
|
#include "util/parse-events.h"
|
|
|
|
#include "util/thread.h"
|
|
#include "util/sort.h"
|
|
#include "util/hist.h"
|
|
|
|
#include <linux/bitmap.h>
|
|
|
|
struct perf_report {
|
|
struct perf_tool tool;
|
|
struct perf_session *session;
|
|
char const *input_name;
|
|
bool force, use_tui, use_stdio;
|
|
bool hide_unresolved;
|
|
bool dont_use_callchains;
|
|
bool show_full_info;
|
|
bool show_threads;
|
|
bool inverted_callchain;
|
|
struct perf_read_values show_threads_values;
|
|
const char *pretty_printing_style;
|
|
symbol_filter_t annotate_init;
|
|
const char *cpu_list;
|
|
DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);
|
|
};
|
|
|
|
static int perf_report__add_branch_hist_entry(struct perf_tool *tool,
|
|
struct addr_location *al,
|
|
struct perf_sample *sample,
|
|
struct perf_evsel *evsel,
|
|
struct machine *machine)
|
|
{
|
|
struct perf_report *rep = container_of(tool, struct perf_report, tool);
|
|
struct symbol *parent = NULL;
|
|
int err = 0;
|
|
unsigned i;
|
|
struct hist_entry *he;
|
|
struct branch_info *bi;
|
|
|
|
if ((sort__has_parent || symbol_conf.use_callchain)
|
|
&& sample->callchain) {
|
|
err = machine__resolve_callchain(machine, evsel, al->thread,
|
|
sample->callchain, &parent);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
bi = machine__resolve_bstack(machine, al->thread,
|
|
sample->branch_stack);
|
|
if (!bi)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < sample->branch_stack->nr; i++) {
|
|
if (rep->hide_unresolved && !(bi[i].from.sym && bi[i].to.sym))
|
|
continue;
|
|
/*
|
|
* The report shows the percentage of total branches captured
|
|
* and not events sampled. Thus we use a pseudo period of 1.
|
|
*/
|
|
he = __hists__add_branch_entry(&evsel->hists, al, parent,
|
|
&bi[i], 1);
|
|
if (he) {
|
|
evsel->hists.stats.total_period += 1;
|
|
hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE);
|
|
} else
|
|
return -ENOMEM;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int perf_evsel__add_hist_entry(struct perf_evsel *evsel,
|
|
struct addr_location *al,
|
|
struct perf_sample *sample,
|
|
struct machine *machine)
|
|
{
|
|
struct symbol *parent = NULL;
|
|
int err = 0;
|
|
struct hist_entry *he;
|
|
|
|
if ((sort__has_parent || symbol_conf.use_callchain) && sample->callchain) {
|
|
err = machine__resolve_callchain(machine, evsel, al->thread,
|
|
sample->callchain, &parent);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
he = __hists__add_entry(&evsel->hists, al, parent, sample->period);
|
|
if (he == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (symbol_conf.use_callchain) {
|
|
err = callchain_append(he->callchain,
|
|
&evsel->hists.callchain_cursor,
|
|
sample->period);
|
|
if (err)
|
|
return err;
|
|
}
|
|
/*
|
|
* Only in the newt browser we are doing integrated annotation,
|
|
* so we don't allocated the extra space needed because the stdio
|
|
* code will not use it.
|
|
*/
|
|
if (al->sym != NULL && use_browser > 0) {
|
|
struct annotation *notes = symbol__annotation(he->ms.sym);
|
|
|
|
assert(evsel != NULL);
|
|
|
|
err = -ENOMEM;
|
|
if (notes->src == NULL && symbol__alloc_hist(he->ms.sym) < 0)
|
|
goto out;
|
|
|
|
err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr);
|
|
}
|
|
|
|
evsel->hists.stats.total_period += sample->period;
|
|
hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
|
|
static int process_sample_event(struct perf_tool *tool,
|
|
union perf_event *event,
|
|
struct perf_sample *sample,
|
|
struct perf_evsel *evsel,
|
|
struct machine *machine)
|
|
{
|
|
struct perf_report *rep = container_of(tool, struct perf_report, tool);
|
|
struct addr_location al;
|
|
|
|
if (perf_event__preprocess_sample(event, machine, &al, sample,
|
|
rep->annotate_init) < 0) {
|
|
fprintf(stderr, "problem processing %d event, skipping it.\n",
|
|
event->header.type);
|
|
return -1;
|
|
}
|
|
|
|
if (al.filtered || (rep->hide_unresolved && al.sym == NULL))
|
|
return 0;
|
|
|
|
if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap))
|
|
return 0;
|
|
|
|
if (sort__branch_mode) {
|
|
if (perf_report__add_branch_hist_entry(tool, &al, sample,
|
|
evsel, machine)) {
|
|
pr_debug("problem adding lbr entry, skipping event\n");
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (al.map != NULL)
|
|
al.map->dso->hit = 1;
|
|
|
|
if (perf_evsel__add_hist_entry(evsel, &al, sample, machine)) {
|
|
pr_debug("problem incrementing symbol period, skipping event\n");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int process_read_event(struct perf_tool *tool,
|
|
union perf_event *event,
|
|
struct perf_sample *sample __used,
|
|
struct perf_evsel *evsel,
|
|
struct machine *machine __used)
|
|
{
|
|
struct perf_report *rep = container_of(tool, struct perf_report, tool);
|
|
|
|
if (rep->show_threads) {
|
|
const char *name = evsel ? event_name(evsel) : "unknown";
|
|
perf_read_values_add_value(&rep->show_threads_values,
|
|
event->read.pid, event->read.tid,
|
|
event->read.id,
|
|
name,
|
|
event->read.value);
|
|
}
|
|
|
|
dump_printf(": %d %d %s %" PRIu64 "\n", event->read.pid, event->read.tid,
|
|
evsel ? event_name(evsel) : "FAIL",
|
|
event->read.value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int perf_report__setup_sample_type(struct perf_report *rep)
|
|
{
|
|
struct perf_session *self = rep->session;
|
|
|
|
if (!(self->sample_type & PERF_SAMPLE_CALLCHAIN)) {
|
|
if (sort__has_parent) {
|
|
ui__warning("Selected --sort parent, but no "
|
|
"callchain data. Did you call "
|
|
"'perf record' without -g?\n");
|
|
return -EINVAL;
|
|
}
|
|
if (symbol_conf.use_callchain) {
|
|
ui__warning("Selected -g but no callchain data. Did "
|
|
"you call 'perf record' without -g?\n");
|
|
return -1;
|
|
}
|
|
} else if (!rep->dont_use_callchains &&
|
|
callchain_param.mode != CHAIN_NONE &&
|
|
!symbol_conf.use_callchain) {
|
|
symbol_conf.use_callchain = true;
|
|
if (callchain_register_param(&callchain_param) < 0) {
|
|
ui__warning("Can't register callchain "
|
|
"params.\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (sort__branch_mode) {
|
|
if (!(self->sample_type & PERF_SAMPLE_BRANCH_STACK)) {
|
|
fprintf(stderr, "selected -b but no branch data."
|
|
" Did you call perf record without"
|
|
" -b?\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern volatile int session_done;
|
|
|
|
static void sig_handler(int sig __used)
|
|
{
|
|
session_done = 1;
|
|
}
|
|
|
|
static size_t hists__fprintf_nr_sample_events(struct hists *self,
|
|
const char *evname, FILE *fp)
|
|
{
|
|
size_t ret;
|
|
char unit;
|
|
unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE];
|
|
|
|
nr_events = convert_unit(nr_events, &unit);
|
|
ret = fprintf(fp, "# Events: %lu%c", nr_events, unit);
|
|
if (evname != NULL)
|
|
ret += fprintf(fp, " %s", evname);
|
|
return ret + fprintf(fp, "\n#\n");
|
|
}
|
|
|
|
static int perf_evlist__tty_browse_hists(struct perf_evlist *evlist,
|
|
struct perf_report *rep,
|
|
const char *help)
|
|
{
|
|
struct perf_evsel *pos;
|
|
|
|
list_for_each_entry(pos, &evlist->entries, node) {
|
|
struct hists *hists = &pos->hists;
|
|
const char *evname = event_name(pos);
|
|
|
|
hists__fprintf_nr_sample_events(hists, evname, stdout);
|
|
hists__fprintf(hists, NULL, false, true, 0, 0, stdout);
|
|
fprintf(stdout, "\n\n");
|
|
}
|
|
|
|
if (sort_order == default_sort_order &&
|
|
parent_pattern == default_parent_pattern) {
|
|
fprintf(stdout, "#\n# (%s)\n#\n", help);
|
|
|
|
if (rep->show_threads) {
|
|
bool style = !strcmp(rep->pretty_printing_style, "raw");
|
|
perf_read_values_display(stdout, &rep->show_threads_values,
|
|
style);
|
|
perf_read_values_destroy(&rep->show_threads_values);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __cmd_report(struct perf_report *rep)
|
|
{
|
|
int ret = -EINVAL;
|
|
u64 nr_samples;
|
|
struct perf_session *session;
|
|
struct perf_evsel *pos;
|
|
struct map *kernel_map;
|
|
struct kmap *kernel_kmap;
|
|
const char *help = "For a higher level overview, try: perf report --sort comm,dso";
|
|
|
|
signal(SIGINT, sig_handler);
|
|
|
|
session = perf_session__new(rep->input_name, O_RDONLY,
|
|
rep->force, false, &rep->tool);
|
|
if (session == NULL)
|
|
return -ENOMEM;
|
|
|
|
rep->session = session;
|
|
|
|
if (rep->cpu_list) {
|
|
ret = perf_session__cpu_bitmap(session, rep->cpu_list,
|
|
rep->cpu_bitmap);
|
|
if (ret)
|
|
goto out_delete;
|
|
}
|
|
|
|
if (use_browser <= 0)
|
|
perf_session__fprintf_info(session, stdout, rep->show_full_info);
|
|
|
|
if (rep->show_threads)
|
|
perf_read_values_init(&rep->show_threads_values);
|
|
|
|
ret = perf_report__setup_sample_type(rep);
|
|
if (ret)
|
|
goto out_delete;
|
|
|
|
ret = perf_session__process_events(session, &rep->tool);
|
|
if (ret)
|
|
goto out_delete;
|
|
|
|
kernel_map = session->host_machine.vmlinux_maps[MAP__FUNCTION];
|
|
kernel_kmap = map__kmap(kernel_map);
|
|
if (kernel_map == NULL ||
|
|
(kernel_map->dso->hit &&
|
|
(kernel_kmap->ref_reloc_sym == NULL ||
|
|
kernel_kmap->ref_reloc_sym->addr == 0))) {
|
|
const struct dso *kdso = kernel_map->dso;
|
|
|
|
ui__warning(
|
|
"Kernel address maps (/proc/{kallsyms,modules}) were restricted.\n\n"
|
|
"Check /proc/sys/kernel/kptr_restrict before running 'perf record'.\n\n%s\n\n"
|
|
"Samples in kernel modules can't be resolved as well.\n\n",
|
|
RB_EMPTY_ROOT(&kdso->symbols[MAP__FUNCTION]) ?
|
|
"As no suitable kallsyms nor vmlinux was found, kernel samples\n"
|
|
"can't be resolved." :
|
|
"If some relocation was applied (e.g. kexec) symbols may be misresolved.");
|
|
}
|
|
|
|
if (dump_trace) {
|
|
perf_session__fprintf_nr_events(session, stdout);
|
|
goto out_delete;
|
|
}
|
|
|
|
if (verbose > 3)
|
|
perf_session__fprintf(session, stdout);
|
|
|
|
if (verbose > 2)
|
|
perf_session__fprintf_dsos(session, stdout);
|
|
|
|
nr_samples = 0;
|
|
list_for_each_entry(pos, &session->evlist->entries, node) {
|
|
struct hists *hists = &pos->hists;
|
|
|
|
hists__collapse_resort(hists);
|
|
hists__output_resort(hists);
|
|
nr_samples += hists->stats.nr_events[PERF_RECORD_SAMPLE];
|
|
}
|
|
|
|
if (nr_samples == 0) {
|
|
ui__warning("The %s file has no samples!\n", session->filename);
|
|
goto out_delete;
|
|
}
|
|
|
|
if (use_browser > 0) {
|
|
perf_evlist__tui_browse_hists(session->evlist, help,
|
|
NULL, NULL, 0);
|
|
} else
|
|
perf_evlist__tty_browse_hists(session->evlist, rep, help);
|
|
|
|
out_delete:
|
|
/*
|
|
* Speed up the exit process, for large files this can
|
|
* take quite a while.
|
|
*
|
|
* XXX Enable this when using valgrind or if we ever
|
|
* librarize this command.
|
|
*
|
|
* Also experiment with obstacks to see how much speed
|
|
* up we'll get here.
|
|
*
|
|
* perf_session__delete(session);
|
|
*/
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
parse_callchain_opt(const struct option *opt, const char *arg, int unset)
|
|
{
|
|
struct perf_report *rep = (struct perf_report *)opt->value;
|
|
char *tok, *tok2;
|
|
char *endptr;
|
|
|
|
/*
|
|
* --no-call-graph
|
|
*/
|
|
if (unset) {
|
|
rep->dont_use_callchains = true;
|
|
return 0;
|
|
}
|
|
|
|
symbol_conf.use_callchain = true;
|
|
|
|
if (!arg)
|
|
return 0;
|
|
|
|
tok = strtok((char *)arg, ",");
|
|
if (!tok)
|
|
return -1;
|
|
|
|
/* get the output mode */
|
|
if (!strncmp(tok, "graph", strlen(arg)))
|
|
callchain_param.mode = CHAIN_GRAPH_ABS;
|
|
|
|
else if (!strncmp(tok, "flat", strlen(arg)))
|
|
callchain_param.mode = CHAIN_FLAT;
|
|
|
|
else if (!strncmp(tok, "fractal", strlen(arg)))
|
|
callchain_param.mode = CHAIN_GRAPH_REL;
|
|
|
|
else if (!strncmp(tok, "none", strlen(arg))) {
|
|
callchain_param.mode = CHAIN_NONE;
|
|
symbol_conf.use_callchain = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
else
|
|
return -1;
|
|
|
|
/* get the min percentage */
|
|
tok = strtok(NULL, ",");
|
|
if (!tok)
|
|
goto setup;
|
|
|
|
callchain_param.min_percent = strtod(tok, &endptr);
|
|
if (tok == endptr)
|
|
return -1;
|
|
|
|
/* get the print limit */
|
|
tok2 = strtok(NULL, ",");
|
|
if (!tok2)
|
|
goto setup;
|
|
|
|
if (tok2[0] != 'c') {
|
|
callchain_param.print_limit = strtoul(tok2, &endptr, 0);
|
|
tok2 = strtok(NULL, ",");
|
|
if (!tok2)
|
|
goto setup;
|
|
}
|
|
|
|
/* get the call chain order */
|
|
if (!strcmp(tok2, "caller"))
|
|
callchain_param.order = ORDER_CALLER;
|
|
else if (!strcmp(tok2, "callee"))
|
|
callchain_param.order = ORDER_CALLEE;
|
|
else
|
|
return -1;
|
|
setup:
|
|
if (callchain_register_param(&callchain_param) < 0) {
|
|
fprintf(stderr, "Can't register callchain params\n");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int cmd_report(int argc, const char **argv, const char *prefix __used)
|
|
{
|
|
struct stat st;
|
|
char callchain_default_opt[] = "fractal,0.5,callee";
|
|
const char * const report_usage[] = {
|
|
"perf report [<options>]",
|
|
NULL
|
|
};
|
|
struct perf_report report = {
|
|
.tool = {
|
|
.sample = process_sample_event,
|
|
.mmap = perf_event__process_mmap,
|
|
.comm = perf_event__process_comm,
|
|
.exit = perf_event__process_task,
|
|
.fork = perf_event__process_task,
|
|
.lost = perf_event__process_lost,
|
|
.read = process_read_event,
|
|
.attr = perf_event__process_attr,
|
|
.event_type = perf_event__process_event_type,
|
|
.tracing_data = perf_event__process_tracing_data,
|
|
.build_id = perf_event__process_build_id,
|
|
.ordered_samples = true,
|
|
.ordering_requires_timestamps = true,
|
|
},
|
|
.pretty_printing_style = "normal",
|
|
};
|
|
const struct option options[] = {
|
|
OPT_STRING('i', "input", &report.input_name, "file",
|
|
"input file name"),
|
|
OPT_INCR('v', "verbose", &verbose,
|
|
"be more verbose (show symbol address, etc)"),
|
|
OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
|
|
"dump raw trace in ASCII"),
|
|
OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
|
|
"file", "vmlinux pathname"),
|
|
OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name,
|
|
"file", "kallsyms pathname"),
|
|
OPT_BOOLEAN('f', "force", &report.force, "don't complain, do it"),
|
|
OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
|
|
"load module symbols - WARNING: use only with -k and LIVE kernel"),
|
|
OPT_BOOLEAN('n', "show-nr-samples", &symbol_conf.show_nr_samples,
|
|
"Show a column with the number of samples"),
|
|
OPT_BOOLEAN('T', "threads", &report.show_threads,
|
|
"Show per-thread event counters"),
|
|
OPT_STRING(0, "pretty", &report.pretty_printing_style, "key",
|
|
"pretty printing style key: normal raw"),
|
|
OPT_BOOLEAN(0, "tui", &report.use_tui, "Use the TUI interface"),
|
|
OPT_BOOLEAN(0, "stdio", &report.use_stdio,
|
|
"Use the stdio interface"),
|
|
OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
|
|
"sort by key(s): pid, comm, dso, symbol, parent, dso_to,"
|
|
" dso_from, symbol_to, symbol_from, mispredict"),
|
|
OPT_BOOLEAN(0, "showcpuutilization", &symbol_conf.show_cpu_utilization,
|
|
"Show sample percentage for different cpu modes"),
|
|
OPT_STRING('p', "parent", &parent_pattern, "regex",
|
|
"regex filter to identify parent, see: '--sort parent'"),
|
|
OPT_BOOLEAN('x', "exclude-other", &symbol_conf.exclude_other,
|
|
"Only display entries with parent-match"),
|
|
OPT_CALLBACK_DEFAULT('g', "call-graph", &report, "output_type,min_percent[,print_limit],call_order",
|
|
"Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit and callchain order. "
|
|
"Default: fractal,0.5,callee", &parse_callchain_opt, callchain_default_opt),
|
|
OPT_BOOLEAN('G', "inverted", &report.inverted_callchain,
|
|
"alias for inverted call graph"),
|
|
OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
|
|
"only consider symbols in these dsos"),
|
|
OPT_STRING('c', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
|
|
"only consider symbols in these comms"),
|
|
OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
|
|
"only consider these symbols"),
|
|
OPT_STRING('w', "column-widths", &symbol_conf.col_width_list_str,
|
|
"width[,width...]",
|
|
"don't try to adjust column width, use these fixed values"),
|
|
OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator",
|
|
"separator for columns, no spaces will be added between "
|
|
"columns '.' is reserved."),
|
|
OPT_BOOLEAN('U', "hide-unresolved", &report.hide_unresolved,
|
|
"Only display entries resolved to a symbol"),
|
|
OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
|
|
"Look for files with symbols relative to this directory"),
|
|
OPT_STRING('C', "cpu", &report.cpu_list, "cpu",
|
|
"list of cpus to profile"),
|
|
OPT_BOOLEAN('I', "show-info", &report.show_full_info,
|
|
"Display extended information about perf.data file"),
|
|
OPT_BOOLEAN(0, "source", &symbol_conf.annotate_src,
|
|
"Interleave source code with assembly code (default)"),
|
|
OPT_BOOLEAN(0, "asm-raw", &symbol_conf.annotate_asm_raw,
|
|
"Display raw encoding of assembly instructions (default)"),
|
|
OPT_STRING('M', "disassembler-style", &disassembler_style, "disassembler style",
|
|
"Specify disassembler style (e.g. -M intel for intel syntax)"),
|
|
OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period,
|
|
"Show a column with the sum of periods"),
|
|
OPT_BOOLEAN('b', "branch-stack", &sort__branch_mode,
|
|
"use branch records for histogram filling"),
|
|
OPT_END()
|
|
};
|
|
|
|
argc = parse_options(argc, argv, options, report_usage, 0);
|
|
|
|
if (report.use_stdio)
|
|
use_browser = 0;
|
|
else if (report.use_tui)
|
|
use_browser = 1;
|
|
|
|
if (report.inverted_callchain)
|
|
callchain_param.order = ORDER_CALLER;
|
|
|
|
if (!report.input_name || !strlen(report.input_name)) {
|
|
if (!fstat(STDIN_FILENO, &st) && S_ISFIFO(st.st_mode))
|
|
report.input_name = "-";
|
|
else
|
|
report.input_name = "perf.data";
|
|
}
|
|
|
|
if (sort__branch_mode) {
|
|
if (use_browser)
|
|
fprintf(stderr, "Warning: TUI interface not supported"
|
|
" in branch mode\n");
|
|
if (symbol_conf.dso_list_str != NULL)
|
|
fprintf(stderr, "Warning: dso filtering not supported"
|
|
" in branch mode\n");
|
|
if (symbol_conf.sym_list_str != NULL)
|
|
fprintf(stderr, "Warning: symbol filtering not"
|
|
" supported in branch mode\n");
|
|
|
|
report.use_stdio = true;
|
|
use_browser = 0;
|
|
setup_browser(true);
|
|
symbol_conf.dso_list_str = NULL;
|
|
symbol_conf.sym_list_str = NULL;
|
|
|
|
/*
|
|
* 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";
|
|
|
|
} else if (strcmp(report.input_name, "-") != 0) {
|
|
setup_browser(true);
|
|
} else {
|
|
use_browser = 0;
|
|
}
|
|
|
|
/*
|
|
* Only in the newt browser we are doing integrated annotation,
|
|
* so don't allocate extra space that won't be used in the stdio
|
|
* implementation.
|
|
*/
|
|
if (use_browser > 0) {
|
|
symbol_conf.priv_size = sizeof(struct annotation);
|
|
report.annotate_init = symbol__annotate_init;
|
|
/*
|
|
* For searching by name on the "Browse map details".
|
|
* providing it only in verbose mode not to bloat too
|
|
* much struct symbol.
|
|
*/
|
|
if (verbose) {
|
|
/*
|
|
* XXX: Need to provide a less kludgy way to ask for
|
|
* more space per symbol, the u32 is for the index on
|
|
* the ui browser.
|
|
* See symbol__browser_index.
|
|
*/
|
|
symbol_conf.priv_size += sizeof(u32);
|
|
symbol_conf.sort_by_name = true;
|
|
}
|
|
}
|
|
|
|
if (symbol__init() < 0)
|
|
return -1;
|
|
|
|
setup_sorting(report_usage, options);
|
|
|
|
if (parent_pattern != default_parent_pattern) {
|
|
if (sort_dimension__add("parent") < 0)
|
|
return -1;
|
|
|
|
/*
|
|
* Only show the parent fields if we explicitly
|
|
* sort that way. If we only use parent machinery
|
|
* for filtering, we don't want it.
|
|
*/
|
|
if (!strstr(sort_order, "parent"))
|
|
sort_parent.elide = 1;
|
|
} else
|
|
symbol_conf.exclude_other = false;
|
|
|
|
/*
|
|
* Any (unrecognized) arguments left?
|
|
*/
|
|
if (argc)
|
|
usage_with_options(report_usage, options);
|
|
|
|
sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", stdout);
|
|
sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, "comm", stdout);
|
|
sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", stdout);
|
|
|
|
return __cmd_report(&report);
|
|
}
|