diff options
Diffstat (limited to 'tools/perf/builtin-report.c')
-rw-r--r-- | tools/perf/builtin-report.c | 374 |
1 files changed, 220 insertions, 154 deletions
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index f2ed2b7e80a3..f5fbd670d619 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -31,6 +31,7 @@ #include "util/evsel.h" #include "util/evswitch.h" #include "util/header.h" +#include "util/mem-info.h" #include "util/session.h" #include "util/srcline.h" #include "util/tool.h" @@ -59,6 +60,7 @@ #include <linux/ctype.h> #include <signal.h> #include <linux/bitmap.h> +#include <linux/list_sort.h> #include <linux/string.h> #include <linux/stringify.h> #include <linux/time64.h> @@ -68,7 +70,7 @@ #include <linux/mman.h> #ifdef HAVE_LIBTRACEEVENT -#include <traceevent/event-parse.h> +#include <event-parse.h> #endif struct report { @@ -171,7 +173,7 @@ static int hist_iter__report_callback(struct hist_entry_iter *iter, struct mem_info *mi; struct branch_info *bi; - if (!ui__has_annotation() && !rep->symbol_ipc && !rep->data_type) + if (!ui__has_annotation() && !rep->symbol_ipc) return 0; if (sort__mode == SORT_MODE__BRANCH) { @@ -184,7 +186,7 @@ static int hist_iter__report_callback(struct hist_entry_iter *iter, } else if (rep->mem_mode) { mi = he->mem_info; - err = addr_map_symbol__inc_samples(&mi->daddr, sample, evsel); + err = addr_map_symbol__inc_samples(mem_info__daddr(mi), sample, evsel); if (err) goto out; @@ -261,7 +263,7 @@ static int process_feature_event(struct perf_session *session, return 0; } -static int process_sample_event(struct perf_tool *tool, +static int process_sample_event(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct evsel *evsel, @@ -321,12 +323,12 @@ static int process_sample_event(struct perf_tool *tool, } if (al.map != NULL) - map__dso(al.map)->hit = 1; + dso__set_hit(map__dso(al.map)); if (ui__has_annotation() || rep->symbol_ipc || rep->total_cycles_mode) { hist__account_cycles(sample->branch_stack, &al, sample, rep->nonany_branch_mode, - &rep->total_cycles); + &rep->total_cycles, evsel); } ret = hist_entry_iter__add(&iter, &al, rep->max_stack, rep); @@ -337,7 +339,7 @@ out_put: return ret; } -static int process_read_event(struct perf_tool *tool, +static int process_read_event(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample __maybe_unused, struct evsel *evsel, @@ -346,11 +348,9 @@ static int process_read_event(struct perf_tool *tool, struct report *rep = container_of(tool, struct report, tool); if (rep->show_threads) { - const char *name = evsel__name(evsel); int err = perf_read_values_add_value(&rep->show_threads_values, event->read.pid, event->read.tid, - evsel->core.idx, - name, + evsel, event->read.value); if (err) @@ -427,7 +427,7 @@ static int report__setup_sample_type(struct report *rep) * compatibility, set the bit if it's an old perf data file. */ evlist__for_each_entry(session->evlist, evsel) { - if (strstr(evsel->name, "arm_spe") && + if (strstr(evsel__name(evsel), "arm_spe") && !(sample_type & PERF_SAMPLE_DATA_SRC)) { evsel->core.attr.sample_type |= PERF_SAMPLE_DATA_SRC; sample_type |= PERF_SAMPLE_DATA_SRC; @@ -453,7 +453,7 @@ static int report__setup_sample_type(struct report *rep) if (!(evlist__combined_branch_type(session->evlist) & PERF_SAMPLE_BRANCH_ANY)) rep->nonany_branch_mode = true; -#if !defined(HAVE_LIBUNWIND_SUPPORT) && !defined(HAVE_DWARF_SUPPORT) +#if !defined(HAVE_LIBUNWIND_SUPPORT) && !defined(HAVE_LIBDW_SUPPORT) if (dwarf_callchain_users) { ui__warning("Please install libunwind or libdw " "development packages during the perf build.\n"); @@ -563,6 +563,7 @@ static int evlist__tty_browse_hists(struct evlist *evlist, struct report *rep, c struct hists *hists = evsel__hists(pos); const char *evname = evsel__name(pos); + i++; if (symbol_conf.event_group && !evsel__is_group_leader(pos)) continue; @@ -572,7 +573,14 @@ static int evlist__tty_browse_hists(struct evlist *evlist, struct report *rep, c hists__fprintf_nr_sample_events(hists, rep, evname, stdout); if (rep->total_cycles_mode) { - report__browse_block_hists(&rep->block_reports[i++].hist, + char *buf; + + if (!annotation_br_cntr_abbr_list(&buf, pos, true)) { + fprintf(stdout, "%s", buf); + fprintf(stdout, "#\n"); + free(buf); + } + report__browse_block_hists(&rep->block_reports[i - 1].hist, rep->min_percent, pos, NULL); continue; } @@ -608,7 +616,7 @@ static void report__warn_kptr_restrict(const struct report *rep) return; if (kernel_map == NULL || - (map__dso(kernel_map)->hit && + (dso__hit(map__dso(kernel_map)) && (kernel_kmap->ref_reloc_sym == NULL || kernel_kmap->ref_reloc_sym->addr == 0))) { const char *desc = @@ -763,7 +771,7 @@ static void report__output_resort(struct report *rep) ui_progress__finish(); } -static int count_sample_event(struct perf_tool *tool __maybe_unused, +static int count_sample_event(const struct perf_tool *tool __maybe_unused, union perf_event *event __maybe_unused, struct perf_sample *sample __maybe_unused, struct evsel *evsel, @@ -775,7 +783,7 @@ static int count_sample_event(struct perf_tool *tool __maybe_unused, return 0; } -static int count_lost_samples_event(struct perf_tool *tool, +static int count_lost_samples_event(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct machine *machine __maybe_unused) @@ -785,22 +793,28 @@ static int count_lost_samples_event(struct perf_tool *tool, evsel = evlist__id2evsel(rep->session->evlist, sample->id); if (evsel) { - hists__inc_nr_lost_samples(evsel__hists(evsel), - event->lost_samples.lost); + struct hists *hists = evsel__hists(evsel); + u32 count = event->lost_samples.lost; + + if (event->header.misc & PERF_RECORD_MISC_LOST_SAMPLES_BPF) + hists__inc_nr_dropped_samples(hists, count); + else + hists__inc_nr_lost_samples(hists, count); } return 0; } -static int process_attr(struct perf_tool *tool __maybe_unused, +static int process_attr(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct evlist **pevlist); static void stats_setup(struct report *rep) { - memset(&rep->tool, 0, sizeof(rep->tool)); + perf_tool__init(&rep->tool, /*ordered_events=*/false); rep->tool.attr = process_attr; rep->tool.sample = count_sample_event; rep->tool.lost_samples = count_lost_samples_event; + rep->tool.event_update = perf_event__process_event_update; rep->tool.no_warn = true; } @@ -808,15 +822,14 @@ static int stats_print(struct report *rep) { struct perf_session *session = rep->session; - perf_session__fprintf_nr_events(session, stdout, rep->skip_empty); - evlist__fprintf_nr_events(session->evlist, stdout, rep->skip_empty); + perf_session__fprintf_nr_events(session, stdout); + evlist__fprintf_nr_events(session->evlist, stdout); return 0; } static void tasks_setup(struct report *rep) { - memset(&rep->tool, 0, sizeof(rep->tool)); - rep->tool.ordered_events = true; + perf_tool__init(&rep->tool, /*ordered_events=*/true); if (rep->mmaps_mode) { rep->tool.mmap = perf_event__process_mmap; rep->tool.mmap2 = perf_event__process_mmap2; @@ -828,35 +841,6 @@ static void tasks_setup(struct report *rep) rep->tool.no_warn = true; } -struct task { - struct thread *thread; - struct list_head list; - struct list_head children; -}; - -static struct task *tasks_list(struct task *task, struct machine *machine) -{ - struct thread *parent_thread, *thread = task->thread; - struct task *parent_task; - - /* Already listed. */ - if (!list_empty(&task->list)) - return NULL; - - /* Last one in the chain. */ - if (thread__ppid(thread) == -1) - return task; - - parent_thread = machine__find_thread(machine, -1, thread__ppid(thread)); - if (!parent_thread) - return ERR_PTR(-ENOENT); - - parent_task = thread__priv(parent_thread); - thread__put(parent_thread); - list_add_tail(&task->list, &parent_task->children); - return tasks_list(parent_task, machine); -} - struct maps__fprintf_task_args { int indent; FILE *fp; @@ -878,7 +862,7 @@ static int maps__fprintf_task_cb(struct map *map, void *data) prot & PROT_EXEC ? 'x' : '-', map__flags(map) ? 's' : 'p', map__pgoff(map), - dso->id.ino, dso->name); + dso__id_const(dso)->ino, dso__name(dso)); if (ret < 0) return ret; @@ -900,89 +884,156 @@ static size_t maps__fprintf_task(struct maps *maps, int indent, FILE *fp) return args.printed; } -static void task__print_level(struct task *task, FILE *fp, int level) +static int thread_level(struct machine *machine, const struct thread *thread) { - struct thread *thread = task->thread; - struct task *child; - int comm_indent = fprintf(fp, " %8d %8d %8d |%*s", - thread__pid(thread), thread__tid(thread), - thread__ppid(thread), level, ""); + struct thread *parent_thread; + int res; - fprintf(fp, "%s\n", thread__comm_str(thread)); + if (thread__tid(thread) <= 0) + return 0; - maps__fprintf_task(thread__maps(thread), comm_indent, fp); + if (thread__ppid(thread) <= 0) + return 1; - if (!list_empty(&task->children)) { - list_for_each_entry(child, &task->children, list) - task__print_level(child, fp, level + 1); + parent_thread = machine__find_thread(machine, -1, thread__ppid(thread)); + if (!parent_thread) { + pr_err("Missing parent thread of %d\n", thread__tid(thread)); + return 0; } + res = 1 + thread_level(machine, parent_thread); + thread__put(parent_thread); + return res; } -static int tasks_print(struct report *rep, FILE *fp) +static void task__print_level(struct machine *machine, struct thread *thread, FILE *fp) { - struct perf_session *session = rep->session; - struct machine *machine = &session->machines.host; - struct task *tasks, *task; - unsigned int nr = 0, itask = 0, i; - struct rb_node *nd; - LIST_HEAD(list); + int level = thread_level(machine, thread); + int comm_indent = fprintf(fp, " %8d %8d %8d |%*s", + thread__pid(thread), thread__tid(thread), + thread__ppid(thread), level, ""); - /* - * No locking needed while accessing machine->threads, - * because --tasks is single threaded command. - */ + fprintf(fp, "%s\n", thread__comm_str(thread)); - /* Count all the threads. */ - for (i = 0; i < THREADS__TABLE_SIZE; i++) - nr += machine->threads[i].nr; + maps__fprintf_task(thread__maps(thread), comm_indent, fp); +} - tasks = malloc(sizeof(*tasks) * nr); - if (!tasks) - return -ENOMEM; +/* + * Sort two thread list nodes such that they form a tree. The first node is the + * root of the tree, its children are ordered numerically after it. If a child + * has children itself then they appear immediately after their parent. For + * example, the 4 threads in the order they'd appear in the list: + * - init with a TID 1 and a parent of 0 + * - systemd with a TID 3000 and a parent of init/1 + * - systemd child thread with TID 4000, the parent is 3000 + * - NetworkManager is a child of init with a TID of 3500. + */ +static int task_list_cmp(void *priv, const struct list_head *la, const struct list_head *lb) +{ + struct machine *machine = priv; + struct thread_list *task_a = list_entry(la, struct thread_list, list); + struct thread_list *task_b = list_entry(lb, struct thread_list, list); + struct thread *a = task_a->thread; + struct thread *b = task_b->thread; + int level_a, level_b, res; + + /* Same thread? */ + if (thread__tid(a) == thread__tid(b)) + return 0; - for (i = 0; i < THREADS__TABLE_SIZE; i++) { - struct threads *threads = &machine->threads[i]; + /* Compare a and b to root. */ + if (thread__tid(a) == 0) + return -1; - for (nd = rb_first_cached(&threads->entries); nd; - nd = rb_next(nd)) { - task = tasks + itask++; + if (thread__tid(b) == 0) + return 1; - task->thread = rb_entry(nd, struct thread_rb_node, rb_node)->thread; - INIT_LIST_HEAD(&task->children); - INIT_LIST_HEAD(&task->list); - thread__set_priv(task->thread, task); - } - } + /* If parents match sort by tid. */ + if (thread__ppid(a) == thread__ppid(b)) + return thread__tid(a) < thread__tid(b) ? -1 : 1; /* - * Iterate every task down to the unprocessed parent - * and link all in task children list. Task with no - * parent is added into 'list'. + * Find a and b such that if they are a child of each other a and b's + * tid's match, otherwise a and b have a common parent and distinct + * tid's to sort by. First make the depths of the threads match. */ - for (itask = 0; itask < nr; itask++) { - task = tasks + itask; - - if (!list_empty(&task->list)) - continue; - - task = tasks_list(task, machine); - if (IS_ERR(task)) { - pr_err("Error: failed to process tasks\n"); - free(tasks); - return PTR_ERR(task); + level_a = thread_level(machine, a); + level_b = thread_level(machine, b); + a = thread__get(a); + b = thread__get(b); + for (int i = level_a; i > level_b; i--) { + struct thread *parent = machine__find_thread(machine, -1, thread__ppid(a)); + + thread__put(a); + if (!parent) { + pr_err("Missing parent thread of %d\n", thread__tid(a)); + thread__put(b); + return -1; } + a = parent; + } + for (int i = level_b; i > level_a; i--) { + struct thread *parent = machine__find_thread(machine, -1, thread__ppid(b)); - if (task) - list_add_tail(&task->list, &list); + thread__put(b); + if (!parent) { + pr_err("Missing parent thread of %d\n", thread__tid(b)); + thread__put(a); + return 1; + } + b = parent; + } + /* Search up to a common parent. */ + while (thread__ppid(a) != thread__ppid(b)) { + struct thread *parent; + + parent = machine__find_thread(machine, -1, thread__ppid(a)); + thread__put(a); + if (!parent) + pr_err("Missing parent thread of %d\n", thread__tid(a)); + a = parent; + parent = machine__find_thread(machine, -1, thread__ppid(b)); + thread__put(b); + if (!parent) + pr_err("Missing parent thread of %d\n", thread__tid(b)); + b = parent; + if (!a || !b) { + /* Handle missing parent (unexpected) with some sanity. */ + thread__put(a); + thread__put(b); + return !a && !b ? 0 : (!a ? -1 : 1); + } } + if (thread__tid(a) == thread__tid(b)) { + /* a is a child of b or vice-versa, deeper levels appear later. */ + res = level_a < level_b ? -1 : (level_a > level_b ? 1 : 0); + } else { + /* Sort by tid now the parent is the same. */ + res = thread__tid(a) < thread__tid(b) ? -1 : 1; + } + thread__put(a); + thread__put(b); + return res; +} - fprintf(fp, "# %8s %8s %8s %s\n", "pid", "tid", "ppid", "comm"); +static int tasks_print(struct report *rep, FILE *fp) +{ + struct machine *machine = &rep->session->machines.host; + LIST_HEAD(tasks); + int ret; - list_for_each_entry(task, &list, list) - task__print_level(task, fp, 0); + ret = machine__thread_list(machine, &tasks); + if (!ret) { + struct thread_list *task; - free(tasks); - return 0; + list_sort(machine, &tasks, task_list_cmp); + + fprintf(fp, "# %8s %8s %8s %s\n", "pid", "tid", "ppid", "comm"); + + list_for_each_entry(task, &tasks, list) + task__print_level(machine, task->thread, fp); + } + thread_list__delete(&tasks); + return ret; } static int __cmd_report(struct report *rep) @@ -1049,10 +1100,7 @@ static int __cmd_report(struct report *rep) perf_session__fprintf_dsos(session, stdout); if (dump_trace) { - perf_session__fprintf_nr_events(session, stdout, - rep->skip_empty); - evlist__fprintf_nr_events(session->evlist, stdout, - rep->skip_empty); + stats_print(rep); return 0; } } @@ -1082,18 +1130,23 @@ static int __cmd_report(struct report *rep) report__output_resort(rep); if (rep->total_cycles_mode) { - int block_hpps[6] = { + int nr_hpps = 4; + int block_hpps[PERF_HPP_REPORT__BLOCK_MAX_INDEX] = { PERF_HPP_REPORT__BLOCK_TOTAL_CYCLES_PCT, PERF_HPP_REPORT__BLOCK_LBR_CYCLES, PERF_HPP_REPORT__BLOCK_CYCLES_PCT, PERF_HPP_REPORT__BLOCK_AVG_CYCLES, - PERF_HPP_REPORT__BLOCK_RANGE, - PERF_HPP_REPORT__BLOCK_DSO, }; + if (session->evlist->nr_br_cntr > 0) + block_hpps[nr_hpps++] = PERF_HPP_REPORT__BLOCK_BRANCH_COUNTER; + + block_hpps[nr_hpps++] = PERF_HPP_REPORT__BLOCK_RANGE; + block_hpps[nr_hpps++] = PERF_HPP_REPORT__BLOCK_DSO; + rep->block_reports = block_info__create_report(session->evlist, rep->total_cycles, - block_hpps, 6, + block_hpps, nr_hpps, &rep->nr_block_reports); if (!rep->block_reports) return -1; @@ -1196,7 +1249,7 @@ parse_percent_limit(const struct option *opt, const char *str, return 0; } -static int process_attr(struct perf_tool *tool __maybe_unused, +static int process_attr(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct evlist **pevlist) { @@ -1216,6 +1269,10 @@ static int process_attr(struct perf_tool *tool __maybe_unused, return 0; } +#define CALLCHAIN_BRANCH_SORT_ORDER \ + "srcline,symbol,dso,callchain_branch_predicted," \ + "callchain_branch_abort,callchain_branch_cycles" + int cmd_report(int argc, const char **argv) { struct perf_session *session; @@ -1235,37 +1292,13 @@ int cmd_report(int argc, const char **argv) NULL }; struct report report = { - .tool = { - .sample = process_sample_event, - .mmap = perf_event__process_mmap, - .mmap2 = perf_event__process_mmap2, - .comm = perf_event__process_comm, - .namespaces = perf_event__process_namespaces, - .cgroup = perf_event__process_cgroup, - .exit = perf_event__process_exit, - .fork = perf_event__process_fork, - .lost = perf_event__process_lost, - .read = process_read_event, - .attr = process_attr, -#ifdef HAVE_LIBTRACEEVENT - .tracing_data = perf_event__process_tracing_data, -#endif - .build_id = perf_event__process_build_id, - .id_index = perf_event__process_id_index, - .auxtrace_info = perf_event__process_auxtrace_info, - .auxtrace = perf_event__process_auxtrace, - .event_update = perf_event__process_event_update, - .feature = process_feature_event, - .ordered_events = true, - .ordering_requires_timestamps = true, - }, .max_stack = PERF_MAX_STACK_DEPTH, .pretty_printing_style = "normal", .socket_filter = -1, .skip_empty = true, }; - char *sort_order_help = sort_help("sort by key(s):"); - char *field_order_help = sort_help("output field(s): overhead period sample "); + char *sort_order_help = sort_help("sort by key(s):", SORT_MODE__NORMAL); + char *field_order_help = sort_help("output field(s):", SORT_MODE__NORMAL); const char *disassembler_style = NULL, *objdump_path = NULL, *addr2line_path = NULL; const struct option options[] = { OPT_STRING('i', "input", &input_name, "file", @@ -1387,7 +1420,7 @@ int cmd_report(int argc, const char **argv) OPT_STRING(0, "addr2line", &addr2line_path, "path", "addr2line binary to use for line numbers"), OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle, - "Disable symbol demangling"), + "Symbol demangling. Enabled by default, use --no-demangle to disable."), OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel, "Enable kernel symbol demangling"), OPT_BOOLEAN(0, "mem-mode", &report.mem_mode, "mem access profile"), @@ -1410,7 +1443,7 @@ int cmd_report(int argc, const char **argv) "only show processor socket that match with this filter"), OPT_BOOLEAN(0, "raw-trace", &symbol_conf.raw_trace, "Show raw trace event output (do not use print fmt or plugins)"), - OPT_BOOLEAN(0, "hierarchy", &symbol_conf.report_hierarchy, + OPT_BOOLEAN('H', "hierarchy", &symbol_conf.report_hierarchy, "Show entries in a hierarchy"), OPT_CALLBACK_DEFAULT(0, "stdio-color", NULL, "mode", "'always' (default), 'never' or 'auto' only applicable to --stdio mode", @@ -1440,6 +1473,7 @@ int cmd_report(int argc, const char **argv) }; int ret = hists__init(); char sort_tmp[128]; + bool ordered_events = true; if (ret < 0) goto exit; @@ -1494,7 +1528,7 @@ int cmd_report(int argc, const char **argv) report.tasks_mode = true; if (dump_trace && report.disable_order) - report.tool.ordered_events = false; + ordered_events = false; if (quiet) perf_quiet_option(); @@ -1522,7 +1556,32 @@ int cmd_report(int argc, const char **argv) data.path = input_name; data.force = symbol_conf.force; + symbol_conf.skip_empty = report.skip_empty; + repeat: + perf_tool__init(&report.tool, ordered_events); + report.tool.sample = process_sample_event; + report.tool.mmap = perf_event__process_mmap; + report.tool.mmap2 = perf_event__process_mmap2; + report.tool.comm = perf_event__process_comm; + report.tool.namespaces = perf_event__process_namespaces; + report.tool.cgroup = perf_event__process_cgroup; + report.tool.exit = perf_event__process_exit; + report.tool.fork = perf_event__process_fork; + report.tool.lost = perf_event__process_lost; + report.tool.read = process_read_event; + report.tool.attr = process_attr; +#ifdef HAVE_LIBTRACEEVENT + report.tool.tracing_data = perf_event__process_tracing_data; +#endif + report.tool.build_id = perf_event__process_build_id; + report.tool.id_index = perf_event__process_id_index; + report.tool.auxtrace_info = perf_event__process_auxtrace_info; + report.tool.auxtrace = perf_event__process_auxtrace; + report.tool.event_update = perf_event__process_event_update; + report.tool.feature = process_feature_event; + report.tool.ordering_requires_timestamps = true; + session = perf_session__new(&data, &report.tool); if (IS_ERR(session)) { ret = PTR_ERR(session); @@ -1582,7 +1641,7 @@ repeat: symbol_conf.use_callchain = true; callchain_register_param(&callchain_param); if (sort_order == NULL) - sort_order = "srcline,symbol,dso"; + sort_order = CALLCHAIN_BRANCH_SORT_ORDER; } if (report.mem_mode) { @@ -1644,7 +1703,7 @@ repeat: report.data_type = true; annotate_opts.annotate_src = false; -#ifndef HAVE_DWARF_GETLOCATIONS_SUPPORT +#ifndef HAVE_LIBDW_SUPPORT pr_err("Error: Data type profiling is disabled due to missing DWARF support\n"); goto error; #endif @@ -1655,6 +1714,11 @@ repeat: else use_browser = 0; + if (report.data_type && use_browser == 1) { + symbol_conf.annotate_data_member = true; + symbol_conf.annotate_data_sample = true; + } + if (sort_order && strstr(sort_order, "ipc")) { parse_options_usage(report_usage, options, "s", 1); goto error; @@ -1766,6 +1830,8 @@ repeat: } else ret = 0; + if (!use_browser && (verbose > 2 || debug_kmaps)) + perf_session__dump_kmaps(session); error: if (report.ptime_range) { itrace_synth_opts__clear_time_range(&itrace_synth_opts); |