aboutsummaryrefslogtreecommitdiff
path: root/tools/perf/builtin-report.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/builtin-report.c')
-rw-r--r--tools/perf/builtin-report.c331
1 files changed, 211 insertions, 120 deletions
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index dcedfe00f04d..dcd93ee5fc24 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -59,6 +59,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>
@@ -96,9 +97,9 @@ struct report {
bool stitch_lbr;
bool disable_order;
bool skip_empty;
+ bool data_type;
int max_stack;
struct perf_read_values show_threads_values;
- struct annotation_options annotation_opts;
const char *pretty_printing_style;
const char *cpu_list;
const char *symbol_filter_str;
@@ -171,7 +172,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)
+ if (!ui__has_annotation() && !rep->symbol_ipc && !rep->data_type)
return 0;
if (sort__mode == SORT_MODE__BRANCH) {
@@ -541,8 +542,7 @@ static int evlist__tui_block_hists_browse(struct evlist *evlist, struct report *
evlist__for_each_entry(evlist, pos) {
ret = report__browse_block_hists(&rep->block_reports[i++].hist,
rep->min_percent, pos,
- &rep->session->header.env,
- &rep->annotation_opts);
+ &rep->session->header.env);
if (ret != 0)
return ret;
}
@@ -574,8 +574,7 @@ static int evlist__tty_browse_hists(struct evlist *evlist, struct report *rep, c
if (rep->total_cycles_mode) {
report__browse_block_hists(&rep->block_reports[i++].hist,
- rep->min_percent, pos,
- NULL, NULL);
+ rep->min_percent, pos, NULL);
continue;
}
@@ -670,7 +669,7 @@ static int report__browse_hists(struct report *rep)
}
ret = evlist__tui_browse_hists(evlist, help, NULL, rep->min_percent,
- &session->header.env, true, &rep->annotation_opts);
+ &session->header.env, true);
/*
* Usually "ret" is the last pressed key, and we only
* care if the key notifies us to switch data file.
@@ -691,10 +690,25 @@ static int report__browse_hists(struct report *rep)
static int report__collapse_hists(struct report *rep)
{
+ struct perf_session *session = rep->session;
+ struct evlist *evlist = session->evlist;
struct ui_progress prog;
struct evsel *pos;
int ret = 0;
+ /*
+ * The pipe data needs to setup hierarchy hpp formats now, because it
+ * cannot know about evsels in the data before reading the data. The
+ * normal file data saves the event (attribute) info in the header
+ * section, but pipe does not have the luxury.
+ */
+ if (perf_data__is_pipe(session->data)) {
+ if (perf_hpp__setup_hists_formats(&perf_hpp_list, evlist) < 0) {
+ ui__error("Failed to setup hierarchy output formats\n");
+ return -1;
+ }
+ }
+
ui_progress__init(&prog, rep->nr_entries, "Merging related events...");
evlist__for_each_entry(rep->session->evlist, pos) {
@@ -730,7 +744,7 @@ static int hists__resort_cb(struct hist_entry *he, void *arg)
if (rep->symbol_ipc && sym && !sym->annotate2) {
struct evsel *evsel = hists_to_evsel(he->hists);
- symbol__annotate2(&he->ms, evsel, &rep->annotation_opts, NULL);
+ symbol__annotate2(&he->ms, evsel, NULL);
}
return 0;
@@ -815,62 +829,73 @@ 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;
+struct maps__fprintf_task_args {
+ int indent;
+ FILE *fp;
+ size_t printed;
};
-static struct task *tasks_list(struct task *task, struct machine *machine)
+static int maps__fprintf_task_cb(struct map *map, void *data)
{
- struct thread *parent_thread, *thread = task->thread;
- struct task *parent_task;
-
- /* Already listed. */
- if (!list_empty(&task->list))
- return NULL;
+ struct maps__fprintf_task_args *args = data;
+ const struct dso *dso = map__dso(map);
+ u32 prot = map__prot(map);
+ int ret;
- /* Last one in the chain. */
- if (thread__ppid(thread) == -1)
- return task;
+ ret = fprintf(args->fp,
+ "%*s %" PRIx64 "-%" PRIx64 " %c%c%c%c %08" PRIx64 " %" PRIu64 " %s\n",
+ args->indent, "", map__start(map), map__end(map),
+ prot & PROT_READ ? 'r' : '-',
+ prot & PROT_WRITE ? 'w' : '-',
+ prot & PROT_EXEC ? 'x' : '-',
+ map__flags(map) ? 's' : 'p',
+ map__pgoff(map),
+ dso->id.ino, dso->name);
- parent_thread = machine__find_thread(machine, -1, thread__ppid(thread));
- if (!parent_thread)
- return ERR_PTR(-ENOENT);
+ if (ret < 0)
+ return ret;
- parent_task = thread__priv(parent_thread);
- thread__put(parent_thread);
- list_add_tail(&task->list, &parent_task->children);
- return tasks_list(parent_task, machine);
+ args->printed += ret;
+ return 0;
}
static size_t maps__fprintf_task(struct maps *maps, int indent, FILE *fp)
{
- size_t printed = 0;
- struct map_rb_node *rb_node;
+ struct maps__fprintf_task_args args = {
+ .indent = indent,
+ .fp = fp,
+ .printed = 0,
+ };
- maps__for_each_entry(maps, rb_node) {
- struct map *map = rb_node->map;
- const struct dso *dso = map__dso(map);
- u32 prot = map__prot(map);
+ maps__for_each_map(maps, maps__fprintf_task_cb, &args);
- printed += fprintf(fp, "%*s %" PRIx64 "-%" PRIx64 " %c%c%c%c %08" PRIx64 " %" PRIu64 " %s\n",
- indent, "", map__start(map), map__end(map),
- prot & PROT_READ ? 'r' : '-',
- prot & PROT_WRITE ? 'w' : '-',
- prot & PROT_EXEC ? 'x' : '-',
- map__flags(map) ? 's' : 'p',
- map__pgoff(map),
- dso->id.ino, dso->name);
- }
+ return args.printed;
+}
+
+static int thread_level(struct machine *machine, const struct thread *thread)
+{
+ struct thread *parent_thread;
+ int res;
+
+ if (thread__tid(thread) <= 0)
+ return 0;
- return printed;
+ if (thread__ppid(thread) <= 0)
+ return 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 void task__print_level(struct task *task, FILE *fp, int level)
+static void task__print_level(struct machine *machine, struct thread *thread, FILE *fp)
{
- struct thread *thread = task->thread;
- struct task *child;
+ 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, "");
@@ -878,78 +903,125 @@ static void task__print_level(struct task *task, FILE *fp, int level)
fprintf(fp, "%s\n", thread__comm_str(thread));
maps__fprintf_task(thread__maps(thread), comm_indent, fp);
-
- if (!list_empty(&task->children)) {
- list_for_each_entry(child, &task->children, list)
- task__print_level(child, fp, level + 1);
- }
}
-static int tasks_print(struct report *rep, FILE *fp)
+/*
+ * 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 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);
-
- /*
- * No locking needed while accessing machine->threads,
- * because --tasks is single threaded command.
- */
-
- /* Count all the threads. */
- for (i = 0; i < THREADS__TABLE_SIZE; i++)
- nr += machine->threads[i].nr;
-
- tasks = malloc(sizeof(*tasks) * nr);
- if (!tasks)
- return -ENOMEM;
+ 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)
@@ -1326,15 +1398,15 @@ int cmd_report(int argc, const char **argv)
"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", &report.annotation_opts.annotate_src,
+ OPT_BOOLEAN(0, "source", &annotate_opts.annotate_src,
"Interleave source code with assembly code (default)"),
- OPT_BOOLEAN(0, "asm-raw", &report.annotation_opts.show_asm_raw,
+ OPT_BOOLEAN(0, "asm-raw", &annotate_opts.show_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_STRING(0, "prefix", &report.annotation_opts.prefix, "prefix",
+ OPT_STRING(0, "prefix", &annotate_opts.prefix, "prefix",
"Add prefix to source file path names in programs (with --prefix-strip)"),
- OPT_STRING(0, "prefix-strip", &report.annotation_opts.prefix_strip, "N",
+ OPT_STRING(0, "prefix-strip", &annotate_opts.prefix_strip, "N",
"Strip first N entries of source file path name in programs (with --prefix)"),
OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period,
"Show a column with the sum of periods"),
@@ -1377,7 +1449,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",
@@ -1386,7 +1458,7 @@ int cmd_report(int argc, const char **argv)
"Time span of interest (start,stop)"),
OPT_BOOLEAN(0, "inline", &symbol_conf.inline_name,
"Show inline function"),
- OPT_CALLBACK(0, "percent-type", &report.annotation_opts, "local-period",
+ OPT_CALLBACK(0, "percent-type", &annotate_opts, "local-period",
"Set percent type local/global-period/hits",
annotate_parse_percent_type),
OPT_BOOLEAN(0, "ns", &symbol_conf.nanosecs, "Show times in nanosecs"),
@@ -1411,7 +1483,14 @@ int cmd_report(int argc, const char **argv)
if (ret < 0)
goto exit;
- annotation_options__init(&report.annotation_opts);
+ /*
+ * tasks_mode require access to exited threads to list those that are in
+ * the data file. Off-cpu events are synthesized after other events and
+ * reference exited threads.
+ */
+ symbol_conf.keep_exited_threads = true;
+
+ annotation_options__init();
ret = perf_config(report__config, &report);
if (ret)
@@ -1430,13 +1509,13 @@ int cmd_report(int argc, const char **argv)
}
if (disassembler_style) {
- report.annotation_opts.disassembler_style = strdup(disassembler_style);
- if (!report.annotation_opts.disassembler_style)
+ annotate_opts.disassembler_style = strdup(disassembler_style);
+ if (!annotate_opts.disassembler_style)
return -ENOMEM;
}
if (objdump_path) {
- report.annotation_opts.objdump_path = strdup(objdump_path);
- if (!report.annotation_opts.objdump_path)
+ annotate_opts.objdump_path = strdup(objdump_path);
+ if (!annotate_opts.objdump_path)
return -ENOMEM;
}
if (addr2line_path) {
@@ -1445,7 +1524,7 @@ int cmd_report(int argc, const char **argv)
return -ENOMEM;
}
- if (annotate_check_args(&report.annotation_opts) < 0) {
+ if (annotate_check_args() < 0) {
ret = -EINVAL;
goto exit;
}
@@ -1600,6 +1679,16 @@ repeat:
sort_order = NULL;
}
+ if (sort_order && strstr(sort_order, "type")) {
+ report.data_type = true;
+ annotate_opts.annotate_src = false;
+
+#ifndef HAVE_DWARF_GETLOCATIONS_SUPPORT
+ pr_err("Error: Data type profiling is disabled due to missing DWARF support\n");
+ goto error;
+#endif
+ }
+
if (strcmp(input_name, "-") != 0)
setup_browser(true);
else
@@ -1658,7 +1747,7 @@ repeat:
* so don't allocate extra space that won't be used in the stdio
* implementation.
*/
- if (ui__has_annotation() || report.symbol_ipc ||
+ if (ui__has_annotation() || report.symbol_ipc || report.data_type ||
report.total_cycles_mode) {
ret = symbol__annotation_init();
if (ret < 0)
@@ -1677,7 +1766,7 @@ repeat:
*/
symbol_conf.priv_size += sizeof(u32);
}
- annotation_config__init(&report.annotation_opts);
+ annotation_config__init();
}
if (symbol__init(&session->header.env) < 0)
@@ -1716,6 +1805,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);
@@ -1731,7 +1822,7 @@ error:
zstd_fini(&(session->zstd_data));
perf_session__delete(session);
exit:
- annotation_options__exit(&report.annotation_opts);
+ annotation_options__exit();
free(sort_order_help);
free(field_order_help);
return ret;