summaryrefslogtreecommitdiff
path: root/tools/perf/ui/browsers
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/ui/browsers')
-rw-r--r--tools/perf/ui/browsers/Build7
-rw-r--r--tools/perf/ui/browsers/annotate-data.c614
-rw-r--r--tools/perf/ui/browsers/annotate.c1282
-rw-r--r--tools/perf/ui/browsers/header.c128
-rw-r--r--tools/perf/ui/browsers/hists.c3603
-rw-r--r--tools/perf/ui/browsers/hists.h38
-rw-r--r--tools/perf/ui/browsers/map.c70
-rw-r--r--tools/perf/ui/browsers/map.h3
-rw-r--r--tools/perf/ui/browsers/res_sample.c96
-rw-r--r--tools/perf/ui/browsers/scripts.c453
10 files changed, 4718 insertions, 1576 deletions
diff --git a/tools/perf/ui/browsers/Build b/tools/perf/ui/browsers/Build
new file mode 100644
index 000000000000..a07489e44765
--- /dev/null
+++ b/tools/perf/ui/browsers/Build
@@ -0,0 +1,7 @@
+perf-ui-y += annotate.o
+perf-ui-y += annotate-data.o
+perf-ui-y += hists.o
+perf-ui-y += map.o
+perf-ui-y += scripts.o
+perf-ui-y += header.o
+perf-ui-y += res_sample.o
diff --git a/tools/perf/ui/browsers/annotate-data.c b/tools/perf/ui/browsers/annotate-data.c
new file mode 100644
index 000000000000..aa8c89fe2e82
--- /dev/null
+++ b/tools/perf/ui/browsers/annotate-data.c
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <inttypes.h>
+#include <string.h>
+#include <linux/zalloc.h>
+#include <sys/ttydefaults.h>
+
+#include "ui/browser.h"
+#include "ui/helpline.h"
+#include "ui/keysyms.h"
+#include "ui/ui.h"
+#include "util/annotate.h"
+#include "util/annotate-data.h"
+#include "util/evsel.h"
+#include "util/evlist.h"
+#include "util/sort.h"
+
+#define FOLDED_SIGN '+'
+#define UNFOLD_SIGN '-'
+#define NOCHLD_SIGN ' '
+
+struct browser_entry {
+ struct list_head node;
+ struct annotated_member *data;
+ struct type_hist_entry *hists;
+ struct browser_entry *parent;
+ struct list_head children;
+ int indent; /*indentation level, starts from 0 */
+ int nr_entries; /* # of visible entries: self + descendents */
+ bool folded; /* only can be false when it has children */
+};
+
+struct annotated_data_browser {
+ struct ui_browser b;
+ struct list_head entries;
+ struct browser_entry *curr;
+ int nr_events;
+};
+
+static struct annotated_data_browser *get_browser(struct ui_browser *uib)
+{
+ return container_of(uib, struct annotated_data_browser, b);
+}
+
+static void update_hist_entry(struct type_hist_entry *dst,
+ struct type_hist_entry *src)
+{
+ dst->nr_samples += src->nr_samples;
+ dst->period += src->period;
+}
+
+static int get_member_overhead(struct annotated_data_type *adt,
+ struct browser_entry *entry,
+ struct evsel *leader)
+{
+ struct annotated_member *member = entry->data;
+ int i, k;
+
+ for (i = 0; i < member->size; i++) {
+ struct type_hist *h;
+ struct evsel *evsel;
+ int offset = member->offset + i;
+
+ k = 0;
+ for_each_group_evsel(evsel, leader) {
+ if (symbol_conf.skip_empty &&
+ evsel__hists(evsel)->stats.nr_samples == 0)
+ continue;
+
+ h = adt->histograms[evsel->core.idx];
+ update_hist_entry(&entry->hists[k++], &h->addr[offset]);
+ }
+ }
+ return 0;
+}
+
+static int add_child_entries(struct annotated_data_browser *browser,
+ struct browser_entry *parent,
+ struct annotated_data_type *adt,
+ struct annotated_member *member,
+ struct evsel *evsel, int indent)
+{
+ struct annotated_member *pos;
+ struct browser_entry *entry;
+ struct list_head *parent_list;
+
+ entry = zalloc(sizeof(*entry));
+ if (entry == NULL)
+ return -1;
+
+ entry->hists = calloc(browser->nr_events, sizeof(*entry->hists));
+ if (entry->hists == NULL) {
+ free(entry);
+ return -1;
+ }
+
+ entry->data = member;
+ entry->parent = parent;
+ entry->indent = indent;
+ if (get_member_overhead(adt, entry, evsel) < 0) {
+ free(entry);
+ return -1;
+ }
+
+ INIT_LIST_HEAD(&entry->children);
+ if (parent)
+ parent_list = &parent->children;
+ else
+ parent_list = &browser->entries;
+
+ list_add_tail(&entry->node, parent_list);
+
+ list_for_each_entry(pos, &member->children, node) {
+ int nr = add_child_entries(browser, entry, adt, pos, evsel,
+ indent + 1);
+ if (nr < 0)
+ return nr;
+ }
+
+ /* add an entry for the closing bracket ("}") */
+ if (!list_empty(&member->children)) {
+ struct browser_entry *bracket;
+
+ bracket = zalloc(sizeof(*bracket));
+ if (bracket == NULL)
+ return -1;
+
+ bracket->indent = indent;
+ bracket->parent = entry;
+ bracket->folded = true;
+ bracket->nr_entries = 1;
+
+ INIT_LIST_HEAD(&bracket->children);
+ list_add_tail(&bracket->node, &entry->children);
+ }
+
+ /* fold child entries by default */
+ entry->folded = true;
+ entry->nr_entries = 1;
+ return 0;
+}
+
+static u32 count_visible_entries(struct annotated_data_browser *browser)
+{
+ int nr = 0;
+ struct browser_entry *entry;
+
+ list_for_each_entry(entry, &browser->entries, node)
+ nr += entry->nr_entries;
+
+ return nr;
+}
+
+static int annotated_data_browser__collect_entries(struct annotated_data_browser *browser)
+{
+ struct hist_entry *he = browser->b.priv;
+ struct annotated_data_type *adt = he->mem_type;
+ struct evsel *evsel = hists_to_evsel(he->hists);
+
+ INIT_LIST_HEAD(&browser->entries);
+
+ add_child_entries(browser, /*parent=*/NULL, adt, &adt->self, evsel,
+ /*indent=*/0);
+
+ browser->b.entries = &browser->entries;
+ browser->b.nr_entries = count_visible_entries(browser);
+ return 0;
+}
+
+static void annotated_data_browser__delete_entries(struct annotated_data_browser *browser)
+{
+ struct browser_entry *pos, *tmp;
+
+ list_for_each_entry_safe(pos, tmp, &browser->entries, node) {
+ list_del_init(&pos->node);
+ zfree(&pos->hists);
+ free(pos);
+ }
+}
+
+static struct browser_entry *get_first_child(struct browser_entry *entry)
+{
+ if (list_empty(&entry->children))
+ return NULL;
+
+ return list_first_entry(&entry->children, struct browser_entry, node);
+}
+
+static struct browser_entry *get_last_child(struct browser_entry *entry)
+{
+ if (list_empty(&entry->children))
+ return NULL;
+
+ return list_last_entry(&entry->children, struct browser_entry, node);
+}
+
+static bool is_first_child(struct browser_entry *entry)
+{
+ /* This will be checked in a different way */
+ if (entry->parent == NULL)
+ return false;
+
+ return get_first_child(entry->parent) == entry;
+}
+
+static bool is_last_child(struct browser_entry *entry)
+{
+ /* This will be checked in a different way */
+ if (entry->parent == NULL)
+ return false;
+
+ return get_last_child(entry->parent) == entry;
+}
+
+static struct browser_entry *browser__prev_entry(struct ui_browser *uib,
+ struct browser_entry *entry)
+{
+ struct annotated_data_browser *browser = get_browser(uib);
+ struct browser_entry *first;
+
+ first = list_first_entry(&browser->entries, struct browser_entry, node);
+
+ while (entry != first) {
+ if (is_first_child(entry))
+ entry = entry->parent;
+ else {
+ entry = list_prev_entry(entry, node);
+ while (!entry->folded)
+ entry = get_last_child(entry);
+ }
+
+ if (!uib->filter || !uib->filter(uib, &entry->node))
+ return entry;
+ }
+ return first;
+}
+
+static struct browser_entry *browser__next_entry(struct ui_browser *uib,
+ struct browser_entry *entry)
+{
+ struct annotated_data_browser *browser = get_browser(uib);
+ struct browser_entry *last;
+
+ last = list_last_entry(&browser->entries, struct browser_entry, node);
+ while (!last->folded)
+ last = get_last_child(last);
+
+ while (entry != last) {
+ if (!entry->folded)
+ entry = get_first_child(entry);
+ else {
+ while (is_last_child(entry))
+ entry = entry->parent;
+
+ entry = list_next_entry(entry, node);
+ }
+
+ if (!uib->filter || !uib->filter(uib, &entry->node))
+ return entry;
+ }
+ return last;
+}
+
+static void browser__seek(struct ui_browser *uib, off_t offset, int whence)
+{
+ struct annotated_data_browser *browser = get_browser(uib);
+ struct browser_entry *entry;
+
+ if (uib->nr_entries == 0)
+ return;
+
+ switch (whence) {
+ case SEEK_SET:
+ entry = list_first_entry(&browser->entries, typeof(*entry), node);
+ if (uib->filter && uib->filter(uib, &entry->node))
+ entry = browser__next_entry(uib, entry);
+ break;
+ case SEEK_CUR:
+ entry = list_entry(uib->top, typeof(*entry), node);
+ break;
+ case SEEK_END:
+ entry = list_last_entry(&browser->entries, typeof(*entry), node);
+ while (!entry->folded)
+ entry = get_last_child(entry);
+ if (uib->filter && uib->filter(uib, &entry->node))
+ entry = browser__prev_entry(uib, entry);
+ break;
+ default:
+ return;
+ }
+
+ assert(entry != NULL);
+
+ if (offset > 0) {
+ while (offset-- != 0)
+ entry = browser__next_entry(uib, entry);
+ } else {
+ while (offset++ != 0)
+ entry = browser__prev_entry(uib, entry);
+ }
+
+ uib->top = &entry->node;
+}
+
+static unsigned int browser__refresh(struct ui_browser *uib)
+{
+ struct annotated_data_browser *browser = get_browser(uib);
+ struct browser_entry *entry, *next;
+ int row = 0;
+
+ if (uib->top == NULL || uib->top == uib->entries)
+ browser__seek(uib, SEEK_SET, 0);
+
+ entry = list_entry(uib->top, typeof(*entry), node);
+
+ while (true) {
+ if (!uib->filter || !uib->filter(uib, &entry->node)) {
+ ui_browser__gotorc(uib, row, 0);
+ uib->write(uib, entry, row);
+ if (uib->top_idx + row == uib->index)
+ browser->curr = entry;
+ if (++row == uib->rows)
+ break;
+ }
+ next = browser__next_entry(uib, entry);
+ if (next == entry)
+ break;
+
+ entry = next;
+ }
+
+ return row;
+}
+
+static int browser__show(struct ui_browser *uib)
+{
+ struct hist_entry *he = uib->priv;
+ struct annotated_data_type *adt = he->mem_type;
+ struct annotated_data_browser *browser = get_browser(uib);
+ const char *help = "Press 'h' for help on key bindings";
+ char title[256];
+
+ snprintf(title, sizeof(title), "Annotate type: '%s' (%d samples)",
+ adt->self.type_name, he->stat.nr_events);
+
+ if (ui_browser__show(uib, title, help) < 0)
+ return -1;
+
+ /* second line header */
+ ui_browser__gotorc_title(uib, 0, 0);
+ ui_browser__set_color(uib, HE_COLORSET_ROOT);
+
+ if (symbol_conf.show_total_period)
+ strcpy(title, "Period");
+ else if (symbol_conf.show_nr_samples)
+ strcpy(title, "Samples");
+ else
+ strcpy(title, "Percent");
+
+ ui_browser__printf(uib, "%*s %10s %10s %10s %s",
+ 2 + 11 * (browser->nr_events - 1), "",
+ title, "Offset", "Size", "Field");
+ ui_browser__write_nstring(uib, "", uib->width);
+ return 0;
+}
+
+static void browser__write_overhead(struct ui_browser *uib,
+ struct type_hist *total,
+ struct type_hist_entry *hist, int row)
+{
+ u64 period = hist->period;
+ double percent = total->period ? (100.0 * period / total->period) : 0;
+ bool current = ui_browser__is_current_entry(uib, row);
+ int nr_samples = 0;
+
+ ui_browser__set_percent_color(uib, percent, current);
+
+ if (symbol_conf.show_total_period)
+ ui_browser__printf(uib, " %10" PRIu64, period);
+ else if (symbol_conf.show_nr_samples)
+ ui_browser__printf(uib, " %10d", nr_samples);
+ else
+ ui_browser__printf(uib, " %10.2f", percent);
+
+ ui_browser__set_percent_color(uib, 0, current);
+}
+
+static void browser__write(struct ui_browser *uib, void *entry, int row)
+{
+ struct annotated_data_browser *browser = get_browser(uib);
+ struct browser_entry *be = entry;
+ struct annotated_member *member = be->data;
+ struct hist_entry *he = uib->priv;
+ struct annotated_data_type *adt = he->mem_type;
+ struct evsel *leader = hists_to_evsel(he->hists);
+ struct evsel *evsel;
+ int idx = 0;
+ bool current = ui_browser__is_current_entry(uib, row);
+
+ if (member == NULL) {
+ /* print the closing bracket */
+ ui_browser__set_percent_color(uib, 0, current);
+ ui_browser__printf(uib, "%c ", NOCHLD_SIGN);
+ ui_browser__write_nstring(uib, "", 11 * browser->nr_events);
+ ui_browser__printf(uib, " %10s %10s %*s};",
+ "", "", be->indent * 4, "");
+ ui_browser__write_nstring(uib, "", uib->width);
+ return;
+ }
+
+ ui_browser__set_percent_color(uib, 0, current);
+
+ if (!list_empty(&be->children))
+ ui_browser__printf(uib, "%c ", be->folded ? FOLDED_SIGN : UNFOLD_SIGN);
+ else
+ ui_browser__printf(uib, "%c ", NOCHLD_SIGN);
+
+ /* print the number */
+ for_each_group_evsel(evsel, leader) {
+ struct type_hist *h = adt->histograms[evsel->core.idx];
+
+ if (symbol_conf.skip_empty &&
+ evsel__hists(evsel)->stats.nr_samples == 0)
+ continue;
+
+ browser__write_overhead(uib, h, &be->hists[idx++], row);
+ }
+
+ /* print type info */
+ if (be->indent == 0 && !member->var_name) {
+ ui_browser__printf(uib, " %#10x %#10x %s%s",
+ member->offset, member->size,
+ member->type_name,
+ list_empty(&member->children) || be->folded? ";" : " {");
+ } else {
+ ui_browser__printf(uib, " %#10x %#10x %*s%s\t%s%s",
+ member->offset, member->size,
+ be->indent * 4, "", member->type_name,
+ member->var_name ?: "",
+ list_empty(&member->children) || be->folded ? ";" : " {");
+ }
+ /* fill the rest */
+ ui_browser__write_nstring(uib, "", uib->width);
+}
+
+static void annotated_data_browser__fold(struct annotated_data_browser *browser,
+ struct browser_entry *entry,
+ bool recursive)
+{
+ struct browser_entry *child;
+
+ if (list_empty(&entry->children))
+ return;
+ if (entry->folded && !recursive)
+ return;
+
+ if (recursive) {
+ list_for_each_entry(child, &entry->children, node)
+ annotated_data_browser__fold(browser, child, true);
+ }
+
+ entry->nr_entries = 1;
+ entry->folded = true;
+}
+
+static void annotated_data_browser__unfold(struct annotated_data_browser *browser,
+ struct browser_entry *entry,
+ bool recursive)
+{
+ struct browser_entry *child;
+ int nr_entries;
+
+ if (list_empty(&entry->children))
+ return;
+ if (!entry->folded && !recursive)
+ return;
+
+ nr_entries = 1; /* for self */
+ list_for_each_entry(child, &entry->children, node) {
+ if (recursive)
+ annotated_data_browser__unfold(browser, child, true);
+
+ nr_entries += child->nr_entries;
+ }
+
+ entry->nr_entries = nr_entries;
+ entry->folded = false;
+}
+
+static void annotated_data_browser__toggle_fold(struct annotated_data_browser *browser,
+ bool recursive)
+{
+ struct browser_entry *curr = browser->curr;
+ struct browser_entry *parent;
+
+ parent = curr->parent;
+ while (parent) {
+ parent->nr_entries -= curr->nr_entries;
+ parent = parent->parent;
+ }
+ browser->b.nr_entries -= curr->nr_entries;
+
+ if (curr->folded)
+ annotated_data_browser__unfold(browser, curr, recursive);
+ else
+ annotated_data_browser__fold(browser, curr, recursive);
+
+ parent = curr->parent;
+ while (parent) {
+ parent->nr_entries += curr->nr_entries;
+ parent = parent->parent;
+ }
+ browser->b.nr_entries += curr->nr_entries;
+
+ assert(browser->b.nr_entries == count_visible_entries(browser));
+}
+
+static int annotated_data_browser__run(struct annotated_data_browser *browser,
+ struct evsel *evsel __maybe_unused,
+ struct hist_browser_timer *hbt)
+{
+ int delay_secs = hbt ? hbt->refresh : 0;
+ int key;
+
+ if (browser__show(&browser->b) < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(&browser->b, delay_secs);
+
+ switch (key) {
+ case K_TIMER:
+ if (hbt)
+ hbt->timer(hbt->arg);
+ continue;
+ case K_F1:
+ case 'h':
+ ui_browser__help_window(&browser->b,
+ "UP/DOWN/PGUP\n"
+ "PGDN/SPACE Navigate\n"
+ "</> Move to prev/next symbol\n"
+ "e Expand/Collapse current entry\n"
+ "E Expand/Collapse all children of the current\n"
+ "q/ESC/CTRL+C Exit\n\n");
+ continue;
+ case 'e':
+ annotated_data_browser__toggle_fold(browser,
+ /*recursive=*/false);
+ break;
+ case 'E':
+ annotated_data_browser__toggle_fold(browser,
+ /*recursive=*/true);
+ break;
+ case K_LEFT:
+ case '<':
+ case '>':
+ case K_ESC:
+ case 'q':
+ case CTRL('c'):
+ goto out;
+ default:
+ ui_browser__warn_unhandled_hotkey(&browser->b, key, delay_secs, ", use 'h'/F1 to see actions");
+ continue;
+ }
+ }
+out:
+ ui_browser__hide(&browser->b);
+ return key;
+}
+
+int hist_entry__annotate_data_tui(struct hist_entry *he, struct evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ struct annotated_data_browser browser = {
+ .b = {
+ .refresh = browser__refresh,
+ .seek = browser__seek,
+ .write = browser__write,
+ .priv = he,
+ .extra_title_lines = 1,
+ },
+ .nr_events = 1,
+ };
+ int ret;
+
+ ui_helpline__push("Press ESC to exit");
+
+ if (evsel__is_group_event(evsel)) {
+ struct evsel *pos;
+ int nr = 0;
+
+ for_each_group_evsel(pos, evsel) {
+ if (!symbol_conf.skip_empty ||
+ evsel__hists(pos)->stats.nr_samples)
+ nr++;
+ }
+ browser.nr_events = nr;
+ }
+
+ ret = annotated_data_browser__collect_entries(&browser);
+ if (ret < 0)
+ goto out;
+
+ /* To get the top and current entry */
+ browser__refresh(&browser.b);
+ /* Show the first-level child entries by default */
+ annotated_data_browser__toggle_fold(&browser, /*recursive=*/false);
+
+ ret = annotated_data_browser__run(&browser, evsel, hbt);
+
+out:
+ annotated_data_browser__delete_entries(&browser);
+
+ return ret;
+}
diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c
index cc64d3f7fc36..36aca8d6d003 100644
--- a/tools/perf/ui/browsers/annotate.c
+++ b/tools/perf/ui/browsers/annotate.c
@@ -1,302 +1,307 @@
-#include "../../util/util.h"
+// SPDX-License-Identifier: GPL-2.0
#include "../browser.h"
#include "../helpline.h"
-#include "../libslang.h"
#include "../ui.h"
-#include "../util.h"
#include "../../util/annotate.h"
+#include "../../util/debug.h"
+#include "../../util/debuginfo.h"
+#include "../../util/dso.h"
+#include "../../util/hashmap.h"
#include "../../util/hist.h"
#include "../../util/sort.h"
+#include "../../util/map.h"
+#include "../../util/mutex.h"
#include "../../util/symbol.h"
#include "../../util/evsel.h"
-#include <pthread.h>
+#include "../../util/evlist.h"
+#include "../../util/thread.h"
+#include <inttypes.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/zalloc.h>
+#include <sys/ttydefaults.h>
+#include <asm/bug.h>
+
+struct arch;
-struct browser_disasm_line {
- struct rb_node rb_node;
- u32 idx;
- int idx_asm;
- int jump_sources;
+struct annotate_browser {
+ struct ui_browser b;
+ struct rb_root entries;
+ struct rb_node *curr_hot;
+ struct annotation_line *selection;
+ struct arch *arch;
/*
- * actual length of this array is saved on the nr_events field
- * of the struct annotate_browser
+ * perf top can delete hist_entry anytime. Callers should make sure
+ * its lifetime.
*/
- double percent[1];
+ struct hist_entry *he;
+ struct debuginfo *dbg;
+ struct evsel *evsel;
+ struct hashmap *type_hash;
+ bool searching_backwards;
+ char search_bf[128];
};
-static struct annotate_browser_opt {
- bool hide_src_code,
- use_offset,
- jump_arrows,
- show_nr_jumps;
-} annotate_browser__opts = {
- .use_offset = true,
- .jump_arrows = true,
-};
+/* A copy of target hist_entry for perf top. */
+static struct hist_entry annotate_he;
-struct annotate_browser {
- struct ui_browser b;
- struct rb_root entries;
- struct rb_node *curr_hot;
- struct disasm_line *selection;
- struct disasm_line **offsets;
- int nr_events;
- u64 start;
- int nr_asm_entries;
- int nr_entries;
- int max_jump_sources;
- int nr_jumps;
- bool searching_backwards;
- u8 addr_width;
- u8 jumps_width;
- u8 target_width;
- u8 min_addr_width;
- u8 max_addr_width;
- char search_bf[128];
-};
+static size_t type_hash(long key, void *ctx __maybe_unused)
+{
+ return key;
+}
-static inline struct browser_disasm_line *disasm_line__browser(struct disasm_line *dl)
+static bool type_equal(long key1, long key2, void *ctx __maybe_unused)
{
- return (struct browser_disasm_line *)(dl + 1);
+ return key1 == key2;
}
-static bool disasm_line__filter(struct ui_browser *browser __maybe_unused,
- void *entry)
+static inline struct annotation *browser__annotation(struct ui_browser *browser)
{
- if (annotate_browser__opts.hide_src_code) {
- struct disasm_line *dl = list_entry(entry, struct disasm_line, node);
- return dl->offset == -1;
- }
+ struct map_symbol *ms = browser->priv;
+ return symbol__annotation(ms->sym);
+}
- return false;
+static bool disasm_line__filter(struct ui_browser *browser __maybe_unused, void *entry)
+{
+ struct annotation_line *al = list_entry(entry, struct annotation_line, node);
+ return annotation_line__filter(al);
}
-static int annotate_browser__jumps_percent_color(struct annotate_browser *browser,
- int nr, bool current)
+static int ui_browser__jumps_percent_color(struct ui_browser *browser, int nr, bool current)
{
- if (current && (!browser->b.use_navkeypressed || browser->b.navkeypressed))
+ struct annotation *notes = browser__annotation(browser);
+
+ if (current && (!browser->use_navkeypressed || browser->navkeypressed))
return HE_COLORSET_SELECTED;
- if (nr == browser->max_jump_sources)
+ if (nr == notes->src->max_jump_sources)
return HE_COLORSET_TOP;
if (nr > 1)
return HE_COLORSET_MEDIUM;
return HE_COLORSET_NORMAL;
}
-static int annotate_browser__set_jumps_percent_color(struct annotate_browser *browser,
- int nr, bool current)
+static int ui_browser__set_jumps_percent_color(void *browser, int nr, bool current)
+{
+ int color = ui_browser__jumps_percent_color(browser, nr, current);
+ return ui_browser__set_color(browser, color);
+}
+
+static int annotate_browser__set_color(void *browser, int color)
+{
+ return ui_browser__set_color(browser, color);
+}
+
+static void annotate_browser__write_graph(void *browser, int graph)
{
- int color = annotate_browser__jumps_percent_color(browser, nr, current);
- return ui_browser__set_color(&browser->b, color);
+ ui_browser__write_graph(browser, graph);
+}
+
+static void annotate_browser__set_percent_color(void *browser, double percent, bool current)
+{
+ ui_browser__set_percent_color(browser, percent, current);
+}
+
+static void annotate_browser__printf(void *browser, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ ui_browser__vprintf(browser, fmt, args);
+ va_end(args);
}
static void annotate_browser__write(struct ui_browser *browser, void *entry, int row)
{
struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
- struct disasm_line *dl = list_entry(entry, struct disasm_line, node);
- struct browser_disasm_line *bdl = disasm_line__browser(dl);
- bool current_entry = ui_browser__is_current_entry(browser, row);
- bool change_color = (!annotate_browser__opts.hide_src_code &&
- (!current_entry || (browser->use_navkeypressed &&
- !browser->navkeypressed)));
- int width = browser->width, printed;
- int i, pcnt_width = 7 * ab->nr_events;
- double percent_max = 0.0;
- char bf[256];
-
- for (i = 0; i < ab->nr_events; i++) {
- if (bdl->percent[i] > percent_max)
- percent_max = bdl->percent[i];
- }
-
- if (dl->offset != -1 && percent_max != 0.0) {
- for (i = 0; i < ab->nr_events; i++) {
- ui_browser__set_percent_color(browser, bdl->percent[i],
- current_entry);
- slsmg_printf("%6.2f ", bdl->percent[i]);
- }
- } else {
- ui_browser__set_percent_color(browser, 0, current_entry);
- slsmg_write_nstring(" ", pcnt_width);
- }
-
- SLsmg_write_char(' ');
+ struct annotation *notes = browser__annotation(browser);
+ struct annotation_line *al = list_entry(entry, struct annotation_line, node);
+ const bool is_current_entry = ui_browser__is_current_entry(browser, row);
+ struct annotation_write_ops ops = {
+ .first_line = row == 0,
+ .current_entry = is_current_entry,
+ .change_color = (!annotate_opts.hide_src_code &&
+ (!is_current_entry ||
+ (browser->use_navkeypressed &&
+ !browser->navkeypressed))),
+ .width = browser->width,
+ .obj = browser,
+ .set_color = annotate_browser__set_color,
+ .set_percent_color = annotate_browser__set_percent_color,
+ .set_jumps_percent_color = ui_browser__set_jumps_percent_color,
+ .printf = annotate_browser__printf,
+ .write_graph = annotate_browser__write_graph,
+ };
+ struct annotation_print_data apd = {
+ .he = ab->he,
+ .arch = ab->arch,
+ .evsel = ab->evsel,
+ .dbg = ab->dbg,
+ };
/* The scroll bar isn't being used */
if (!browser->navkeypressed)
- width += 1;
-
- if (!*dl->line)
- slsmg_write_nstring(" ", width - pcnt_width);
- else if (dl->offset == -1) {
- printed = scnprintf(bf, sizeof(bf), "%*s ",
- ab->addr_width, " ");
- slsmg_write_nstring(bf, printed);
- slsmg_write_nstring(dl->line, width - printed - pcnt_width + 1);
- } else {
- u64 addr = dl->offset;
- int color = -1;
-
- if (!annotate_browser__opts.use_offset)
- addr += ab->start;
-
- if (!annotate_browser__opts.use_offset) {
- printed = scnprintf(bf, sizeof(bf), "%" PRIx64 ": ", addr);
- } else {
- if (bdl->jump_sources) {
- if (annotate_browser__opts.show_nr_jumps) {
- int prev;
- printed = scnprintf(bf, sizeof(bf), "%*d ",
- ab->jumps_width,
- bdl->jump_sources);
- prev = annotate_browser__set_jumps_percent_color(ab, bdl->jump_sources,
- current_entry);
- slsmg_write_nstring(bf, printed);
- ui_browser__set_color(browser, prev);
- }
-
- printed = scnprintf(bf, sizeof(bf), "%*" PRIx64 ": ",
- ab->target_width, addr);
- } else {
- printed = scnprintf(bf, sizeof(bf), "%*s ",
- ab->addr_width, " ");
- }
- }
+ ops.width += 1;
- if (change_color)
- color = ui_browser__set_color(browser, HE_COLORSET_ADDR);
- slsmg_write_nstring(bf, printed);
- if (change_color)
- ui_browser__set_color(browser, color);
- if (dl->ins && dl->ins->ops->scnprintf) {
- if (ins__is_jump(dl->ins)) {
- bool fwd = dl->ops.target.offset > (u64)dl->offset;
-
- ui_browser__write_graph(browser, fwd ? SLSMG_DARROW_CHAR :
- SLSMG_UARROW_CHAR);
- SLsmg_write_char(' ');
- } else if (ins__is_call(dl->ins)) {
- ui_browser__write_graph(browser, SLSMG_RARROW_CHAR);
- SLsmg_write_char(' ');
- } else {
- slsmg_write_nstring(" ", 2);
- }
- } else {
- if (strcmp(dl->name, "retq")) {
- slsmg_write_nstring(" ", 2);
- } else {
- ui_browser__write_graph(browser, SLSMG_LARROW_CHAR);
- SLsmg_write_char(' ');
- }
- }
+ if (!IS_ERR_OR_NULL(ab->type_hash))
+ apd.type_hash = ab->type_hash;
- disasm_line__scnprintf(dl, bf, sizeof(bf), !annotate_browser__opts.use_offset);
- slsmg_write_nstring(bf, width - pcnt_width - 3 - printed);
- }
+ annotation_line__write(al, notes, &ops, &apd);
- if (current_entry)
- ab->selection = dl;
+ if (ops.current_entry)
+ ab->selection = al;
}
-static bool disasm_line__is_valid_jump(struct disasm_line *dl, struct symbol *sym)
+static int is_fused(struct annotate_browser *ab, struct disasm_line *cursor)
{
- if (!dl || !dl->ins || !ins__is_jump(dl->ins)
- || !disasm_line__has_offset(dl)
- || dl->ops.target.offset >= symbol__size(sym))
- return false;
+ struct disasm_line *pos = list_prev_entry(cursor, al.node);
+ const char *name;
+ int diff = 1;
- return true;
+ while (pos && pos->al.offset == -1) {
+ pos = list_prev_entry(pos, al.node);
+ if (!annotate_opts.hide_src_code)
+ diff++;
+ }
+
+ if (!pos)
+ return 0;
+
+ if (ins__is_lock(&pos->ins))
+ name = pos->ops.locked.ins.name;
+ else
+ name = pos->ins.name;
+
+ if (!name || !cursor->ins.name)
+ return 0;
+
+ if (ins__is_fused(ab->arch, name, cursor->ins.name))
+ return diff;
+ return 0;
}
static void annotate_browser__draw_current_jump(struct ui_browser *browser)
{
struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
- struct disasm_line *cursor = ab->selection, *target;
- struct browser_disasm_line *btarget, *bcursor;
+ struct disasm_line *cursor = disasm_line(ab->selection);
+ struct annotation_line *target;
unsigned int from, to;
struct map_symbol *ms = ab->b.priv;
struct symbol *sym = ms->sym;
- u8 pcnt_width = 7;
+ struct annotation *notes = symbol__annotation(sym);
+ u8 pcnt_width = annotation__pcnt_width(notes);
+ u8 cntr_width = annotation__br_cntr_width();
+ int width;
+ int diff = 0;
/* PLT symbols contain external offsets */
if (strstr(sym->name, "@plt"))
return;
- if (!disasm_line__is_valid_jump(cursor, sym))
+ if (!disasm_line__is_valid_local_jump(cursor, sym))
return;
- target = ab->offsets[cursor->ops.target.offset];
- if (!target)
+ /*
+ * This first was seen with a gcc function, _cpp_lex_token, that
+ * has the usual jumps:
+ *
+ * │1159e6c: ↓ jne 115aa32 <_cpp_lex_token@@Base+0xf92>
+ *
+ * I.e. jumps to a label inside that function (_cpp_lex_token), and
+ * those works, but also this kind:
+ *
+ * │1159e8b: ↓ jne c469be <cpp_named_operator2name@@Base+0xa72>
+ *
+ * I.e. jumps to another function, outside _cpp_lex_token, which
+ * are not being correctly handled generating as a side effect references
+ * to ab->offset[] entries that are set to NULL, so to make this code
+ * more robust, check that here.
+ *
+ * A proper fix for will be put in place, looking at the function
+ * name right after the '<' token and probably treating this like a
+ * 'call' instruction.
+ */
+ target = annotated_source__get_line(notes->src, cursor->ops.target.offset);
+ if (target == NULL) {
+ ui_helpline__printf("WARN: jump target inconsistency, press 'o', notes->offsets[%#x] = NULL\n",
+ cursor->ops.target.offset);
return;
+ }
- bcursor = disasm_line__browser(cursor);
- btarget = disasm_line__browser(target);
-
- if (annotate_browser__opts.hide_src_code) {
- from = bcursor->idx_asm;
- to = btarget->idx_asm;
+ if (annotate_opts.hide_src_code) {
+ from = cursor->al.idx_asm;
+ to = target->idx_asm;
} else {
- from = (u64)bcursor->idx;
- to = (u64)btarget->idx;
+ from = (u64)cursor->al.idx;
+ to = (u64)target->idx;
}
- pcnt_width *= ab->nr_events;
+ width = annotation__cycles_width(notes);
- ui_browser__set_color(browser, HE_COLORSET_CODE);
- __ui_browser__line_arrow(browser, pcnt_width + 2 + ab->addr_width,
+ ui_browser__set_color(browser, HE_COLORSET_JUMP_ARROWS);
+ __ui_browser__line_arrow(browser,
+ pcnt_width + 2 + notes->src->widths.addr + width + cntr_width,
from, to);
+
+ diff = is_fused(ab, cursor);
+ if (diff > 0) {
+ ui_browser__mark_fused(browser,
+ pcnt_width + 3 + notes->src->widths.addr + width + cntr_width,
+ from - diff, diff, to > from);
+ }
}
static unsigned int annotate_browser__refresh(struct ui_browser *browser)
{
- struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
+ struct annotation *notes = browser__annotation(browser);
int ret = ui_browser__list_head_refresh(browser);
- int pcnt_width;
+ int pcnt_width = annotation__pcnt_width(notes);
- pcnt_width = 7 * ab->nr_events;
-
- if (annotate_browser__opts.jump_arrows)
+ if (annotate_opts.jump_arrows)
annotate_browser__draw_current_jump(browser);
ui_browser__set_color(browser, HE_COLORSET_NORMAL);
- __ui_browser__vline(browser, pcnt_width, 0, browser->height - 1);
+ __ui_browser__vline(browser, pcnt_width, 0, browser->rows - 1);
return ret;
}
-static int disasm__cmp(struct browser_disasm_line *a,
- struct browser_disasm_line *b, int nr_pcnt)
+static double disasm__cmp(struct annotation_line *a, struct annotation_line *b,
+ int percent_type)
{
int i;
- for (i = 0; i < nr_pcnt; i++) {
- if (a->percent[i] == b->percent[i])
+ for (i = 0; i < a->data_nr; i++) {
+ if (a->data[i].percent[percent_type] == b->data[i].percent[percent_type])
continue;
- return a->percent[i] < b->percent[i];
+ return a->data[i].percent[percent_type] -
+ b->data[i].percent[percent_type];
}
return 0;
}
-static void disasm_rb_tree__insert(struct rb_root *root, struct browser_disasm_line *bdl,
- int nr_events)
+static void disasm_rb_tree__insert(struct annotate_browser *browser,
+ struct annotation_line *al)
{
+ struct rb_root *root = &browser->entries;
struct rb_node **p = &root->rb_node;
struct rb_node *parent = NULL;
- struct browser_disasm_line *l;
+ struct annotation_line *l;
while (*p != NULL) {
parent = *p;
- l = rb_entry(parent, struct browser_disasm_line, rb_node);
+ l = rb_entry(parent, struct annotation_line, rb_node);
- if (disasm__cmp(bdl, l, nr_events))
+ if (disasm__cmp(al, l, annotate_opts.percent_type) < 0)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
- rb_link_node(&bdl->rb_node, parent, p);
- rb_insert_color(&bdl->rb_node, root);
+ rb_link_node(&al->rb_node, parent, p);
+ rb_insert_color(&al->rb_node, root);
}
static void annotate_browser__set_top(struct annotate_browser *browser,
- struct disasm_line *pos, u32 idx)
+ struct annotation_line *pos, u32 idx)
{
unsigned back;
@@ -305,9 +310,9 @@ static void annotate_browser__set_top(struct annotate_browser *browser,
browser->b.top_idx = browser->b.index = idx;
while (browser->b.top_idx != 0 && back != 0) {
- pos = list_entry(pos->node.prev, struct disasm_line, node);
+ pos = list_entry(pos->node.prev, struct annotation_line, node);
- if (disasm_line__filter(&browser->b, &pos->node))
+ if (annotation_line__filter(pos))
continue;
--browser->b.top_idx;
@@ -321,147 +326,293 @@ static void annotate_browser__set_top(struct annotate_browser *browser,
static void annotate_browser__set_rb_top(struct annotate_browser *browser,
struct rb_node *nd)
{
- struct browser_disasm_line *bpos;
- struct disasm_line *pos;
- u32 idx;
+ struct annotation_line * pos = rb_entry(nd, struct annotation_line, rb_node);
+ u32 idx = pos->idx;
- bpos = rb_entry(nd, struct browser_disasm_line, rb_node);
- pos = ((struct disasm_line *)bpos) - 1;
- idx = bpos->idx;
- if (annotate_browser__opts.hide_src_code)
- idx = bpos->idx_asm;
+ if (annotate_opts.hide_src_code)
+ idx = pos->idx_asm;
annotate_browser__set_top(browser, pos, idx);
browser->curr_hot = nd;
}
static void annotate_browser__calc_percent(struct annotate_browser *browser,
- struct perf_evsel *evsel)
+ struct evsel *evsel)
{
struct map_symbol *ms = browser->b.priv;
struct symbol *sym = ms->sym;
struct annotation *notes = symbol__annotation(sym);
- struct disasm_line *pos, *next;
- s64 len = symbol__size(sym);
+ struct disasm_line *pos;
browser->entries = RB_ROOT;
- pthread_mutex_lock(&notes->lock);
+ annotation__lock(notes);
+
+ symbol__calc_percent(sym, evsel);
- list_for_each_entry(pos, &notes->src->source, node) {
- struct browser_disasm_line *bpos = disasm_line__browser(pos);
- const char *path = NULL;
+ list_for_each_entry(pos, &notes->src->source, al.node) {
double max_percent = 0.0;
int i;
- if (pos->offset == -1) {
- RB_CLEAR_NODE(&bpos->rb_node);
+ if (pos->al.offset == -1) {
+ RB_CLEAR_NODE(&pos->al.rb_node);
continue;
}
- next = disasm__get_next_ip_line(&notes->src->source, pos);
+ for (i = 0; i < pos->al.data_nr; i++) {
+ double percent;
- for (i = 0; i < browser->nr_events; i++) {
- bpos->percent[i] = disasm__calc_percent(notes,
- evsel->idx + i,
- pos->offset,
- next ? next->offset : len,
- &path);
+ percent = annotation_data__percent(&pos->al.data[i],
+ annotate_opts.percent_type);
- if (max_percent < bpos->percent[i])
- max_percent = bpos->percent[i];
+ if (max_percent < percent)
+ max_percent = percent;
}
- if (max_percent < 0.01) {
- RB_CLEAR_NODE(&bpos->rb_node);
+ if (max_percent < 0.01 && (!pos->al.cycles || pos->al.cycles->ipc == 0)) {
+ RB_CLEAR_NODE(&pos->al.rb_node);
continue;
}
- disasm_rb_tree__insert(&browser->entries, bpos,
- browser->nr_events);
+ disasm_rb_tree__insert(browser, &pos->al);
}
- pthread_mutex_unlock(&notes->lock);
+ annotation__unlock(notes);
browser->curr_hot = rb_last(&browser->entries);
}
-static bool annotate_browser__toggle_source(struct annotate_browser *browser)
+static struct annotation_line *annotate_browser__find_new_asm_line(
+ struct annotate_browser *browser,
+ int idx_asm)
+{
+ struct annotation_line *al;
+ struct list_head *head = browser->b.entries;
+
+ /* find an annotation line in the new list with the same idx_asm */
+ list_for_each_entry(al, head, node) {
+ if (al->idx_asm == idx_asm)
+ return al;
+ }
+
+ /* There are no asm lines */
+ return NULL;
+}
+
+static struct annotation_line *annotate_browser__find_next_asm_line(
+ struct annotate_browser *browser,
+ struct annotation_line *al)
+{
+ struct annotation_line *it = al;
+
+ /* find next asm line */
+ list_for_each_entry_continue(it, browser->b.entries, node) {
+ if (it->idx_asm >= 0)
+ return it;
+ }
+
+ /* no asm line found forwards, try backwards */
+ it = al;
+ list_for_each_entry_continue_reverse(it, browser->b.entries, node) {
+ if (it->idx_asm >= 0)
+ return it;
+ }
+
+ /* There are no asm lines */
+ return NULL;
+}
+
+static bool annotation__has_source(struct annotation *notes)
+{
+ struct annotation_line *al;
+ bool found_asm = false;
+
+ /* Let's skip the first non-asm lines which present regardless of source. */
+ list_for_each_entry(al, &notes->src->source, node) {
+ if (al->offset >= 0) {
+ found_asm = true;
+ break;
+ }
+ }
+
+ if (found_asm) {
+ /* After assembly lines, any line without offset means source. */
+ list_for_each_entry_continue(al, &notes->src->source, node) {
+ if (al->offset == -1)
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool annotate_browser__toggle_source(struct annotate_browser *browser,
+ struct evsel *evsel)
{
- struct disasm_line *dl;
- struct browser_disasm_line *bdl;
+ struct annotation *notes = browser__annotation(&browser->b);
+ struct annotation_line *al;
off_t offset = browser->b.index - browser->b.top_idx;
browser->b.seek(&browser->b, offset, SEEK_CUR);
- dl = list_entry(browser->b.top, struct disasm_line, node);
- bdl = disasm_line__browser(dl);
+ al = list_entry(browser->b.top, struct annotation_line, node);
- if (annotate_browser__opts.hide_src_code) {
- if (bdl->idx_asm < offset)
- offset = bdl->idx;
+ if (!annotate_opts.annotate_src)
+ annotate_opts.annotate_src = true;
- browser->b.nr_entries = browser->nr_entries;
- annotate_browser__opts.hide_src_code = false;
+ /*
+ * It's about to get source code annotation for the first time.
+ * Drop the existing annotation_lines and get the new one with source.
+ * And then move to the original line at the same asm index.
+ */
+ if (annotate_opts.hide_src_code && !notes->src->tried_source) {
+ struct map_symbol *ms = browser->b.priv;
+ int orig_idx_asm = al->idx_asm;
+
+ /* annotate again with source code info */
+ annotate_opts.hide_src_code = false;
+ annotated_source__purge(notes->src);
+ symbol__annotate2(ms, evsel, &browser->arch);
+ annotate_opts.hide_src_code = true;
+
+ /* should be after annotated_source__purge() */
+ notes->src->tried_source = true;
+
+ if (!annotation__has_source(notes))
+ ui__warning("Annotation has no source code.");
+
+ browser->b.entries = &notes->src->source;
+ al = annotate_browser__find_new_asm_line(browser, orig_idx_asm);
+ if (unlikely(al == NULL)) {
+ al = list_first_entry(&notes->src->source,
+ struct annotation_line, node);
+ }
+ browser->b.seek(&browser->b, al->idx_asm, SEEK_SET);
+ }
+
+ if (annotate_opts.hide_src_code) {
+ if (al->idx_asm < offset)
+ offset = al->idx;
+
+ browser->b.nr_entries = notes->src->nr_entries;
+ annotate_opts.hide_src_code = false;
browser->b.seek(&browser->b, -offset, SEEK_CUR);
- browser->b.top_idx = bdl->idx - offset;
- browser->b.index = bdl->idx;
+ browser->b.top_idx = al->idx - offset;
+ browser->b.index = al->idx;
} else {
- if (bdl->idx_asm < 0) {
- ui_helpline__puts("Only available for assembly lines.");
- browser->b.seek(&browser->b, -offset, SEEK_CUR);
- return false;
+ if (al->idx_asm < 0) {
+ /* move cursor to next asm line */
+ al = annotate_browser__find_next_asm_line(browser, al);
+ if (!al) {
+ browser->b.seek(&browser->b, -offset, SEEK_CUR);
+ return false;
+ }
}
- if (bdl->idx_asm < offset)
- offset = bdl->idx_asm;
+ if (al->idx_asm < offset)
+ offset = al->idx_asm;
- browser->b.nr_entries = browser->nr_asm_entries;
- annotate_browser__opts.hide_src_code = true;
+ browser->b.nr_entries = notes->src->nr_asm_entries;
+ annotate_opts.hide_src_code = true;
browser->b.seek(&browser->b, -offset, SEEK_CUR);
- browser->b.top_idx = bdl->idx_asm - offset;
- browser->b.index = bdl->idx_asm;
+ browser->b.top_idx = al->idx_asm - offset;
+ browser->b.index = al->idx_asm;
}
+ if (annotate_opts.hide_src_code_on_title)
+ annotate_opts.hide_src_code_on_title = false;
+
return true;
}
-static void annotate_browser__init_asm_mode(struct annotate_browser *browser)
+#define SYM_TITLE_MAX_SIZE (PATH_MAX + 64)
+
+static void annotate_browser__show_full_location(struct ui_browser *browser)
{
- ui_browser__reset_index(&browser->b);
- browser->b.nr_entries = browser->nr_asm_entries;
+ struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
+ struct disasm_line *cursor = disasm_line(ab->selection);
+ struct annotation_line *al = &cursor->al;
+
+ if (al->offset != -1)
+ ui_helpline__puts("Only available for source code lines.");
+ else if (al->fileloc == NULL)
+ ui_helpline__puts("No source file location.");
+ else {
+ char help_line[SYM_TITLE_MAX_SIZE];
+ sprintf (help_line, "Source file location: %s", al->fileloc);
+ ui_helpline__puts(help_line);
+ }
}
+static void ui_browser__init_asm_mode(struct ui_browser *browser)
+{
+ struct annotation *notes = browser__annotation(browser);
+ ui_browser__reset_index(browser);
+ browser->nr_entries = notes->src->nr_asm_entries;
+}
+
+static int sym_title(struct symbol *sym, struct map *map, char *title,
+ size_t sz, int percent_type)
+{
+ return snprintf(title, sz, "%s %s [Percent: %s] %s", sym->name,
+ dso__long_name(map__dso(map)),
+ percent_type_str(percent_type),
+ annotate_opts.code_with_type ? "[Type]" : "");
+}
+
+static void annotate_browser__show_function_title(struct annotate_browser *browser)
+{
+ struct ui_browser *b = &browser->b;
+ struct map_symbol *ms = b->priv;
+ struct symbol *sym = ms->sym;
+ char title[SYM_TITLE_MAX_SIZE];
+
+ sym_title(sym, ms->map, title, sizeof(title), annotate_opts.percent_type);
+
+ ui_browser__gotorc_title(b, 0, 0);
+ ui_browser__set_color(b, HE_COLORSET_ROOT);
+ ui_browser__write_nstring(b, title, b->width + 1);
+}
+
+/*
+ * This can be called from external jumps, i.e. jumps from one function
+ * to another, like from the kernel's entry_SYSCALL_64 function to the
+ * swapgs_restore_regs_and_return_to_usermode() function.
+ *
+ * So all we check here is that dl->ops.target.sym is set, if it is, just
+ * go to that function and when exiting from its disassembly, come back
+ * to the calling function.
+ */
static bool annotate_browser__callq(struct annotate_browser *browser,
- struct perf_evsel *evsel,
+ struct evsel *evsel,
struct hist_browser_timer *hbt)
{
- struct map_symbol *ms = browser->b.priv;
- struct disasm_line *dl = browser->selection;
- struct symbol *sym = ms->sym;
+ struct map_symbol *ms = browser->b.priv, target_ms;
+ struct disasm_line *dl = disasm_line(browser->selection);
struct annotation *notes;
- struct symbol *target;
- u64 ip;
- if (!ins__is_call(dl->ins))
- return false;
-
- ip = ms->map->map_ip(ms->map, dl->ops.target.addr);
- target = map__find_symbol(ms->map, ip, NULL);
- if (target == NULL) {
+ if (!dl->ops.target.sym) {
ui_helpline__puts("The called function was not found.");
return true;
}
- notes = symbol__annotation(target);
- pthread_mutex_lock(&notes->lock);
+ notes = symbol__annotation(dl->ops.target.sym);
+ annotation__lock(notes);
- if (notes->src == NULL && symbol__alloc_hist(target) < 0) {
- pthread_mutex_unlock(&notes->lock);
+ if (!symbol__hists(dl->ops.target.sym, evsel->evlist->core.nr_entries)) {
+ annotation__unlock(notes);
ui__warning("Not enough memory for annotating '%s' symbol!\n",
- target->name);
+ dl->ops.target.sym->name);
return true;
}
- pthread_mutex_unlock(&notes->lock);
- symbol__tui_annotate(target, ms->map, evsel, hbt);
- ui_browser__show_title(&browser->b, sym->name);
+ target_ms.maps = ms->maps;
+ target_ms.map = ms->map;
+ target_ms.sym = dl->ops.target.sym;
+ annotation__unlock(notes);
+ __hist_entry__tui_annotate(browser->he, &target_ms, evsel, hbt, NO_ADDR);
+
+ /*
+ * The annotate_browser above changed the title with the target function
+ * and now it's back to the original function. Refresh the header line
+ * for the original function again.
+ */
+ annotate_browser__show_function_title(browser);
return true;
}
@@ -469,59 +620,64 @@ static
struct disasm_line *annotate_browser__find_offset(struct annotate_browser *browser,
s64 offset, s64 *idx)
{
- struct map_symbol *ms = browser->b.priv;
- struct symbol *sym = ms->sym;
- struct annotation *notes = symbol__annotation(sym);
+ struct annotation *notes = browser__annotation(&browser->b);
struct disasm_line *pos;
*idx = 0;
- list_for_each_entry(pos, &notes->src->source, node) {
- if (pos->offset == offset)
+ list_for_each_entry(pos, &notes->src->source, al.node) {
+ if (pos->al.offset == offset)
return pos;
- if (!disasm_line__filter(&browser->b, &pos->node))
+ if (!annotation_line__filter(&pos->al))
++*idx;
}
return NULL;
}
-static bool annotate_browser__jump(struct annotate_browser *browser)
+static bool annotate_browser__jump(struct annotate_browser *browser,
+ struct evsel *evsel,
+ struct hist_browser_timer *hbt)
{
- struct disasm_line *dl = browser->selection;
+ struct disasm_line *dl = disasm_line(browser->selection);
+ u64 offset;
s64 idx;
- if (!ins__is_jump(dl->ins))
+ if (!ins__is_jump(&dl->ins))
return false;
- dl = annotate_browser__find_offset(browser, dl->ops.target.offset, &idx);
+ if (dl->ops.target.outside) {
+ annotate_browser__callq(browser, evsel, hbt);
+ return true;
+ }
+
+ offset = dl->ops.target.offset;
+ dl = annotate_browser__find_offset(browser, offset, &idx);
if (dl == NULL) {
- ui_helpline__puts("Invallid jump offset");
+ ui_helpline__printf("Invalid jump offset: %" PRIx64, offset);
return true;
}
- annotate_browser__set_top(browser, dl, idx);
-
+ annotate_browser__set_top(browser, &dl->al, idx);
+
return true;
}
static
-struct disasm_line *annotate_browser__find_string(struct annotate_browser *browser,
+struct annotation_line *annotate_browser__find_string(struct annotate_browser *browser,
char *s, s64 *idx)
{
- struct map_symbol *ms = browser->b.priv;
- struct symbol *sym = ms->sym;
- struct annotation *notes = symbol__annotation(sym);
- struct disasm_line *pos = browser->selection;
+ struct annotation *notes = browser__annotation(&browser->b);
+ struct annotation_line *al = browser->selection;
*idx = browser->b.index;
- list_for_each_entry_continue(pos, &notes->src->source, node) {
- if (disasm_line__filter(&browser->b, &pos->node))
+ list_for_each_entry_continue(al, &notes->src->source, node) {
+ if (annotation_line__filter(al))
continue;
++*idx;
- if (pos->line && strstr(pos->line, s) != NULL)
- return pos;
+ if (al->line && strstr(al->line, s) != NULL)
+ return al;
}
return NULL;
@@ -529,38 +685,36 @@ struct disasm_line *annotate_browser__find_string(struct annotate_browser *brows
static bool __annotate_browser__search(struct annotate_browser *browser)
{
- struct disasm_line *dl;
+ struct annotation_line *al;
s64 idx;
- dl = annotate_browser__find_string(browser, browser->search_bf, &idx);
- if (dl == NULL) {
+ al = annotate_browser__find_string(browser, browser->search_bf, &idx);
+ if (al == NULL) {
ui_helpline__puts("String not found!");
return false;
}
- annotate_browser__set_top(browser, dl, idx);
+ annotate_browser__set_top(browser, al, idx);
browser->searching_backwards = false;
return true;
}
static
-struct disasm_line *annotate_browser__find_string_reverse(struct annotate_browser *browser,
+struct annotation_line *annotate_browser__find_string_reverse(struct annotate_browser *browser,
char *s, s64 *idx)
{
- struct map_symbol *ms = browser->b.priv;
- struct symbol *sym = ms->sym;
- struct annotation *notes = symbol__annotation(sym);
- struct disasm_line *pos = browser->selection;
+ struct annotation *notes = browser__annotation(&browser->b);
+ struct annotation_line *al = browser->selection;
*idx = browser->b.index;
- list_for_each_entry_continue_reverse(pos, &notes->src->source, node) {
- if (disasm_line__filter(&browser->b, &pos->node))
+ list_for_each_entry_continue_reverse(al, &notes->src->source, node) {
+ if (annotation_line__filter(al))
continue;
--*idx;
- if (pos->line && strstr(pos->line, s) != NULL)
- return pos;
+ if (al->line && strstr(al->line, s) != NULL)
+ return al;
}
return NULL;
@@ -568,16 +722,16 @@ struct disasm_line *annotate_browser__find_string_reverse(struct annotate_browse
static bool __annotate_browser__search_reverse(struct annotate_browser *browser)
{
- struct disasm_line *dl;
+ struct annotation_line *al;
s64 idx;
- dl = annotate_browser__find_string_reverse(browser, browser->search_bf, &idx);
- if (dl == NULL) {
+ al = annotate_browser__find_string_reverse(browser, browser->search_bf, &idx);
+ if (al == NULL) {
ui_helpline__puts("String not found!");
return false;
}
- annotate_browser__set_top(browser, dl, idx);
+ annotate_browser__set_top(browser, al, idx);
browser->searching_backwards = true;
return true;
}
@@ -630,35 +784,122 @@ bool annotate_browser__continue_search_reverse(struct annotate_browser *browser,
return __annotate_browser__search_reverse(browser);
}
-static void annotate_browser__update_addr_width(struct annotate_browser *browser)
+static int annotate_browser__show(struct annotate_browser *browser, char *title, const char *help)
{
- if (annotate_browser__opts.use_offset)
- browser->target_width = browser->min_addr_width;
- else
- browser->target_width = browser->max_addr_width;
+ if (ui_browser__show(&browser->b, title, help) < 0)
+ return -1;
+
+ annotate_browser__show_function_title(browser);
+ return 0;
+}
+
+static void
+switch_percent_type(struct annotation_options *opts, bool base)
+{
+ switch (opts->percent_type) {
+ case PERCENT_HITS_LOCAL:
+ if (base)
+ opts->percent_type = PERCENT_PERIOD_LOCAL;
+ else
+ opts->percent_type = PERCENT_HITS_GLOBAL;
+ break;
+ case PERCENT_HITS_GLOBAL:
+ if (base)
+ opts->percent_type = PERCENT_PERIOD_GLOBAL;
+ else
+ opts->percent_type = PERCENT_HITS_LOCAL;
+ break;
+ case PERCENT_PERIOD_LOCAL:
+ if (base)
+ opts->percent_type = PERCENT_HITS_LOCAL;
+ else
+ opts->percent_type = PERCENT_PERIOD_GLOBAL;
+ break;
+ case PERCENT_PERIOD_GLOBAL:
+ if (base)
+ opts->percent_type = PERCENT_HITS_GLOBAL;
+ else
+ opts->percent_type = PERCENT_PERIOD_LOCAL;
+ break;
+ default:
+ WARN_ON(1);
+ }
+}
- browser->addr_width = browser->target_width;
+static int annotate__scnprintf_title(struct hists *hists, char *bf, size_t size)
+{
+ int printed = hists__scnprintf_title(hists, bf, size);
- if (annotate_browser__opts.show_nr_jumps)
- browser->addr_width += browser->jumps_width + 1;
+ if (!annotate_opts.hide_src_code_on_title) {
+ printed += scnprintf(bf + printed, size - printed, " [source: %s]",
+ annotate_opts.hide_src_code ? "OFF" : "On");
+ }
+
+ return printed;
+}
+
+static void annotate_browser__debuginfo_warning(struct annotate_browser *browser)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct dso *dso = map__dso(ms->map);
+
+ if (browser->dbg == NULL && annotate_opts.code_with_type &&
+ !dso__debuginfo_warned(dso)) {
+ ui__warning("DWARF debuginfo not found.\n\n"
+ "Data-type in this DSO will not be displayed.\n"
+ "Please make sure to have debug information.");
+ dso__set_debuginfo_warned(dso);
+ }
+}
+
+static s64 annotate_browser__curr_hot_offset(struct annotate_browser *browser)
+{
+ struct annotation_line *al = NULL;
+
+ if (browser->curr_hot)
+ al = rb_entry(browser->curr_hot, struct annotation_line, rb_node);
+
+ return al ? al->offset : 0;
+}
+
+static void annotate_browser__symbol_annotate_error(struct annotate_browser *browser, int err)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ struct dso *dso = map__dso(ms->map);
+ char msg[BUFSIZ];
+
+ dso__set_annotate_warned(dso);
+ symbol__strerror_disassemble(ms, err, msg, sizeof(msg));
+ ui__error("Couldn't annotate %s:\n%s", sym->name, msg);
}
static int annotate_browser__run(struct annotate_browser *browser,
- struct perf_evsel *evsel,
+ struct evsel *evsel,
struct hist_browser_timer *hbt)
{
struct rb_node *nd = NULL;
+ struct hists *hists = evsel__hists(evsel);
struct map_symbol *ms = browser->b.priv;
struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(ms->sym);
const char *help = "Press 'h' for help on key bindings";
int delay_secs = hbt ? hbt->refresh : 0;
+ char *br_cntr_text = NULL;
+ char title[256];
int key;
- if (ui_browser__show(&browser->b, sym->name, help) < 0)
+ annotate__scnprintf_title(hists, title, sizeof(title));
+ if (annotate_browser__show(browser, title, help) < 0)
return -1;
annotate_browser__calc_percent(browser, evsel);
+ if (browser->selection != NULL) {
+ browser->curr_hot = &browser->selection->rb_node;
+ browser->b.use_navkeypressed = false;
+ }
+
if (browser->curr_hot) {
annotate_browser__set_rb_top(browser, browser->curr_hot);
browser->b.navkeypressed = false;
@@ -666,6 +907,10 @@ static int annotate_browser__run(struct annotate_browser *browser,
nd = browser->curr_hot;
+ annotation_br_cntr_abbr_list(&br_cntr_text, evsel, false);
+
+ annotate_browser__debuginfo_warning(browser);
+
while (1) {
key = ui_browser__run(&browser->b, delay_secs);
@@ -685,8 +930,11 @@ static int annotate_browser__run(struct annotate_browser *browser,
if (hbt)
hbt->timer(hbt->arg);
- if (delay_secs != 0)
- symbol__annotate_decay_histogram(sym, evsel->idx);
+ if (delay_secs != 0) {
+ symbol__annotate_decay_histogram(sym, evsel);
+ annotate__scnprintf_title(hists, title, sizeof(title));
+ annotate_browser__show(browser, title, help);
+ }
continue;
case K_TAB:
if (nd != NULL) {
@@ -697,11 +945,11 @@ static int annotate_browser__run(struct annotate_browser *browser,
nd = browser->curr_hot;
break;
case K_UNTAB:
- if (nd != NULL)
+ if (nd != NULL) {
nd = rb_next(nd);
if (nd == NULL)
nd = rb_first(&browser->entries);
- else
+ } else
nd = browser->curr_hot;
break;
case K_F1:
@@ -709,41 +957,76 @@ static int annotate_browser__run(struct annotate_browser *browser,
ui_browser__help_window(&browser->b,
"UP/DOWN/PGUP\n"
"PGDN/SPACE Navigate\n"
+ "</> Move to prev/next symbol\n"
"q/ESC/CTRL+C Exit\n\n"
- "-> Go to target\n"
- "<- Exit\n"
- "H Cycle thru hottest instructions\n"
+ "ENTER Go to target\n"
+ "H Go to hottest instruction\n"
+ "TAB/shift+TAB Cycle thru hottest instructions\n"
"j Toggle showing jump to target arrows\n"
"J Toggle showing number of jump sources on targets\n"
"n Search next string\n"
"o Toggle disassembler output/simplified view\n"
+ "O Bump offset level (jump targets -> +call -> all -> cycle thru)\n"
"s Toggle source code view\n"
+ "t Circulate percent, total period, samples view\n"
+ "c Show min/max cycle\n"
"/ Search string\n"
+ "k Toggle line numbers\n"
+ "l Show full source file location\n"
+ "P Print to [symbol_name].annotation file.\n"
"r Run available scripts\n"
- "? Search previous string\n");
+ "p Toggle percent type [local/global]\n"
+ "b Toggle percent base [period/hits]\n"
+ "B Branch counter abbr list (Optional)\n"
+ "? Search string backwards\n"
+ "f Toggle showing offsets to full address\n"
+ "T Toggle data type display\n");
continue;
case 'r':
- {
- script_browse(NULL);
- continue;
- }
+ script_browse(NULL, NULL);
+ annotate_browser__show(browser, title, help);
+ continue;
+ case 'k':
+ annotate_opts.show_linenr = !annotate_opts.show_linenr;
+ continue;
+ case 'l':
+ annotate_browser__show_full_location (&browser->b);
+ continue;
case 'H':
nd = browser->curr_hot;
break;
- case 's':
- if (annotate_browser__toggle_source(browser))
+ case 's': {
+ struct annotation_line *al = NULL;
+ s64 offset = annotate_browser__curr_hot_offset(browser);
+
+ if (annotate_browser__toggle_source(browser, evsel))
ui_helpline__puts(help);
+
+ /* Update the annotation browser's rb_tree, and reset the nd */
+ annotate_browser__calc_percent(browser, evsel);
+ /* Try to find the same asm line as before */
+ al = annotated_source__get_line(notes->src, offset);
+ browser->curr_hot = al ? &al->rb_node : NULL;
+ nd = browser->curr_hot;
+
+ annotate__scnprintf_title(hists, title, sizeof(title));
+ annotate_browser__show(browser, title, help);
continue;
+ }
case 'o':
- annotate_browser__opts.use_offset = !annotate_browser__opts.use_offset;
- annotate_browser__update_addr_width(browser);
+ annotate_opts.use_offset = !annotate_opts.use_offset;
+ annotation__update_column_widths(notes);
+ continue;
+ case 'O':
+ if (++annotate_opts.offset_level > ANNOTATION__MAX_OFFSET_LEVEL)
+ annotate_opts.offset_level = ANNOTATION__MIN_OFFSET_LEVEL;
continue;
case 'j':
- annotate_browser__opts.jump_arrows = !annotate_browser__opts.jump_arrows;
+ annotate_opts.jump_arrows = !annotate_opts.jump_arrows;
continue;
case 'J':
- annotate_browser__opts.show_nr_jumps = !annotate_browser__opts.show_nr_jumps;
- annotate_browser__update_addr_width(browser);
+ annotate_opts.show_nr_jumps = !annotate_opts.show_nr_jumps;
+ annotation__update_column_widths(notes);
continue;
case '/':
if (annotate_browser__search(browser, delay_secs)) {
@@ -769,31 +1052,86 @@ show_help:
browser->b.height,
browser->b.index,
browser->b.top_idx,
- browser->nr_asm_entries);
+ notes->src->nr_asm_entries);
}
continue;
case K_ENTER:
case K_RIGHT:
+ {
+ struct disasm_line *dl = disasm_line(browser->selection);
+
if (browser->selection == NULL)
ui_helpline__puts("Huh? No selection. Report to linux-kernel@vger.kernel.org");
else if (browser->selection->offset == -1)
ui_helpline__puts("Actions are only available for assembly lines.");
- else if (!browser->selection->ins) {
- if (strcmp(browser->selection->name, "retq"))
- goto show_sup_ins;
+ else if (!dl->ins.ops)
+ goto show_sup_ins;
+ else if (ins__is_ret(&dl->ins))
goto out;
- } else if (!(annotate_browser__jump(browser) ||
+ else if (!(annotate_browser__jump(browser, evsel, hbt) ||
annotate_browser__callq(browser, evsel, hbt))) {
show_sup_ins:
- ui_helpline__puts("Actions are only available for 'callq', 'retq' & jump instructions.");
+ ui_helpline__puts("Actions are only available for function call/return & jump/branch instructions.");
}
continue;
+ }
+ case 'P':
+ map_symbol__annotation_dump(ms, evsel, browser->he);
+ continue;
+ case 't':
+ if (symbol_conf.show_total_period) {
+ symbol_conf.show_total_period = false;
+ symbol_conf.show_nr_samples = true;
+ } else if (symbol_conf.show_nr_samples)
+ symbol_conf.show_nr_samples = false;
+ else
+ symbol_conf.show_total_period = true;
+ annotation__update_column_widths(notes);
+ continue;
+ case 'c':
+ if (annotate_opts.show_minmax_cycle)
+ annotate_opts.show_minmax_cycle = false;
+ else
+ annotate_opts.show_minmax_cycle = true;
+ annotation__update_column_widths(notes);
+ continue;
+ case 'p':
+ case 'b':
+ switch_percent_type(&annotate_opts, key == 'b');
+ annotate__scnprintf_title(hists, title, sizeof(title));
+ annotate_browser__show(browser, title, help);
+ continue;
+ case 'B':
+ if (br_cntr_text)
+ ui_browser__help_window(&browser->b, br_cntr_text);
+ else {
+ ui_browser__help_window(&browser->b,
+ "\n The branch counter is not available.\n");
+ }
+ continue;
+ case 'f':
+ annotation__toggle_full_addr(notes, ms);
+ continue;
+ case 'T':
+ annotate_opts.code_with_type ^= 1;
+ if (browser->dbg == NULL)
+ browser->dbg = dso__debuginfo(map__dso(ms->map));
+ if (browser->type_hash == NULL) {
+ browser->type_hash = hashmap__new(type_hash, type_equal,
+ /*ctx=*/NULL);
+ }
+ annotate_browser__show(browser, title, help);
+ annotate_browser__debuginfo_warning(browser);
+ continue;
case K_LEFT:
+ case '<':
+ case '>':
case K_ESC:
case 'q':
case CTRL('c'):
goto out;
default:
+ ui_browser__warn_unhandled_hotkey(&browser->b, key, delay_secs, ", use 'h'/F1 to see actions");
continue;
}
@@ -802,208 +1140,130 @@ show_sup_ins:
}
out:
ui_browser__hide(&browser->b);
+ free(br_cntr_text);
return key;
}
-int hist_entry__tui_annotate(struct hist_entry *he, struct perf_evsel *evsel,
- struct hist_browser_timer *hbt)
+int hist_entry__tui_annotate(struct hist_entry *he, struct evsel *evsel,
+ struct hist_browser_timer *hbt, u64 al_addr)
{
- return symbol__tui_annotate(he->ms.sym, he->ms.map, evsel, hbt);
-}
-
-static void annotate_browser__mark_jump_targets(struct annotate_browser *browser,
- size_t size)
-{
- u64 offset;
- struct map_symbol *ms = browser->b.priv;
- struct symbol *sym = ms->sym;
-
- /* PLT symbols contain external offsets */
- if (strstr(sym->name, "@plt"))
- return;
-
- for (offset = 0; offset < size; ++offset) {
- struct disasm_line *dl = browser->offsets[offset], *dlt;
- struct browser_disasm_line *bdlt;
-
- if (!disasm_line__is_valid_jump(dl, sym))
- continue;
+ /* reset abort key so that it can get Ctrl-C as a key */
+ SLang_reset_tty();
+ SLang_init_tty(0, 0, 0);
+ SLtty_set_suspend_state(true);
- dlt = browser->offsets[dl->ops.target.offset];
- /*
- * FIXME: Oops, no jump target? Buggy disassembler? Or do we
- * have to adjust to the previous offset?
- */
- if (dlt == NULL)
- continue;
-
- bdlt = disasm_line__browser(dlt);
- if (++bdlt->jump_sources > browser->max_jump_sources)
- browser->max_jump_sources = bdlt->jump_sources;
-
- ++browser->nr_jumps;
- }
-
+ return __hist_entry__tui_annotate(he, &he->ms, evsel, hbt, al_addr);
}
-static inline int width_jumps(int n)
+int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms,
+ struct evsel *evsel,
+ struct hist_browser_timer *hbt, u64 al_addr)
{
- if (n >= 100)
- return 5;
- if (n / 10)
- return 2;
- return 1;
-}
-
-int symbol__tui_annotate(struct symbol *sym, struct map *map,
- struct perf_evsel *evsel,
- struct hist_browser_timer *hbt)
-{
- struct disasm_line *pos, *n;
- struct annotation *notes;
- size_t size;
- struct map_symbol ms = {
- .map = map,
- .sym = sym,
- };
+ struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(sym);
struct annotate_browser browser = {
.b = {
.refresh = annotate_browser__refresh,
.seek = ui_browser__list_head_seek,
.write = annotate_browser__write,
.filter = disasm_line__filter,
- .priv = &ms,
+ .extra_title_lines = 1, /* for hists__scnprintf_title() */
+ .priv = ms,
.use_navkeypressed = true,
},
+ .he = he,
+ .evsel = evsel,
};
- int ret = -1;
- int nr_pcnt = 1;
- size_t sizeof_bdl = sizeof(struct browser_disasm_line);
+ struct dso *dso;
+ int ret = -1, err;
+ int not_annotated = list_empty(&notes->src->source);
if (sym == NULL)
return -1;
- size = symbol__size(sym);
-
- if (map->dso->annotate_warned)
+ dso = map__dso(ms->map);
+ if (dso__annotate_warned(dso))
return -1;
- browser.offsets = zalloc(size * sizeof(struct disasm_line *));
- if (browser.offsets == NULL) {
- ui__error("Not enough memory!");
- return -1;
- }
+ if (not_annotated || !sym->annotate2) {
+ err = symbol__annotate2(ms, evsel, &browser.arch);
+ if (err) {
+ annotate_browser__symbol_annotate_error(&browser, err);
+ return -1;
+ }
- if (perf_evsel__is_group_event(evsel)) {
- nr_pcnt = evsel->nr_members;
- sizeof_bdl += sizeof(double) * (nr_pcnt - 1);
+ if (!annotate_opts.hide_src_code) {
+ notes->src->tried_source = true;
+ if (!annotation__has_source(notes))
+ ui__warning("Annotation has no source code.");
+ }
+ } else {
+ err = evsel__get_arch(evsel, &browser.arch);
+ if (err) {
+ annotate_browser__symbol_annotate_error(&browser, err);
+ return -1;
+ }
}
- if (symbol__annotate(sym, map, sizeof_bdl) < 0) {
- ui__error("%s", ui_helpline__last_msg);
- goto out_free_offsets;
- }
+ /* Copy necessary information when it's called from perf top */
+ if (hbt != NULL && he != &annotate_he) {
+ annotate_he.hists = he->hists;
+ annotate_he.thread = thread__get(he->thread);
+ annotate_he.cpumode = he->cpumode;
+ map_symbol__copy(&annotate_he.ms, ms);
- ui_helpline__push("Press <- or ESC to exit");
+ browser.he = &annotate_he;
+ }
- notes = symbol__annotation(sym);
- browser.start = map__rip_2objdump(map, sym->start);
+ ui_helpline__push("Press ESC to exit");
- list_for_each_entry(pos, &notes->src->source, node) {
- struct browser_disasm_line *bpos;
- size_t line_len = strlen(pos->line);
+ if (annotate_opts.code_with_type) {
+ browser.dbg = dso__debuginfo(dso);
+ browser.type_hash = hashmap__new(type_hash, type_equal, /*ctx=*/NULL);
+ }
- if (browser.b.width < line_len)
- browser.b.width = line_len;
- bpos = disasm_line__browser(pos);
- bpos->idx = browser.nr_entries++;
- if (pos->offset != -1) {
- bpos->idx_asm = browser.nr_asm_entries++;
- /*
- * FIXME: short term bandaid to cope with assembly
- * routines that comes with labels in the same column
- * as the address in objdump, sigh.
- *
- * E.g. copy_user_generic_unrolled
- */
- if (pos->offset < (s64)size)
- browser.offsets[pos->offset] = pos;
- } else
- bpos->idx_asm = -1;
- }
-
- annotate_browser__mark_jump_targets(&browser, size);
-
- browser.addr_width = browser.target_width = browser.min_addr_width = hex_width(size);
- browser.max_addr_width = hex_width(sym->end);
- browser.jumps_width = width_jumps(browser.max_jump_sources);
- browser.nr_events = nr_pcnt;
- browser.b.nr_entries = browser.nr_entries;
- browser.b.entries = &notes->src->source,
+ browser.b.width = notes->src->widths.max_line_len;
+ browser.b.nr_entries = notes->src->nr_entries;
+ browser.b.entries = &notes->src->source;
browser.b.width += 18; /* Percentage */
- if (annotate_browser__opts.hide_src_code)
- annotate_browser__init_asm_mode(&browser);
+ if (annotate_opts.hide_src_code)
+ ui_browser__init_asm_mode(&browser.b);
- annotate_browser__update_addr_width(&browser);
+ /*
+ * If al_addr is set, it means that there should be a line
+ * intentionally selected, not based on the percentages
+ * which caculated by the event sampling. In this case, we
+ * convey this information into the browser selection, where
+ * the selection in other cases should be empty.
+ */
+ if (al_addr != NO_ADDR) {
+ struct annotation_line *al = annotated_source__get_line(notes->src,
+ al_addr - sym->start);
- ret = annotate_browser__run(&browser, evsel, hbt);
- list_for_each_entry_safe(pos, n, &notes->src->source, node) {
- list_del(&pos->node);
- disasm_line__free(pos);
+ browser.selection = al;
}
-out_free_offsets:
- free(browser.offsets);
- return ret;
-}
-
-#define ANNOTATE_CFG(n) \
- { .name = #n, .value = &annotate_browser__opts.n, }
-
-/*
- * Keep the entries sorted, they are bsearch'ed
- */
-static struct annotate_config {
- const char *name;
- bool *value;
-} annotate__configs[] = {
- ANNOTATE_CFG(hide_src_code),
- ANNOTATE_CFG(jump_arrows),
- ANNOTATE_CFG(show_nr_jumps),
- ANNOTATE_CFG(use_offset),
-};
-
-#undef ANNOTATE_CFG
-
-static int annotate_config__cmp(const void *name, const void *cfgp)
-{
- const struct annotate_config *cfg = cfgp;
-
- return strcmp(name, cfg->name);
-}
+ ret = annotate_browser__run(&browser, evsel, hbt);
-static int annotate__config(const char *var, const char *value,
- void *data __maybe_unused)
-{
- struct annotate_config *cfg;
- const char *name;
+ debuginfo__delete(browser.dbg);
- if (prefixcmp(var, "annotate.") != 0)
- return 0;
+ if (!IS_ERR_OR_NULL(browser.type_hash)) {
+ struct hashmap_entry *cur;
+ size_t bkt;
- name = var + 9;
- cfg = bsearch(name, annotate__configs, ARRAY_SIZE(annotate__configs),
- sizeof(struct annotate_config), annotate_config__cmp);
+ hashmap__for_each_entry(browser.type_hash, cur, bkt)
+ zfree(&cur->pvalue);
+ hashmap__free(browser.type_hash);
+ }
- if (cfg == NULL)
- return -1;
+ if (not_annotated && !notes->src->tried_source)
+ annotated_source__purge(notes->src);
- *cfg->value = perf_config_bool(name, value);
- return 0;
-}
+ if (hbt != NULL && he != &annotate_he) {
+ thread__zput(annotate_he.thread);
+ map_symbol__exit(&annotate_he.ms);
+ }
-void annotate_browser__init(void)
-{
- perf_config(annotate__config, NULL);
+ return ret;
}
diff --git a/tools/perf/ui/browsers/header.c b/tools/perf/ui/browsers/header.c
new file mode 100644
index 000000000000..5b5ca32e3eef
--- /dev/null
+++ b/tools/perf/ui/browsers/header.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "ui/browser.h"
+#include "ui/keysyms.h"
+#include "ui/ui.h"
+#include "ui/util.h"
+#include "ui/libslang.h"
+#include "util/header.h"
+#include "util/session.h"
+
+#include <sys/ttydefaults.h>
+
+static void ui_browser__argv_write(struct ui_browser *browser,
+ void *entry, int row)
+{
+ char **arg = entry;
+ char *str = *arg;
+ char empty[] = " ";
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+ unsigned long offset = (unsigned long)browser->priv;
+
+ if (offset >= strlen(str))
+ str = empty;
+ else
+ str = str + offset;
+
+ ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
+ HE_COLORSET_NORMAL);
+
+ ui_browser__write_nstring(browser, str, browser->width);
+}
+
+static int list_menu__run(struct ui_browser *menu)
+{
+ int key;
+ unsigned long offset;
+ static const char help[] =
+ "h/?/F1 Show this window\n"
+ "UP/DOWN/PGUP\n"
+ "PGDN/SPACE\n"
+ "LEFT/RIGHT Navigate\n"
+ "q/ESC/CTRL+C Exit browser";
+
+ if (ui_browser__show(menu, "Header information", "Press 'q' to exit") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(menu, 0);
+
+ switch (key) {
+ case K_RIGHT:
+ offset = (unsigned long)menu->priv;
+ offset += 10;
+ menu->priv = (void *)offset;
+ continue;
+ case K_LEFT:
+ offset = (unsigned long)menu->priv;
+ if (offset >= 10)
+ offset -= 10;
+ menu->priv = (void *)offset;
+ continue;
+ case K_F1:
+ case 'h':
+ case '?':
+ ui_browser__help_window(menu, help);
+ continue;
+ case K_ESC:
+ case 'q':
+ case CTRL('c'):
+ key = -1;
+ break;
+ default:
+ ui_browser__warn_unhandled_hotkey(menu, key, 0, ", use 'h'/'?'/F1 to see actions");
+ continue;
+ }
+
+ break;
+ }
+
+ ui_browser__hide(menu);
+ return key;
+}
+
+static int ui__list_menu(int argc, char * const argv[])
+{
+ struct ui_browser menu = {
+ .entries = (void *)argv,
+ .refresh = ui_browser__argv_refresh,
+ .seek = ui_browser__argv_seek,
+ .write = ui_browser__argv_write,
+ .nr_entries = argc,
+ };
+
+ return list_menu__run(&menu);
+}
+
+int tui__header_window(struct perf_session *session)
+{
+ int i, argc = 0;
+ char **argv;
+ char *ptr, *pos;
+ size_t size;
+ FILE *fp = open_memstream(&ptr, &size);
+
+ perf_header__fprintf_info(session, fp, true);
+ fclose(fp);
+
+ for (pos = ptr, argc = 0; (pos = strchr(pos, '\n')) != NULL; pos++)
+ argc++;
+
+ argv = calloc(argc + 1, sizeof(*argv));
+ if (argv == NULL)
+ goto out;
+
+ argv[0] = pos = ptr;
+ for (i = 1; (pos = strchr(pos, '\n')) != NULL; i++) {
+ *pos++ = '\0';
+ argv[i] = pos;
+ }
+
+ BUG_ON(i != argc + 1);
+
+ ui__list_menu(argc, argv);
+
+out:
+ free(argv);
+ free(ptr);
+ return 0;
+}
diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c
index fc0bd3843d34..08fecbe28a52 100644
--- a/tools/perf/ui/browsers/hists.c
+++ b/tools/perf/ui/browsers/hists.c
@@ -1,50 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <dirent.h>
+#include <errno.h>
+#include <inttypes.h>
#include <stdio.h>
-#include "../libslang.h"
#include <stdlib.h>
#include <string.h>
#include <linux/rbtree.h>
-
+#include <linux/string.h>
+#include <sys/ttydefaults.h>
+#include <linux/time64.h>
+#include <linux/zalloc.h>
+
+#include "../../util/debug.h"
+#include "../../util/dso.h"
+#include "../../util/callchain.h"
#include "../../util/evsel.h"
#include "../../util/evlist.h"
+#include "../../util/header.h"
#include "../../util/hist.h"
+#include "../../util/machine.h"
+#include "../../util/map.h"
+#include "../../util/maps.h"
+#include "../../util/symbol.h"
+#include "../../util/map_symbol.h"
+#include "../../util/branch.h"
#include "../../util/pstack.h"
#include "../../util/sort.h"
+#include "../../util/top.h"
+#include "../../util/thread.h"
+#include "../../util/block-info.h"
#include "../../util/util.h"
#include "../../arch/common.h"
-#include "../browser.h"
+#include "../browsers/hists.h"
#include "../helpline.h"
#include "../util.h"
#include "../ui.h"
#include "map.h"
+#include "annotate.h"
+#include "annotate-data.h"
+#include "srcline.h"
+#include "string2.h"
+#include "units.h"
+#include "time-utils.h"
-struct hist_browser {
- struct ui_browser b;
- struct hists *hists;
- struct hist_entry *he_selection;
- struct map_symbol *selection;
- int print_seq;
- bool show_dso;
- float min_pcnt;
- u64 nr_pcnt_entries;
-};
+#include <linux/ctype.h>
extern void hist_browser__init_hpp(void);
-static int hists__browser_title(struct hists *hists, char *bf, size_t size,
- const char *ev_name);
+static int hists_browser__scnprintf_title(struct hist_browser *browser, char *bf, size_t size);
+static void hist_browser__update_nr_entries(struct hist_browser *hb);
+
+static struct rb_node *hists__filter_entries(struct rb_node *nd,
+ float min_pcnt);
+
+static bool hist_browser__has_filter(struct hist_browser *hb)
+{
+ return hists__has_filter(hb->hists) || hb->min_pcnt || symbol_conf.has_filter || hb->c2c_filter;
+}
+
+static int hist_browser__get_folding(struct hist_browser *browser)
+{
+ struct rb_node *nd;
+ struct hists *hists = browser->hists;
+ int unfolded_rows = 0;
+
+ for (nd = rb_first_cached(&hists->entries);
+ (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
+ nd = rb_hierarchy_next(nd)) {
+ struct hist_entry *he =
+ rb_entry(nd, struct hist_entry, rb_node);
+
+ if (he->leaf && he->unfolded)
+ unfolded_rows += he->nr_rows;
+ }
+ return unfolded_rows;
+}
+
+static void hist_browser__set_title_space(struct hist_browser *hb)
+{
+ struct ui_browser *browser = &hb->b;
+ struct hists *hists = hb->hists;
+ struct perf_hpp_list *hpp_list = hists->hpp_list;
+
+ browser->extra_title_lines = hb->show_headers ? hpp_list->nr_header_lines : 0;
+}
+
+static u32 hist_browser__nr_entries(struct hist_browser *hb)
+{
+ u32 nr_entries;
+
+ if (symbol_conf.report_hierarchy)
+ nr_entries = hb->nr_hierarchy_entries;
+ else if (hist_browser__has_filter(hb))
+ nr_entries = hb->nr_non_filtered_entries;
+ else
+ nr_entries = hb->hists->nr_entries;
+
+ hb->nr_callchain_rows = hist_browser__get_folding(hb);
+ return nr_entries + hb->nr_callchain_rows;
+}
+
+static void hist_browser__update_rows(struct hist_browser *hb)
+{
+ struct ui_browser *browser = &hb->b;
+ struct hists *hists = hb->hists;
+ struct perf_hpp_list *hpp_list = hists->hpp_list;
+ u16 index_row;
+
+ if (!hb->show_headers) {
+ browser->rows += browser->extra_title_lines;
+ browser->extra_title_lines = 0;
+ return;
+ }
+
+ browser->extra_title_lines = hpp_list->nr_header_lines;
+ browser->rows -= browser->extra_title_lines;
+ /*
+ * Verify if we were at the last line and that line isn't
+ * visible because we now show the header line(s).
+ */
+ index_row = browser->index - browser->top_idx;
+ if (index_row >= browser->rows)
+ browser->index -= index_row - browser->rows + 1;
+}
-static void hist_browser__refresh_dimensions(struct hist_browser *browser)
+static void hist_browser__refresh_dimensions(struct ui_browser *browser)
{
+ struct hist_browser *hb = container_of(browser, struct hist_browser, b);
+
/* 3 == +/- toggle symbol before actual hist_entry rendering */
- browser->b.width = 3 + (hists__sort_list_width(browser->hists) +
- sizeof("[k]"));
+ browser->width = 3 + (hists__sort_list_width(hb->hists) + sizeof("[k]"));
+ /*
+ * FIXME: Just keeping existing behaviour, but this really should be
+ * before updating browser->width, as it will invalidate the
+ * calculation above. Fix this and the fallout in another
+ * changeset.
+ */
+ ui_browser__refresh_dimensions(browser);
}
static void hist_browser__reset(struct hist_browser *browser)
{
- browser->b.nr_entries = browser->hists->nr_entries;
- hist_browser__refresh_dimensions(browser);
+ /*
+ * The hists__remove_entry_filter() already folds non-filtered
+ * entries so we can assume it has 0 callchain rows.
+ */
+ browser->nr_callchain_rows = 0;
+
+ hist_browser__update_nr_entries(browser);
+ browser->b.nr_entries = hist_browser__nr_entries(browser);
+ hist_browser__refresh_dimensions(&browser->b);
ui_browser__reset_index(&browser->b);
}
@@ -53,24 +159,19 @@ static char tree__folded_sign(bool unfolded)
return unfolded ? '-' : '+';
}
-static char map_symbol__folded(const struct map_symbol *ms)
-{
- return ms->has_children ? tree__folded_sign(ms->unfolded) : ' ';
-}
-
static char hist_entry__folded(const struct hist_entry *he)
{
- return map_symbol__folded(&he->ms);
+ return he->has_children ? tree__folded_sign(he->unfolded) : ' ';
}
static char callchain_list__folded(const struct callchain_list *cl)
{
- return map_symbol__folded(&cl->ms);
+ return cl->has_children ? tree__folded_sign(cl->unfolded) : ' ';
}
-static void map_symbol__set_folding(struct map_symbol *ms, bool unfold)
+static void callchain_list__set_folding(struct callchain_list *cl, bool unfold)
{
- ms->unfolded = unfold ? ms->has_children : false;
+ cl->unfolded = unfold ? cl->has_children : false;
}
static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
@@ -85,6 +186,7 @@ static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
list_for_each_entry(chain, &child->val, list) {
++n;
+
/* We need this because we may not have children */
folded_sign = callchain_list__folded(chain);
if (folded_sign == '+')
@@ -98,15 +200,55 @@ static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
return n;
}
+static int callchain_node__count_flat_rows(struct callchain_node *node)
+{
+ struct callchain_list *chain;
+ char folded_sign = 0;
+ int n = 0;
+
+ list_for_each_entry(chain, &node->parent_val, list) {
+ if (!folded_sign) {
+ /* only check first chain list entry */
+ folded_sign = callchain_list__folded(chain);
+ if (folded_sign == '+')
+ return 1;
+ }
+ n++;
+ }
+
+ list_for_each_entry(chain, &node->val, list) {
+ if (!folded_sign) {
+ /* node->parent_val list might be empty */
+ folded_sign = callchain_list__folded(chain);
+ if (folded_sign == '+')
+ return 1;
+ }
+ n++;
+ }
+
+ return n;
+}
+
+static int callchain_node__count_folded_rows(struct callchain_node *node __maybe_unused)
+{
+ return 1;
+}
+
static int callchain_node__count_rows(struct callchain_node *node)
{
struct callchain_list *chain;
bool unfolded = false;
int n = 0;
+ if (callchain_param.mode == CHAIN_FLAT)
+ return callchain_node__count_flat_rows(node);
+ else if (callchain_param.mode == CHAIN_FOLDED)
+ return callchain_node__count_folded_rows(node);
+
list_for_each_entry(chain, &node->val, list) {
++n;
- unfolded = chain->ms.unfolded;
+
+ unfolded = chain->unfolded;
}
if (unfolded)
@@ -128,15 +270,59 @@ static int callchain__count_rows(struct rb_root *chain)
return n;
}
-static bool map_symbol__toggle_fold(struct map_symbol *ms)
+static int hierarchy_count_rows(struct hist_browser *hb, struct hist_entry *he,
+ bool include_children)
{
- if (!ms)
+ int count = 0;
+ struct rb_node *node;
+ struct hist_entry *child;
+
+ if (he->leaf)
+ return callchain__count_rows(&he->sorted_chain);
+
+ if (he->has_no_entry)
+ return 1;
+
+ node = rb_first_cached(&he->hroot_out);
+ while (node) {
+ float percent;
+
+ child = rb_entry(node, struct hist_entry, rb_node);
+ percent = hist_entry__get_percent_limit(child);
+
+ if (!child->filtered && percent >= hb->min_pcnt) {
+ count++;
+
+ if (include_children && child->unfolded)
+ count += hierarchy_count_rows(hb, child, true);
+ }
+
+ node = rb_next(node);
+ }
+ return count;
+}
+
+static bool hist_entry__toggle_fold(struct hist_entry *he)
+{
+ if (!he)
+ return false;
+
+ if (!he->has_children)
return false;
- if (!ms->has_children)
+ he->unfolded = !he->unfolded;
+ return true;
+}
+
+static bool callchain_list__toggle_fold(struct callchain_list *cl)
+{
+ if (!cl)
return false;
- ms->unfolded = !ms->unfolded;
+ if (!cl->has_children)
+ return false;
+
+ cl->unfolded = !cl->unfolded;
return true;
}
@@ -152,10 +338,10 @@ static void callchain_node__init_have_children_rb_tree(struct callchain_node *no
list_for_each_entry(chain, &child->val, list) {
if (first) {
first = false;
- chain->ms.has_children = chain->list.next != &child->val ||
+ chain->has_children = chain->list.next != &child->val ||
!RB_EMPTY_ROOT(&child->rb_root);
} else
- chain->ms.has_children = chain->list.next == &child->val &&
+ chain->has_children = chain->list.next == &child->val &&
!RB_EMPTY_ROOT(&child->rb_root);
}
@@ -163,49 +349,157 @@ static void callchain_node__init_have_children_rb_tree(struct callchain_node *no
}
}
-static void callchain_node__init_have_children(struct callchain_node *node)
+static void callchain_node__init_have_children(struct callchain_node *node,
+ bool has_sibling)
{
struct callchain_list *chain;
- list_for_each_entry(chain, &node->val, list)
- chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root);
+ chain = list_entry(node->val.next, struct callchain_list, list);
+ chain->has_children = has_sibling;
+
+ if (!list_empty(&node->val)) {
+ chain = list_entry(node->val.prev, struct callchain_list, list);
+ chain->has_children = !RB_EMPTY_ROOT(&node->rb_root);
+ }
callchain_node__init_have_children_rb_tree(node);
}
static void callchain__init_have_children(struct rb_root *root)
{
- struct rb_node *nd;
+ struct rb_node *nd = rb_first(root);
+ bool has_sibling = nd && rb_next(nd);
for (nd = rb_first(root); nd; nd = rb_next(nd)) {
struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
- callchain_node__init_have_children(node);
+ callchain_node__init_have_children(node, has_sibling);
+ if (callchain_param.mode == CHAIN_FLAT ||
+ callchain_param.mode == CHAIN_FOLDED)
+ callchain_node__make_parent_list(node);
}
}
static void hist_entry__init_have_children(struct hist_entry *he)
{
- if (!he->init_have_children) {
- he->ms.has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
+ if (he->init_have_children)
+ return;
+
+ if (he->leaf) {
+ he->has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
callchain__init_have_children(&he->sorted_chain);
- he->init_have_children = true;
+ } else {
+ he->has_children = !RB_EMPTY_ROOT(&he->hroot_out.rb_root);
}
+
+ he->init_have_children = true;
+}
+
+static bool hist_browser__selection_has_children(struct hist_browser *browser)
+{
+ struct hist_entry *he = browser->he_selection;
+ struct map_symbol *ms = browser->selection;
+
+ if (!he || !ms)
+ return false;
+
+ if (ms == &he->ms)
+ return he->has_children;
+
+ return container_of(ms, struct callchain_list, ms)->has_children;
+}
+
+static bool hist_browser__selection_unfolded(struct hist_browser *browser)
+{
+ struct hist_entry *he = browser->he_selection;
+ struct map_symbol *ms = browser->selection;
+
+ if (!he || !ms)
+ return false;
+
+ if (ms == &he->ms)
+ return he->unfolded;
+
+ return container_of(ms, struct callchain_list, ms)->unfolded;
+}
+
+static char *hist_browser__selection_sym_name(struct hist_browser *browser, char *bf, size_t size)
+{
+ struct hist_entry *he = browser->he_selection;
+ struct map_symbol *ms = browser->selection;
+ struct callchain_list *callchain_entry;
+
+ if (!he || !ms)
+ return NULL;
+
+ if (ms == &he->ms) {
+ hist_entry__sym_snprintf(he, bf, size, 0);
+ return bf + 4; // skip the level, e.g. '[k] '
+ }
+
+ callchain_entry = container_of(ms, struct callchain_list, ms);
+ return callchain_list__sym_name(callchain_entry, bf, size, browser->show_dso);
}
static bool hist_browser__toggle_fold(struct hist_browser *browser)
{
- if (map_symbol__toggle_fold(browser->selection)) {
- struct hist_entry *he = browser->he_selection;
+ struct hist_entry *he = browser->he_selection;
+ struct map_symbol *ms = browser->selection;
+ struct callchain_list *cl = container_of(ms, struct callchain_list, ms);
+ bool has_children;
+
+ if (!he || !ms)
+ return false;
+
+ if (ms == &he->ms)
+ has_children = hist_entry__toggle_fold(he);
+ else
+ has_children = callchain_list__toggle_fold(cl);
+
+ if (has_children) {
+ int child_rows = 0;
hist_entry__init_have_children(he);
- browser->hists->nr_entries -= he->nr_rows;
+ browser->b.nr_entries -= he->nr_rows;
- if (he->ms.unfolded)
- he->nr_rows = callchain__count_rows(&he->sorted_chain);
+ if (he->leaf)
+ browser->nr_callchain_rows -= he->nr_rows;
else
+ browser->nr_hierarchy_entries -= he->nr_rows;
+
+ if (symbol_conf.report_hierarchy)
+ child_rows = hierarchy_count_rows(browser, he, true);
+
+ if (he->unfolded) {
+ if (he->leaf)
+ he->nr_rows = callchain__count_rows(
+ &he->sorted_chain);
+ else
+ he->nr_rows = hierarchy_count_rows(browser, he, false);
+
+ /* account grand children */
+ if (symbol_conf.report_hierarchy)
+ browser->b.nr_entries += child_rows - he->nr_rows;
+
+ if (!he->leaf && he->nr_rows == 0) {
+ he->has_no_entry = true;
+ he->nr_rows = 1;
+ }
+ } else {
+ if (symbol_conf.report_hierarchy)
+ browser->b.nr_entries -= child_rows - he->nr_rows;
+
+ if (he->has_no_entry)
+ he->has_no_entry = false;
+
he->nr_rows = 0;
- browser->hists->nr_entries += he->nr_rows;
- browser->b.nr_entries = browser->hists->nr_entries;
+ }
+
+ browser->b.nr_entries += he->nr_rows;
+
+ if (he->leaf)
+ browser->nr_callchain_rows += he->nr_rows;
+ else
+ browser->nr_hierarchy_entries += he->nr_rows;
return true;
}
@@ -226,8 +520,8 @@ static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool
list_for_each_entry(chain, &child->val, list) {
++n;
- map_symbol__set_folding(&chain->ms, unfold);
- has_children = chain->ms.has_children;
+ callchain_list__set_folding(chain, unfold);
+ has_children = chain->has_children;
}
if (has_children)
@@ -245,8 +539,8 @@ static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
list_for_each_entry(chain, &node->val, list) {
++n;
- map_symbol__set_folding(&chain->ms, unfold);
- has_children = chain->ms.has_children;
+ callchain_list__set_folding(chain, unfold);
+ has_children = chain->has_children;
}
if (has_children)
@@ -268,39 +562,98 @@ static int callchain__set_folding(struct rb_root *chain, bool unfold)
return n;
}
-static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
+static int hierarchy_set_folding(struct hist_browser *hb, struct hist_entry *he,
+ bool unfold __maybe_unused)
+{
+ float percent;
+ struct rb_node *nd;
+ struct hist_entry *child;
+ int n = 0;
+
+ for (nd = rb_first_cached(&he->hroot_out); nd; nd = rb_next(nd)) {
+ child = rb_entry(nd, struct hist_entry, rb_node);
+ percent = hist_entry__get_percent_limit(child);
+ if (!child->filtered && percent >= hb->min_pcnt)
+ n++;
+ }
+
+ return n;
+}
+
+static void hist_entry__set_folding(struct hist_entry *he,
+ struct hist_browser *hb, bool unfold)
{
hist_entry__init_have_children(he);
- map_symbol__set_folding(&he->ms, unfold);
+ he->unfolded = unfold ? he->has_children : false;
+
+ if (he->has_children) {
+ int n;
+
+ if (he->leaf)
+ n = callchain__set_folding(&he->sorted_chain, unfold);
+ else
+ n = hierarchy_set_folding(hb, he, unfold);
- if (he->ms.has_children) {
- int n = callchain__set_folding(&he->sorted_chain, unfold);
he->nr_rows = unfold ? n : 0;
} else
he->nr_rows = 0;
}
-static void hists__set_folding(struct hists *hists, bool unfold)
+static void
+__hist_browser__set_folding(struct hist_browser *browser, bool unfold)
{
struct rb_node *nd;
+ struct hist_entry *he;
+ double percent;
+
+ nd = rb_first_cached(&browser->hists->entries);
+ while (nd) {
+ he = rb_entry(nd, struct hist_entry, rb_node);
- hists->nr_entries = 0;
+ /* set folding state even if it's currently folded */
+ nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD);
- for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
- struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
- hist_entry__set_folding(he, unfold);
- hists->nr_entries += 1 + he->nr_rows;
+ hist_entry__set_folding(he, browser, unfold);
+
+ percent = hist_entry__get_percent_limit(he);
+ if (he->filtered || percent < browser->min_pcnt)
+ continue;
+
+ if (!he->depth || unfold)
+ browser->nr_hierarchy_entries++;
+ if (he->leaf)
+ browser->nr_callchain_rows += he->nr_rows;
+ else if (unfold && !hist_entry__has_hierarchy_children(he, browser->min_pcnt)) {
+ browser->nr_hierarchy_entries++;
+ he->has_no_entry = true;
+ he->nr_rows = 1;
+ } else
+ he->has_no_entry = false;
}
}
static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
{
- hists__set_folding(browser->hists, unfold);
- browser->b.nr_entries = browser->hists->nr_entries;
+ browser->nr_hierarchy_entries = 0;
+ browser->nr_callchain_rows = 0;
+ __hist_browser__set_folding(browser, unfold);
+
+ browser->b.nr_entries = hist_browser__nr_entries(browser);
/* Go to the start, we may be way after valid entries after a collapse */
ui_browser__reset_index(&browser->b);
}
+static void hist_browser__set_folding_selected(struct hist_browser *browser, bool unfold)
+{
+ if (!browser->he_selection)
+ return;
+
+ if (unfold == browser->he_selection->unfolded)
+ return;
+
+ hist_browser__toggle_fold(browser);
+}
+
static void ui_browser__warn_lost_events(struct ui_browser *browser)
{
ui_browser__warning(browser, 4,
@@ -310,403 +663,643 @@ static void ui_browser__warn_lost_events(struct ui_browser *browser)
"Or reduce the sampling frequency.");
}
-static void hist_browser__update_pcnt_entries(struct hist_browser *hb);
+static int hist_browser__title(struct hist_browser *browser, char *bf, size_t size)
+{
+ return browser->title ? browser->title(browser, bf, size) : 0;
+}
-static int hist_browser__run(struct hist_browser *browser, const char *ev_name,
- struct hist_browser_timer *hbt)
+static int hist_browser__handle_hotkey(struct hist_browser *browser, bool warn_lost_event, char *title, size_t size, int key)
+{
+ switch (key) {
+ case K_TIMER: {
+ struct hist_browser_timer *hbt = browser->hbt;
+ struct evsel *evsel = hists_to_evsel(browser->hists);
+ u64 nr_entries;
+
+ WARN_ON_ONCE(!hbt);
+
+ if (hbt)
+ hbt->timer(hbt->arg);
+
+ if (hist_browser__has_filter(browser) || symbol_conf.report_hierarchy)
+ hist_browser__update_nr_entries(browser);
+
+ nr_entries = hist_browser__nr_entries(browser);
+ ui_browser__update_nr_entries(&browser->b, nr_entries);
+
+ if (warn_lost_event &&
+ (evsel->evlist->stats.nr_lost_warned !=
+ evsel->evlist->stats.nr_events[PERF_RECORD_LOST])) {
+ evsel->evlist->stats.nr_lost_warned =
+ evsel->evlist->stats.nr_events[PERF_RECORD_LOST];
+ ui_browser__warn_lost_events(&browser->b);
+ }
+
+ hist_browser__title(browser, title, size);
+ ui_browser__show_title(&browser->b, title);
+ break;
+ }
+ case 'D': { /* Debug */
+ struct hist_entry *h = rb_entry(browser->b.top, struct hist_entry, rb_node);
+ static int seq;
+
+ ui_helpline__pop();
+ ui_helpline__fpush("%d: nr_ent=(%d,%d), etl: %d, rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
+ seq++, browser->b.nr_entries, browser->hists->nr_entries,
+ browser->b.extra_title_lines, browser->b.rows,
+ browser->b.index, browser->b.top_idx, h->row_offset, h->nr_rows);
+ }
+ break;
+ case 'C':
+ /* Collapse the whole world. */
+ hist_browser__set_folding(browser, false);
+ break;
+ case 'c':
+ /* Collapse the selected entry. */
+ hist_browser__set_folding_selected(browser, false);
+ break;
+ case 'E':
+ /* Expand the whole world. */
+ hist_browser__set_folding(browser, true);
+ break;
+ case 'e':
+ /* Toggle expand/collapse the selected entry. */
+ hist_browser__toggle_fold(browser);
+ break;
+ case 'H':
+ browser->show_headers = !browser->show_headers;
+ hist_browser__update_rows(browser);
+ break;
+ case '+':
+ if (hist_browser__toggle_fold(browser))
+ break;
+ /* fall thru */
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+int hist_browser__run(struct hist_browser *browser, const char *help,
+ bool warn_lost_event, int key)
{
- int key;
char title[160];
+ struct hist_browser_timer *hbt = browser->hbt;
int delay_secs = hbt ? hbt->refresh : 0;
browser->b.entries = &browser->hists->entries;
- browser->b.nr_entries = browser->hists->nr_entries;
- if (browser->min_pcnt)
- browser->b.nr_entries = browser->nr_pcnt_entries;
+ browser->b.nr_entries = hist_browser__nr_entries(browser);
- hist_browser__refresh_dimensions(browser);
- hists__browser_title(browser->hists, title, sizeof(title), ev_name);
+ hist_browser__title(browser, title, sizeof(title));
- if (ui_browser__show(&browser->b, title,
- "Press '?' for help on key bindings") < 0)
+ if (ui_browser__show(&browser->b, title, "%s", help) < 0)
return -1;
+ if (key && hist_browser__handle_hotkey(browser, warn_lost_event, title, sizeof(title), key))
+ goto out;
+
while (1) {
key = ui_browser__run(&browser->b, delay_secs);
- switch (key) {
- case K_TIMER: {
- u64 nr_entries;
- hbt->timer(hbt->arg);
-
- if (browser->min_pcnt) {
- hist_browser__update_pcnt_entries(browser);
- nr_entries = browser->nr_pcnt_entries;
- } else {
- nr_entries = browser->hists->nr_entries;
- }
-
- ui_browser__update_nr_entries(&browser->b, nr_entries);
-
- if (browser->hists->stats.nr_lost_warned !=
- browser->hists->stats.nr_events[PERF_RECORD_LOST]) {
- browser->hists->stats.nr_lost_warned =
- browser->hists->stats.nr_events[PERF_RECORD_LOST];
- ui_browser__warn_lost_events(&browser->b);
- }
-
- hists__browser_title(browser->hists, title, sizeof(title), ev_name);
- ui_browser__show_title(&browser->b, title);
- continue;
- }
- case 'D': { /* Debug */
- static int seq;
- struct hist_entry *h = rb_entry(browser->b.top,
- struct hist_entry, rb_node);
- ui_helpline__pop();
- ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
- seq++, browser->b.nr_entries,
- browser->hists->nr_entries,
- browser->b.height,
- browser->b.index,
- browser->b.top_idx,
- h->row_offset, h->nr_rows);
- }
+ if (hist_browser__handle_hotkey(browser, warn_lost_event, title, sizeof(title), key))
break;
- case 'C':
- /* Collapse the whole world. */
- hist_browser__set_folding(browser, false);
- break;
- case 'E':
- /* Expand the whole world. */
- hist_browser__set_folding(browser, true);
- break;
- case K_ENTER:
- if (hist_browser__toggle_fold(browser))
- break;
- /* fall thru */
- default:
- goto out;
- }
}
out:
ui_browser__hide(&browser->b);
return key;
}
-static char *callchain_list__sym_name(struct callchain_list *cl,
- char *bf, size_t bfsize, bool show_dso)
+struct callchain_print_arg {
+ /* for hists browser */
+ off_t row_offset;
+ bool is_current_entry;
+
+ /* for file dump */
+ FILE *fp;
+ int printed;
+};
+
+typedef void (*print_callchain_entry_fn)(struct hist_browser *browser,
+ struct callchain_list *chain,
+ const char *str, int offset,
+ unsigned short row,
+ struct callchain_print_arg *arg);
+
+static void hist_browser__show_callchain_entry(struct hist_browser *browser,
+ struct callchain_list *chain,
+ const char *str, int offset,
+ unsigned short row,
+ struct callchain_print_arg *arg)
{
- int printed;
+ int color, width;
+ char folded_sign = callchain_list__folded(chain);
+ bool show_annotated = browser->show_dso && chain->ms.sym && symbol__annotation(chain->ms.sym)->src;
+
+ color = HE_COLORSET_NORMAL;
+ width = browser->b.width - (offset + 2);
+ if (ui_browser__is_current_entry(&browser->b, row)) {
+ browser->selection = &chain->ms;
+ color = HE_COLORSET_SELECTED;
+ arg->is_current_entry = true;
+ }
- if (cl->ms.sym)
- printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
- else
- printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip);
+ ui_browser__set_color(&browser->b, color);
+ ui_browser__gotorc(&browser->b, row, 0);
+ ui_browser__write_nstring(&browser->b, " ", offset);
+ ui_browser__printf(&browser->b, "%c", folded_sign);
+ ui_browser__write_graph(&browser->b, show_annotated ? SLSMG_RARROW_CHAR : ' ');
+ ui_browser__write_nstring(&browser->b, str, width);
+}
+
+static void hist_browser__fprintf_callchain_entry(struct hist_browser *b __maybe_unused,
+ struct callchain_list *chain,
+ const char *str, int offset,
+ unsigned short row __maybe_unused,
+ struct callchain_print_arg *arg)
+{
+ char folded_sign = callchain_list__folded(chain);
+
+ arg->printed += fprintf(arg->fp, "%*s%c %s\n", offset, " ",
+ folded_sign, str);
+}
- if (show_dso)
- scnprintf(bf + printed, bfsize - printed, " %s",
- cl->ms.map ? cl->ms.map->dso->short_name : "unknown");
+typedef bool (*check_output_full_fn)(struct hist_browser *browser,
+ unsigned short row);
- return bf;
+static bool hist_browser__check_output_full(struct hist_browser *browser,
+ unsigned short row)
+{
+ return browser->b.rows == row;
+}
+
+static bool hist_browser__check_dump_full(struct hist_browser *browser __maybe_unused,
+ unsigned short row __maybe_unused)
+{
+ return false;
}
#define LEVEL_OFFSET_STEP 3
-static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browser,
- struct callchain_node *chain_node,
- u64 total, int level,
- unsigned short row,
- off_t *row_offset,
- bool *is_current_entry)
+static int hist_browser__show_callchain_list(struct hist_browser *browser,
+ struct callchain_node *node,
+ struct callchain_list *chain,
+ unsigned short row, u64 total,
+ bool need_percent, int offset,
+ print_callchain_entry_fn print,
+ struct callchain_print_arg *arg)
+{
+ char bf[1024], *alloc_str;
+ char buf[64], *alloc_str2;
+ const char *str;
+ int ret = 1;
+
+ if (arg->row_offset != 0) {
+ arg->row_offset--;
+ return 0;
+ }
+
+ alloc_str = NULL;
+ alloc_str2 = NULL;
+
+ str = callchain_list__sym_name(chain, bf, sizeof(bf),
+ browser->show_dso);
+
+ if (symbol_conf.show_branchflag_count) {
+ callchain_list_counts__printf_value(chain, NULL,
+ buf, sizeof(buf));
+
+ if (asprintf(&alloc_str2, "%s%s", str, buf) < 0)
+ str = "Not enough memory!";
+ else
+ str = alloc_str2;
+ }
+
+ if (need_percent) {
+ callchain_node__scnprintf_value(node, buf, sizeof(buf),
+ total);
+
+ if (asprintf(&alloc_str, "%s %s", buf, str) < 0)
+ str = "Not enough memory!";
+ else
+ str = alloc_str;
+ }
+
+ print(browser, chain, str, offset, row, arg);
+ free(alloc_str);
+ free(alloc_str2);
+
+ return ret;
+}
+
+static bool check_percent_display(struct rb_node *node, u64 parent_total)
+{
+ struct callchain_node *child;
+
+ if (node == NULL)
+ return false;
+
+ if (rb_next(node))
+ return true;
+
+ child = rb_entry(node, struct callchain_node, rb_node);
+ return callchain_cumul_hits(child) != parent_total;
+}
+
+static int hist_browser__show_callchain_flat(struct hist_browser *browser,
+ struct rb_root *root,
+ unsigned short row, u64 total,
+ u64 parent_total,
+ print_callchain_entry_fn print,
+ struct callchain_print_arg *arg,
+ check_output_full_fn is_output_full)
{
struct rb_node *node;
- int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
- u64 new_total, remaining;
+ int first_row = row, offset = LEVEL_OFFSET_STEP;
+ bool need_percent;
- if (callchain_param.mode == CHAIN_GRAPH_REL)
- new_total = chain_node->children_hit;
- else
- new_total = total;
+ node = rb_first(root);
+ need_percent = check_percent_display(node, parent_total);
- remaining = new_total;
- node = rb_first(&chain_node->rb_root);
while (node) {
struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
struct rb_node *next = rb_next(node);
- u64 cumul = callchain_cumul_hits(child);
struct callchain_list *chain;
char folded_sign = ' ';
int first = true;
int extra_offset = 0;
- remaining -= cumul;
-
- list_for_each_entry(chain, &child->val, list) {
- char bf[1024], *alloc_str;
- const char *str;
- int color;
+ list_for_each_entry(chain, &child->parent_val, list) {
bool was_first = first;
if (first)
first = false;
- else
+ else if (need_percent)
extra_offset = LEVEL_OFFSET_STEP;
folded_sign = callchain_list__folded(chain);
- if (*row_offset != 0) {
- --*row_offset;
- goto do_next;
- }
- alloc_str = NULL;
- str = callchain_list__sym_name(chain, bf, sizeof(bf),
- browser->show_dso);
- if (was_first) {
- double percent = cumul * 100.0 / new_total;
+ row += hist_browser__show_callchain_list(browser, child,
+ chain, row, total,
+ was_first && need_percent,
+ offset + extra_offset,
+ print, arg);
- if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
- str = "Not enough memory!";
- else
- str = alloc_str;
- }
+ if (is_output_full(browser, row))
+ goto out;
- color = HE_COLORSET_NORMAL;
- width = browser->b.width - (offset + extra_offset + 2);
- if (ui_browser__is_current_entry(&browser->b, row)) {
- browser->selection = &chain->ms;
- color = HE_COLORSET_SELECTED;
- *is_current_entry = true;
- }
+ if (folded_sign == '+')
+ goto next;
+ }
- ui_browser__set_color(&browser->b, color);
- ui_browser__gotorc(&browser->b, row, 0);
- slsmg_write_nstring(" ", offset + extra_offset);
- slsmg_printf("%c ", folded_sign);
- slsmg_write_nstring(str, width);
- free(alloc_str);
+ list_for_each_entry(chain, &child->val, list) {
+ bool was_first = first;
- if (++row == browser->b.height)
+ if (first)
+ first = false;
+ else if (need_percent)
+ extra_offset = LEVEL_OFFSET_STEP;
+
+ folded_sign = callchain_list__folded(chain);
+
+ row += hist_browser__show_callchain_list(browser, child,
+ chain, row, total,
+ was_first && need_percent,
+ offset + extra_offset,
+ print, arg);
+
+ if (is_output_full(browser, row))
goto out;
-do_next:
+
if (folded_sign == '+')
break;
}
- if (folded_sign == '-') {
- const int new_level = level + (extra_offset ? 2 : 1);
- row += hist_browser__show_callchain_node_rb_tree(browser, child, new_total,
- new_level, row, row_offset,
- is_current_entry);
- }
- if (row == browser->b.height)
- goto out;
+next:
+ if (is_output_full(browser, row))
+ break;
node = next;
}
out:
return row - first_row;
}
-static int hist_browser__show_callchain_node(struct hist_browser *browser,
- struct callchain_node *node,
- int level, unsigned short row,
- off_t *row_offset,
- bool *is_current_entry)
+static char *hist_browser__folded_callchain_str(struct hist_browser *browser,
+ struct callchain_list *chain,
+ char *value_str, char *old_str)
{
- struct callchain_list *chain;
- int first_row = row,
- offset = level * LEVEL_OFFSET_STEP,
- width = browser->b.width - offset;
- char folded_sign = ' ';
+ char bf[1024];
+ const char *str;
+ char *new;
+
+ str = callchain_list__sym_name(chain, bf, sizeof(bf),
+ browser->show_dso);
+ if (old_str) {
+ if (asprintf(&new, "%s%s%s", old_str,
+ symbol_conf.field_sep ?: ";", str) < 0)
+ new = NULL;
+ } else {
+ if (value_str) {
+ if (asprintf(&new, "%s %s", value_str, str) < 0)
+ new = NULL;
+ } else {
+ if (asprintf(&new, "%s", str) < 0)
+ new = NULL;
+ }
+ }
+ return new;
+}
- list_for_each_entry(chain, &node->val, list) {
- char bf[1024], *s;
- int color;
+static int hist_browser__show_callchain_folded(struct hist_browser *browser,
+ struct rb_root *root,
+ unsigned short row, u64 total,
+ u64 parent_total,
+ print_callchain_entry_fn print,
+ struct callchain_print_arg *arg,
+ check_output_full_fn is_output_full)
+{
+ struct rb_node *node;
+ int first_row = row, offset = LEVEL_OFFSET_STEP;
+ bool need_percent;
- folded_sign = callchain_list__folded(chain);
+ node = rb_first(root);
+ need_percent = check_percent_display(node, parent_total);
- if (*row_offset != 0) {
- --*row_offset;
- continue;
+ while (node) {
+ struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
+ struct rb_node *next = rb_next(node);
+ struct callchain_list *chain, *first_chain = NULL;
+ int first = true;
+ char *value_str = NULL, *value_str_alloc = NULL;
+ char *chain_str = NULL, *chain_str_alloc = NULL;
+
+ if (arg->row_offset != 0) {
+ arg->row_offset--;
+ goto next;
}
- color = HE_COLORSET_NORMAL;
- if (ui_browser__is_current_entry(&browser->b, row)) {
- browser->selection = &chain->ms;
- color = HE_COLORSET_SELECTED;
- *is_current_entry = true;
+ if (need_percent) {
+ char buf[64];
+
+ callchain_node__scnprintf_value(child, buf, sizeof(buf), total);
+ if (asprintf(&value_str, "%s", buf) < 0) {
+ value_str = (char *)"<...>";
+ goto do_print;
+ }
+ value_str_alloc = value_str;
}
- s = callchain_list__sym_name(chain, bf, sizeof(bf),
- browser->show_dso);
- ui_browser__gotorc(&browser->b, row, 0);
- ui_browser__set_color(&browser->b, color);
- slsmg_write_nstring(" ", offset);
- slsmg_printf("%c ", folded_sign);
- slsmg_write_nstring(s, width - 2);
+ list_for_each_entry(chain, &child->parent_val, list) {
+ chain_str = hist_browser__folded_callchain_str(browser,
+ chain, value_str, chain_str);
+ if (first) {
+ first = false;
+ first_chain = chain;
+ }
- if (++row == browser->b.height)
- goto out;
- }
+ if (chain_str == NULL) {
+ chain_str = (char *)"Not enough memory!";
+ goto do_print;
+ }
- if (folded_sign == '-')
- row += hist_browser__show_callchain_node_rb_tree(browser, node,
- browser->hists->stats.total_period,
- level + 1, row,
- row_offset,
- is_current_entry);
-out:
- return row - first_row;
-}
+ chain_str_alloc = chain_str;
+ }
-static int hist_browser__show_callchain(struct hist_browser *browser,
- struct rb_root *chain,
- int level, unsigned short row,
- off_t *row_offset,
- bool *is_current_entry)
-{
- struct rb_node *nd;
- int first_row = row;
+ list_for_each_entry(chain, &child->val, list) {
+ chain_str = hist_browser__folded_callchain_str(browser,
+ chain, value_str, chain_str);
+ if (first) {
+ first = false;
+ first_chain = chain;
+ }
- for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
- struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+ if (chain_str == NULL) {
+ chain_str = (char *)"Not enough memory!";
+ goto do_print;
+ }
+
+ chain_str_alloc = chain_str;
+ }
+
+do_print:
+ print(browser, first_chain, chain_str, offset, row++, arg);
+ free(value_str_alloc);
+ free(chain_str_alloc);
- row += hist_browser__show_callchain_node(browser, node, level,
- row, row_offset,
- is_current_entry);
- if (row == browser->b.height)
+next:
+ if (is_output_full(browser, row))
break;
+ node = next;
}
return row - first_row;
}
-struct hpp_arg {
- struct ui_browser *b;
- char folded_sign;
- bool current_entry;
-};
-
-static int __hpp__color_callchain(struct hpp_arg *arg)
+static int hist_browser__show_callchain_graph(struct hist_browser *browser,
+ struct rb_root *root, int level,
+ unsigned short row, u64 total,
+ u64 parent_total,
+ print_callchain_entry_fn print,
+ struct callchain_print_arg *arg,
+ check_output_full_fn is_output_full)
{
- if (!symbol_conf.use_callchain)
- return 0;
+ struct rb_node *node;
+ int first_row = row, offset = level * LEVEL_OFFSET_STEP;
+ bool need_percent;
+ u64 percent_total = total;
- slsmg_printf("%c ", arg->folded_sign);
- return 2;
-}
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ percent_total = parent_total;
-static int __hpp__color_fmt(struct perf_hpp *hpp, struct hist_entry *he,
- u64 (*get_field)(struct hist_entry *),
- int (*callchain_cb)(struct hpp_arg *))
-{
- int ret = 0;
- double percent = 0.0;
- struct hists *hists = he->hists;
- struct hpp_arg *arg = hpp->ptr;
+ node = rb_first(root);
+ need_percent = check_percent_display(node, parent_total);
- if (hists->stats.total_period)
- percent = 100.0 * get_field(he) / hists->stats.total_period;
+ while (node) {
+ struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
+ struct rb_node *next = rb_next(node);
+ struct callchain_list *chain;
+ char folded_sign = ' ';
+ int first = true;
+ int extra_offset = 0;
- ui_browser__set_percent_color(arg->b, percent, arg->current_entry);
+ list_for_each_entry(chain, &child->val, list) {
+ bool was_first = first;
- if (callchain_cb)
- ret += callchain_cb(arg);
+ if (first)
+ first = false;
+ else if (need_percent)
+ extra_offset = LEVEL_OFFSET_STEP;
- ret += scnprintf(hpp->buf, hpp->size, "%6.2f%%", percent);
- slsmg_printf("%s", hpp->buf);
+ folded_sign = callchain_list__folded(chain);
- if (symbol_conf.event_group) {
- int prev_idx, idx_delta;
- struct perf_evsel *evsel = hists_to_evsel(hists);
- struct hist_entry *pair;
- int nr_members = evsel->nr_members;
+ row += hist_browser__show_callchain_list(browser, child,
+ chain, row, percent_total,
+ was_first && need_percent,
+ offset + extra_offset,
+ print, arg);
- if (nr_members <= 1)
- goto out;
+ if (is_output_full(browser, row))
+ goto out;
- prev_idx = perf_evsel__group_idx(evsel);
+ if (folded_sign == '+')
+ break;
+ }
- list_for_each_entry(pair, &he->pairs.head, pairs.node) {
- u64 period = get_field(pair);
- u64 total = pair->hists->stats.total_period;
+ if (folded_sign == '-') {
+ const int new_level = level + (extra_offset ? 2 : 1);
- if (!total)
- continue;
+ row += hist_browser__show_callchain_graph(browser, &child->rb_root,
+ new_level, row, total,
+ child->children_hit,
+ print, arg, is_output_full);
+ }
+ if (is_output_full(browser, row))
+ break;
+ node = next;
+ }
+out:
+ return row - first_row;
+}
- evsel = hists_to_evsel(pair->hists);
- idx_delta = perf_evsel__group_idx(evsel) - prev_idx - 1;
+static int hist_browser__show_callchain(struct hist_browser *browser,
+ struct hist_entry *entry, int level,
+ unsigned short row,
+ print_callchain_entry_fn print,
+ struct callchain_print_arg *arg,
+ check_output_full_fn is_output_full)
+{
+ u64 total = hists__total_period(entry->hists);
+ u64 parent_total;
+ int printed;
- while (idx_delta--) {
- /*
- * zero-fill group members in the middle which
- * have no sample
- */
- ui_browser__set_percent_color(arg->b, 0.0,
- arg->current_entry);
- ret += scnprintf(hpp->buf, hpp->size,
- " %6.2f%%", 0.0);
- slsmg_printf("%s", hpp->buf);
- }
+ if (symbol_conf.cumulate_callchain)
+ parent_total = entry->stat_acc->period;
+ else
+ parent_total = entry->stat.period;
+
+ if (callchain_param.mode == CHAIN_FLAT) {
+ printed = hist_browser__show_callchain_flat(browser,
+ &entry->sorted_chain, row,
+ total, parent_total, print, arg,
+ is_output_full);
+ } else if (callchain_param.mode == CHAIN_FOLDED) {
+ printed = hist_browser__show_callchain_folded(browser,
+ &entry->sorted_chain, row,
+ total, parent_total, print, arg,
+ is_output_full);
+ } else {
+ printed = hist_browser__show_callchain_graph(browser,
+ &entry->sorted_chain, level, row,
+ total, parent_total, print, arg,
+ is_output_full);
+ }
- percent = 100.0 * period / total;
- ui_browser__set_percent_color(arg->b, percent,
- arg->current_entry);
- ret += scnprintf(hpp->buf, hpp->size,
- " %6.2f%%", percent);
- slsmg_printf("%s", hpp->buf);
+ if (arg->is_current_entry)
+ browser->he_selection = entry;
- prev_idx = perf_evsel__group_idx(evsel);
- }
+ return printed;
+}
+
+struct hpp_arg {
+ struct ui_browser *b;
+ char folded_sign;
+ bool current_entry;
+};
- idx_delta = nr_members - prev_idx - 1;
+int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
+{
+ struct hpp_arg *arg = hpp->ptr;
+ int ret, len;
+ va_list args;
+ double percent;
- while (idx_delta--) {
- /*
- * zero-fill group members at last which have no sample
- */
- ui_browser__set_percent_color(arg->b, 0.0,
- arg->current_entry);
- ret += scnprintf(hpp->buf, hpp->size,
- " %6.2f%%", 0.0);
- slsmg_printf("%s", hpp->buf);
- }
- }
-out:
- if (!arg->current_entry || !arg->b->navkeypressed)
- ui_browser__set_color(arg->b, HE_COLORSET_NORMAL);
+ va_start(args, fmt);
+ len = va_arg(args, int);
+ percent = va_arg(args, double);
+ va_end(args);
+
+ ui_browser__set_percent_color(arg->b, percent, arg->current_entry);
+
+ ret = scnprintf(hpp->buf, hpp->size, fmt, len, percent);
+ ui_browser__printf(arg->b, "%s", hpp->buf);
return ret;
}
-#define __HPP_COLOR_PERCENT_FN(_type, _field, _cb) \
+#define __HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \
static u64 __hpp_get_##_field(struct hist_entry *he) \
{ \
return he->stat._field; \
} \
\
-static int hist_browser__hpp_color_##_type(struct perf_hpp *hpp, \
- struct hist_entry *he) \
+static int \
+hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
+{ \
+ return hpp__fmt(fmt, hpp, he, __hpp_get_##_field, " %*.2f%%", \
+ __hpp__slsmg_color_printf, _fmttype); \
+}
+
+#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field, _fmttype) \
+static u64 __hpp_get_acc_##_field(struct hist_entry *he) \
+{ \
+ return he->stat_acc->_field; \
+} \
+ \
+static int \
+hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
{ \
- return __hpp__color_fmt(hpp, he, __hpp_get_##_field, _cb); \
+ if (!symbol_conf.cumulate_callchain) { \
+ struct hpp_arg *arg = hpp->ptr; \
+ int len = fmt->user_len ?: fmt->len; \
+ int ret = scnprintf(hpp->buf, hpp->size, \
+ "%*s", len, "N/A"); \
+ ui_browser__printf(arg->b, "%s", hpp->buf); \
+ \
+ return ret; \
+ } \
+ return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field, \
+ " %*.2f%%", __hpp__slsmg_color_printf, \
+ _fmttype); \
}
-__HPP_COLOR_PERCENT_FN(overhead, period, __hpp__color_callchain)
-__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys, NULL)
-__HPP_COLOR_PERCENT_FN(overhead_us, period_us, NULL)
-__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys, NULL)
-__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us, NULL)
+#define __HPP_COLOR_MEM_STAT_FN(_name, _type) \
+static int \
+hist_browser__hpp_color_mem_stat_##_name(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
+{ \
+ return hpp__fmt_mem_stat(fmt, hpp, he, PERF_MEM_STAT_##_type, \
+ " %5.1f%%", __hpp__slsmg_color_printf);\
+}
+
+__HPP_COLOR_PERCENT_FN(overhead, period, PERF_HPP_FMT_TYPE__PERCENT)
+__HPP_COLOR_PERCENT_FN(latency, latency, PERF_HPP_FMT_TYPE__LATENCY)
+__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys, PERF_HPP_FMT_TYPE__PERCENT)
+__HPP_COLOR_PERCENT_FN(overhead_us, period_us, PERF_HPP_FMT_TYPE__PERCENT)
+__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys, PERF_HPP_FMT_TYPE__PERCENT)
+__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us, PERF_HPP_FMT_TYPE__PERCENT)
+__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period, PERF_HPP_FMT_TYPE__PERCENT)
+__HPP_COLOR_ACC_PERCENT_FN(latency_acc, latency, PERF_HPP_FMT_TYPE__LATENCY)
+__HPP_COLOR_MEM_STAT_FN(op, OP)
+__HPP_COLOR_MEM_STAT_FN(cache, CACHE)
+__HPP_COLOR_MEM_STAT_FN(memory, MEMORY)
+__HPP_COLOR_MEM_STAT_FN(snoop, SNOOP)
+__HPP_COLOR_MEM_STAT_FN(dtlb, DTLB)
#undef __HPP_COLOR_PERCENT_FN
+#undef __HPP_COLOR_ACC_PERCENT_FN
+#undef __HPP_COLOR_MEM_STAT_FN
void hist_browser__init_hpp(void)
{
- perf_hpp__column_enable(PERF_HPP__OVERHEAD);
-
- perf_hpp__init();
-
perf_hpp__format[PERF_HPP__OVERHEAD].color =
hist_browser__hpp_color_overhead;
+ perf_hpp__format[PERF_HPP__LATENCY].color =
+ hist_browser__hpp_color_latency;
perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
hist_browser__hpp_color_overhead_sys;
perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
@@ -715,17 +1308,33 @@ void hist_browser__init_hpp(void)
hist_browser__hpp_color_overhead_guest_sys;
perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
hist_browser__hpp_color_overhead_guest_us;
+ perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
+ hist_browser__hpp_color_overhead_acc;
+ perf_hpp__format[PERF_HPP__LATENCY_ACC].color =
+ hist_browser__hpp_color_latency_acc;
+ perf_hpp__format[PERF_HPP__MEM_STAT_OP].color =
+ hist_browser__hpp_color_mem_stat_op;
+ perf_hpp__format[PERF_HPP__MEM_STAT_CACHE].color =
+ hist_browser__hpp_color_mem_stat_cache;
+ perf_hpp__format[PERF_HPP__MEM_STAT_MEMORY].color =
+ hist_browser__hpp_color_mem_stat_memory;
+ perf_hpp__format[PERF_HPP__MEM_STAT_SNOOP].color =
+ hist_browser__hpp_color_mem_stat_snoop;
+ perf_hpp__format[PERF_HPP__MEM_STAT_DTLB].color =
+ hist_browser__hpp_color_mem_stat_dtlb;
+
+ res_sample_init();
}
static int hist_browser__show_entry(struct hist_browser *browser,
struct hist_entry *entry,
unsigned short row)
{
- char s[256];
int printed = 0;
int width = browser->b.width;
char folded_sign = ' ';
bool current_entry = ui_browser__is_current_entry(&browser->b, row);
+ bool use_callchain = hist_entry__has_callchains(entry) && symbol_conf.use_callchain;
off_t row_offset = entry->row_offset;
bool first = true;
struct perf_hpp_fmt *fmt;
@@ -735,69 +1344,512 @@ static int hist_browser__show_entry(struct hist_browser *browser,
browser->selection = &entry->ms;
}
- if (symbol_conf.use_callchain) {
+ if (use_callchain) {
hist_entry__init_have_children(entry);
folded_sign = hist_entry__folded(entry);
}
if (row_offset == 0) {
struct hpp_arg arg = {
- .b = &browser->b,
+ .b = &browser->b,
.folded_sign = folded_sign,
.current_entry = current_entry,
};
- struct perf_hpp hpp = {
- .buf = s,
- .size = sizeof(s),
- .ptr = &arg,
- };
+ int column = 0;
ui_browser__gotorc(&browser->b, row, 0);
- perf_hpp__for_each_format(fmt) {
- if (!first) {
- slsmg_printf(" ");
+ hists__for_each_format(browser->hists, fmt) {
+ char s[2048];
+ struct perf_hpp hpp = {
+ .buf = s,
+ .size = sizeof(s),
+ .ptr = &arg,
+ };
+
+ if (perf_hpp__should_skip(fmt, entry->hists) ||
+ column++ < browser->b.horiz_scroll)
+ 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 (use_callchain) {
+ ui_browser__printf(&browser->b, "%c ", folded_sign);
+ width -= 2;
+ }
+ first = false;
+ } else {
+ ui_browser__printf(&browser->b, " ");
width -= 2;
}
- first = false;
if (fmt->color) {
- width -= fmt->color(&hpp, entry);
+ int ret = fmt->color(fmt, &hpp, entry);
+ hist_entry__snprintf_alignment(entry, &hpp, fmt, ret);
+ /*
+ * fmt->color() already used ui_browser to
+ * print the non alignment bits, skip it (+ret):
+ */
+ ui_browser__printf(&browser->b, "%s", s + ret);
} else {
- width -= fmt->entry(&hpp, entry);
- slsmg_printf("%s", s);
+ hist_entry__snprintf_alignment(entry, &hpp, fmt, fmt->entry(fmt, &hpp, entry));
+ ui_browser__printf(&browser->b, "%s", s);
}
+ width -= hpp.buf - s;
}
/* The scroll bar isn't being used */
if (!browser->b.navkeypressed)
width += 1;
- hist_entry__sort_snprintf(entry, s, sizeof(s), browser->hists);
- slsmg_write_nstring(s, width);
+ ui_browser__write_nstring(&browser->b, "", width);
+
++row;
++printed;
} else
--row_offset;
- if (folded_sign == '-' && row != browser->b.height) {
- printed += hist_browser__show_callchain(browser, &entry->sorted_chain,
- 1, row, &row_offset,
- &current_entry);
- if (current_entry)
- browser->he_selection = entry;
+ if (folded_sign == '-' && row != browser->b.rows) {
+ struct callchain_print_arg arg = {
+ .row_offset = row_offset,
+ .is_current_entry = current_entry,
+ };
+
+ printed += hist_browser__show_callchain(browser,
+ entry, 1, row,
+ hist_browser__show_callchain_entry,
+ &arg,
+ hist_browser__check_output_full);
+ }
+
+ return printed;
+}
+
+static int hist_browser__show_hierarchy_entry(struct hist_browser *browser,
+ struct hist_entry *entry,
+ unsigned short row,
+ int level)
+{
+ int printed = 0;
+ int width = browser->b.width;
+ char folded_sign = ' ';
+ bool current_entry = ui_browser__is_current_entry(&browser->b, row);
+ off_t row_offset = entry->row_offset;
+ bool first = true;
+ struct perf_hpp_fmt *fmt;
+ struct perf_hpp_list_node *fmt_node;
+ struct hpp_arg arg = {
+ .b = &browser->b,
+ .current_entry = current_entry,
+ };
+ int column = 0;
+ int hierarchy_indent = (entry->hists->nr_hpp_node - 2) * HIERARCHY_INDENT;
+
+ if (current_entry) {
+ browser->he_selection = entry;
+ browser->selection = &entry->ms;
+ }
+
+ hist_entry__init_have_children(entry);
+ folded_sign = hist_entry__folded(entry);
+ arg.folded_sign = folded_sign;
+
+ if (entry->leaf && row_offset) {
+ row_offset--;
+ goto show_callchain;
+ }
+
+ ui_browser__gotorc(&browser->b, row, 0);
+
+ 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);
+
+ ui_browser__write_nstring(&browser->b, "", level * HIERARCHY_INDENT);
+ width -= level * HIERARCHY_INDENT;
+
+ /* the first hpp_list_node is for overhead columns */
+ fmt_node = list_first_entry(&entry->hists->hpp_formats,
+ struct perf_hpp_list_node, list);
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
+ char s[2048];
+ struct perf_hpp hpp = {
+ .buf = s,
+ .size = sizeof(s),
+ .ptr = &arg,
+ };
+
+ if (perf_hpp__should_skip(fmt, entry->hists) ||
+ column++ < browser->b.horiz_scroll)
+ 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) {
+ ui_browser__printf(&browser->b, "%c ", folded_sign);
+ width -= 2;
+ first = false;
+ } else {
+ ui_browser__printf(&browser->b, " ");
+ width -= 2;
+ }
+
+ if (fmt->color) {
+ int ret = fmt->color(fmt, &hpp, entry);
+ hist_entry__snprintf_alignment(entry, &hpp, fmt, ret);
+ /*
+ * fmt->color() already used ui_browser to
+ * print the non alignment bits, skip it (+ret):
+ */
+ ui_browser__printf(&browser->b, "%s", s + ret);
+ } else {
+ int ret = fmt->entry(fmt, &hpp, entry);
+ hist_entry__snprintf_alignment(entry, &hpp, fmt, ret);
+ ui_browser__printf(&browser->b, "%s", s);
+ }
+ width -= hpp.buf - s;
+ }
+
+ if (!first) {
+ ui_browser__write_nstring(&browser->b, "", hierarchy_indent);
+ width -= hierarchy_indent;
+ }
+
+ if (column >= browser->b.horiz_scroll) {
+ char s[2048];
+ struct perf_hpp hpp = {
+ .buf = s,
+ .size = sizeof(s),
+ .ptr = &arg,
+ };
+
+ 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);
+ }
+
+ perf_hpp_list__for_each_format(entry->hpp_list, fmt) {
+ if (first) {
+ ui_browser__printf(&browser->b, "%c ", folded_sign);
+ first = false;
+ } else {
+ ui_browser__write_nstring(&browser->b, "", 2);
+ }
+
+ width -= 2;
+
+ /*
+ * No need to call hist_entry__snprintf_alignment()
+ * since this fmt is always the last column in the
+ * hierarchy mode.
+ */
+ if (fmt->color) {
+ width -= fmt->color(fmt, &hpp, entry);
+ } else {
+ int i = 0;
+
+ width -= fmt->entry(fmt, &hpp, entry);
+ ui_browser__printf(&browser->b, "%s", skip_spaces(s));
+
+ while (isspace(s[i++]))
+ width++;
+ }
+ }
+ }
+
+ /* The scroll bar isn't being used */
+ if (!browser->b.navkeypressed)
+ width += 1;
+
+ ui_browser__write_nstring(&browser->b, "", width);
+
+ ++row;
+ ++printed;
+
+show_callchain:
+ if (entry->leaf && folded_sign == '-' && row != browser->b.rows) {
+ struct callchain_print_arg carg = {
+ .row_offset = row_offset,
+ };
+
+ printed += hist_browser__show_callchain(browser, entry,
+ level + 1, row,
+ hist_browser__show_callchain_entry, &carg,
+ hist_browser__check_output_full);
}
return printed;
}
+static int hist_browser__show_no_entry(struct hist_browser *browser,
+ unsigned short row, int level)
+{
+ int width = browser->b.width;
+ bool current_entry = ui_browser__is_current_entry(&browser->b, row);
+ bool first = true;
+ int column = 0;
+ int ret;
+ struct perf_hpp_fmt *fmt;
+ struct perf_hpp_list_node *fmt_node;
+ int indent = browser->hists->nr_hpp_node - 2;
+
+ if (current_entry) {
+ browser->he_selection = NULL;
+ browser->selection = NULL;
+ }
+
+ ui_browser__gotorc(&browser->b, row, 0);
+
+ 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);
+
+ ui_browser__write_nstring(&browser->b, "", level * HIERARCHY_INDENT);
+ width -= level * HIERARCHY_INDENT;
+
+ /* the first hpp_list_node is for overhead columns */
+ fmt_node = list_first_entry(&browser->hists->hpp_formats,
+ struct perf_hpp_list_node, list);
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
+ if (perf_hpp__should_skip(fmt, browser->hists) ||
+ column++ < browser->b.horiz_scroll)
+ continue;
+
+ ret = fmt->width(fmt, NULL, browser->hists);
+
+ if (first) {
+ /* for folded sign */
+ first = false;
+ ret++;
+ } else {
+ /* space between columns */
+ ret += 2;
+ }
+
+ ui_browser__write_nstring(&browser->b, "", ret);
+ width -= ret;
+ }
+
+ ui_browser__write_nstring(&browser->b, "", indent * HIERARCHY_INDENT);
+ width -= indent * HIERARCHY_INDENT;
+
+ if (column >= browser->b.horiz_scroll) {
+ char buf[32];
+
+ ret = snprintf(buf, sizeof(buf), "no entry >= %.2f%%", browser->min_pcnt);
+ ui_browser__printf(&browser->b, " %s", buf);
+ width -= ret + 2;
+ }
+
+ /* The scroll bar isn't being used */
+ if (!browser->b.navkeypressed)
+ width += 1;
+
+ ui_browser__write_nstring(&browser->b, "", width);
+ return 1;
+}
+
+static int advance_hpp_check(struct perf_hpp *hpp, int inc)
+{
+ advance_hpp(hpp, inc);
+ return hpp->size <= 0;
+}
+
+static int
+hists_browser__scnprintf_headers(struct hist_browser *browser, char *buf,
+ size_t size, int line)
+{
+ struct hists *hists = browser->hists;
+ struct perf_hpp dummy_hpp = {
+ .buf = buf,
+ .size = size,
+ };
+ struct perf_hpp_fmt *fmt;
+ size_t ret = 0;
+ int column = 0;
+ int span = 0;
+
+ if (hists__has_callchains(hists) && symbol_conf.use_callchain) {
+ ret = scnprintf(buf, size, " ");
+ if (advance_hpp_check(&dummy_hpp, ret))
+ return ret;
+ }
+
+ hists__for_each_format(browser->hists, fmt) {
+ if (perf_hpp__should_skip(fmt, hists) || column++ < browser->b.horiz_scroll)
+ continue;
+
+ ret = fmt->header(fmt, &dummy_hpp, hists, line, &span);
+ if (advance_hpp_check(&dummy_hpp, ret))
+ break;
+
+ if (span)
+ continue;
+
+ ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " ");
+ if (advance_hpp_check(&dummy_hpp, ret))
+ break;
+ }
+
+ return ret;
+}
+
+static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *browser,
+ char *buf, size_t size, int line)
+{
+ struct hists *hists = browser->hists;
+ struct perf_hpp dummy_hpp = {
+ .buf = buf,
+ .size = size,
+ };
+ struct perf_hpp_fmt *fmt;
+ struct perf_hpp_list_node *fmt_node;
+ size_t ret = 0;
+ int column = 0;
+ int indent = hists->nr_hpp_node - 2;
+ bool first_node, first_col;
+
+ ret = scnprintf(buf, size, " ");
+ if (advance_hpp_check(&dummy_hpp, ret))
+ return ret;
+
+ first_node = true;
+ /* the first hpp_list_node is for overhead columns */
+ fmt_node = list_first_entry(&hists->hpp_formats,
+ struct perf_hpp_list_node, list);
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
+ if (column++ < browser->b.horiz_scroll)
+ continue;
+
+ ret = fmt->header(fmt, &dummy_hpp, hists, line, NULL);
+ if (advance_hpp_check(&dummy_hpp, ret))
+ break;
+
+ ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " ");
+ if (advance_hpp_check(&dummy_hpp, ret))
+ break;
+
+ first_node = false;
+ }
+
+ if (line < hists->hpp_list->nr_header_lines - 1)
+ return ret;
+
+ if (!first_node) {
+ ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "%*s",
+ indent * HIERARCHY_INDENT, "");
+ if (advance_hpp_check(&dummy_hpp, ret))
+ return ret;
+ }
+
+ first_node = true;
+ list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
+ if (!first_node) {
+ ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " / ");
+ if (advance_hpp_check(&dummy_hpp, ret))
+ break;
+ }
+ first_node = false;
+
+ first_col = true;
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
+ char *start;
+
+ if (perf_hpp__should_skip(fmt, hists))
+ continue;
+
+ if (!first_col) {
+ ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "+");
+ if (advance_hpp_check(&dummy_hpp, ret))
+ break;
+ }
+ first_col = false;
+
+ ret = fmt->header(fmt, &dummy_hpp, hists, line, NULL);
+ dummy_hpp.buf[ret] = '\0';
+
+ start = strim(dummy_hpp.buf);
+ ret = strlen(start);
+
+ if (start != dummy_hpp.buf)
+ memmove(dummy_hpp.buf, start, ret + 1);
+
+ if (advance_hpp_check(&dummy_hpp, ret))
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void hists_browser__hierarchy_headers(struct hist_browser *browser)
+{
+ struct perf_hpp_list *hpp_list = browser->hists->hpp_list;
+ char headers[1024];
+ int line;
+
+ for (line = 0; line < hpp_list->nr_header_lines; line++) {
+ hists_browser__scnprintf_hierarchy_headers(browser, headers,
+ sizeof(headers), line);
+
+ ui_browser__gotorc_title(&browser->b, line, 0);
+ ui_browser__set_color(&browser->b, HE_COLORSET_ROOT);
+ ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1);
+ }
+}
+
+static void hists_browser__headers(struct hist_browser *browser)
+{
+ struct hists *hists = browser->hists;
+ struct perf_hpp_list *hpp_list = hists->hpp_list;
+
+ int line;
+
+ for (line = 0; line < hpp_list->nr_header_lines; line++) {
+ char headers[1024];
+
+ hists_browser__scnprintf_headers(browser, headers,
+ sizeof(headers), line);
+
+ ui_browser__gotorc_title(&browser->b, line, 0);
+ ui_browser__set_color(&browser->b, HE_COLORSET_ROOT);
+ ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1);
+ }
+}
+
+static void hist_browser__show_headers(struct hist_browser *browser)
+{
+ if (symbol_conf.report_hierarchy)
+ hists_browser__hierarchy_headers(browser);
+ else
+ hists_browser__headers(browser);
+}
+
static void ui_browser__hists_init_top(struct ui_browser *browser)
{
if (browser->top == NULL) {
struct hist_browser *hb;
hb = container_of(browser, struct hist_browser, b);
- browser->top = rb_first(&hb->hists->entries);
+ browser->top = rb_first_cached(&hb->hists->entries);
}
}
@@ -807,21 +1859,46 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser)
struct rb_node *nd;
struct hist_browser *hb = container_of(browser, struct hist_browser, b);
+ if (hb->show_headers)
+ hist_browser__show_headers(hb);
+
ui_browser__hists_init_top(browser);
+ hb->he_selection = NULL;
+ hb->selection = NULL;
- for (nd = browser->top; nd; nd = rb_next(nd)) {
+ for (nd = browser->top; nd; nd = rb_hierarchy_next(nd)) {
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
- float percent = h->stat.period * 100.0 /
- hb->hists->stats.total_period;
+ float percent;
- if (h->filtered)
+ if (h->filtered) {
+ /* let it move to sibling */
+ h->unfolded = false;
continue;
+ }
+
+ if (symbol_conf.report_individual_block)
+ percent = block_info__total_cycles_percent(h);
+ else
+ percent = hist_entry__get_percent_limit(h);
if (percent < hb->min_pcnt)
continue;
- row += hist_browser__show_entry(hb, h, row);
- if (row == browser->height)
+ if (symbol_conf.report_hierarchy) {
+ row += hist_browser__show_hierarchy_entry(hb, h, row,
+ h->depth);
+ if (row == browser->rows)
+ break;
+
+ if (h->has_no_entry) {
+ hist_browser__show_no_entry(hb, row, h->depth + 1);
+ row++;
+ }
+ } else {
+ row += hist_browser__show_entry(hb, h, row);
+ }
+
+ if (row == browser->rows)
break;
}
@@ -829,39 +1906,39 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser)
}
static struct rb_node *hists__filter_entries(struct rb_node *nd,
- struct hists *hists,
float min_pcnt)
{
while (nd != NULL) {
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
- float percent = h->stat.period * 100.0 /
- hists->stats.total_period;
-
- if (percent < min_pcnt)
- return NULL;
+ float percent = hist_entry__get_percent_limit(h);
- if (!h->filtered)
+ if (!h->filtered && percent >= min_pcnt)
return nd;
- nd = rb_next(nd);
+ /*
+ * If it's filtered, its all children also were filtered.
+ * So move to sibling node.
+ */
+ if (rb_next(nd))
+ nd = rb_next(nd);
+ else
+ nd = rb_hierarchy_next(nd);
}
return NULL;
}
static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
- struct hists *hists,
float min_pcnt)
{
while (nd != NULL) {
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
- float percent = h->stat.period * 100.0 /
- hists->stats.total_period;
+ float percent = hist_entry__get_percent_limit(h);
if (!h->filtered && percent >= min_pcnt)
return nd;
- nd = rb_prev(nd);
+ nd = rb_hierarchy_prev(nd);
}
return NULL;
@@ -885,14 +1962,14 @@ static void ui_browser__hists_seek(struct ui_browser *browser,
switch (whence) {
case SEEK_SET:
nd = hists__filter_entries(rb_first(browser->entries),
- hb->hists, hb->min_pcnt);
+ hb->min_pcnt);
break;
case SEEK_CUR:
nd = browser->top;
goto do_offset;
case SEEK_END:
- nd = hists__filter_prev_entries(rb_last(browser->entries),
- hb->hists, hb->min_pcnt);
+ nd = rb_hierarchy_last(rb_last(browser->entries));
+ nd = hists__filter_prev_entries(nd, hb->min_pcnt);
first = false;
break;
default:
@@ -920,10 +1997,13 @@ static void ui_browser__hists_seek(struct ui_browser *browser,
* and stop when we printed enough lines to fill the screen.
*/
do_offset:
+ if (!nd)
+ return;
+
if (offset > 0) {
do {
h = rb_entry(nd, struct hist_entry, rb_node);
- if (h->ms.unfolded) {
+ if (h->unfolded && h->leaf) {
u16 remaining = h->nr_rows - h->row_offset;
if (offset > remaining) {
offset -= remaining;
@@ -935,7 +2015,7 @@ do_offset:
break;
}
}
- nd = hists__filter_entries(rb_next(nd), hb->hists,
+ nd = hists__filter_entries(rb_hierarchy_next(nd),
hb->min_pcnt);
if (nd == NULL)
break;
@@ -945,7 +2025,7 @@ do_offset:
} else if (offset < 0) {
while (1) {
h = rb_entry(nd, struct hist_entry, rb_node);
- if (h->ms.unfolded) {
+ if (h->unfolded && h->leaf) {
if (first) {
if (-offset > h->row_offset) {
offset += h->row_offset;
@@ -969,7 +2049,7 @@ do_offset:
}
}
- nd = hists__filter_prev_entries(rb_prev(nd), hb->hists,
+ nd = hists__filter_prev_entries(rb_hierarchy_prev(nd),
hb->min_pcnt);
if (nd == NULL)
break;
@@ -982,7 +2062,7 @@ do_offset:
* row_offset at its last entry.
*/
h = rb_entry(nd, struct hist_entry, rb_node);
- if (h->ms.unfolded)
+ if (h->unfolded && h->leaf)
h->row_offset = h->nr_rows;
break;
}
@@ -995,144 +2075,116 @@ do_offset:
}
}
-static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser,
- struct callchain_node *chain_node,
- u64 total, int level,
- FILE *fp)
+static int hist_browser__fprintf_callchain(struct hist_browser *browser,
+ struct hist_entry *he, FILE *fp,
+ int level)
{
- struct rb_node *node;
- int offset = level * LEVEL_OFFSET_STEP;
- u64 new_total, remaining;
- int printed = 0;
-
- if (callchain_param.mode == CHAIN_GRAPH_REL)
- new_total = chain_node->children_hit;
- else
- new_total = total;
-
- remaining = new_total;
- node = rb_first(&chain_node->rb_root);
- while (node) {
- struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
- struct rb_node *next = rb_next(node);
- u64 cumul = callchain_cumul_hits(child);
- struct callchain_list *chain;
- char folded_sign = ' ';
- int first = true;
- int extra_offset = 0;
-
- remaining -= cumul;
-
- list_for_each_entry(chain, &child->val, list) {
- char bf[1024], *alloc_str;
- const char *str;
- bool was_first = first;
-
- if (first)
- first = false;
- else
- extra_offset = LEVEL_OFFSET_STEP;
-
- folded_sign = callchain_list__folded(chain);
-
- alloc_str = NULL;
- str = callchain_list__sym_name(chain, bf, sizeof(bf),
- browser->show_dso);
- if (was_first) {
- double percent = cumul * 100.0 / new_total;
-
- if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
- str = "Not enough memory!";
- else
- str = alloc_str;
- }
-
- printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str);
- free(alloc_str);
- if (folded_sign == '+')
- break;
- }
-
- if (folded_sign == '-') {
- const int new_level = level + (extra_offset ? 2 : 1);
- printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total,
- new_level, fp);
- }
-
- node = next;
- }
+ struct callchain_print_arg arg = {
+ .fp = fp,
+ };
- return printed;
+ hist_browser__show_callchain(browser, he, level, 0,
+ hist_browser__fprintf_callchain_entry, &arg,
+ hist_browser__check_dump_full);
+ return arg.printed;
}
-static int hist_browser__fprintf_callchain_node(struct hist_browser *browser,
- struct callchain_node *node,
- int level, FILE *fp)
+static int hist_browser__fprintf_entry(struct hist_browser *browser,
+ struct hist_entry *he, FILE *fp)
{
- struct callchain_list *chain;
- int offset = level * LEVEL_OFFSET_STEP;
- char folded_sign = ' ';
+ char s[8192];
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;
- list_for_each_entry(chain, &node->val, list) {
- char bf[1024], *s;
-
- folded_sign = callchain_list__folded(chain);
- s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso);
- printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s);
+ if (hist_entry__has_callchains(he) && symbol_conf.use_callchain) {
+ folded_sign = hist_entry__folded(he);
+ printed += fprintf(fp, "%c ", folded_sign);
}
- if (folded_sign == '-')
- printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node,
- browser->hists->stats.total_period,
- level + 1, fp);
- return printed;
-}
-
-static int hist_browser__fprintf_callchain(struct hist_browser *browser,
- struct rb_root *chain, int level, FILE *fp)
-{
- struct rb_node *nd;
- int printed = 0;
+ hists__for_each_format(browser->hists, fmt) {
+ if (perf_hpp__should_skip(fmt, he->hists))
+ continue;
- for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
- struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+ if (!first) {
+ ret = scnprintf(hpp.buf, hpp.size, " ");
+ advance_hpp(&hpp, ret);
+ } else
+ first = false;
- printed += hist_browser__fprintf_callchain_node(browser, node, level, fp);
+ ret = fmt->entry(fmt, &hpp, he);
+ ret = hist_entry__snprintf_alignment(he, &hpp, fmt, ret);
+ advance_hpp(&hpp, ret);
}
+ printed += fprintf(fp, "%s\n", s);
+
+ if (folded_sign == '-')
+ printed += hist_browser__fprintf_callchain(browser, he, fp, 1);
return printed;
}
-static int hist_browser__fprintf_entry(struct hist_browser *browser,
- struct hist_entry *he, FILE *fp)
+
+static int hist_browser__fprintf_hierarchy_entry(struct hist_browser *browser,
+ struct hist_entry *he,
+ FILE *fp, int level)
{
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;
+ struct perf_hpp_list_node *fmt_node;
+ bool first = true;
+ int ret;
+ int hierarchy_indent = (he->hists->nr_hpp_node - 2) * HIERARCHY_INDENT;
+
+ printed = fprintf(fp, "%*s", level * HIERARCHY_INDENT, "");
+
+ folded_sign = hist_entry__folded(he);
+ printed += fprintf(fp, "%c", folded_sign);
+
+ /* the first hpp_list_node is for overhead columns */
+ fmt_node = list_first_entry(&he->hists->hpp_formats,
+ struct perf_hpp_list_node, list);
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
+ if (!first) {
+ ret = scnprintf(hpp.buf, hpp.size, " ");
+ advance_hpp(&hpp, ret);
+ } else
+ first = false;
- 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);
+ ret = fmt->entry(fmt, &hpp, he);
+ advance_hpp(&hpp, ret);
+ }
- printed += fprintf(fp, " %5.2f%%", percent);
+ ret = scnprintf(hpp.buf, hpp.size, "%*s", hierarchy_indent, "");
+ advance_hpp(&hpp, ret);
- if (symbol_conf.show_nr_samples)
- printed += fprintf(fp, " %11u", he->stat.nr_events);
+ perf_hpp_list__for_each_format(he->hpp_list, fmt) {
+ ret = scnprintf(hpp.buf, hpp.size, " ");
+ advance_hpp(&hpp, ret);
- if (symbol_conf.show_total_period)
- printed += fprintf(fp, " %12" PRIu64, he->stat.period);
+ ret = fmt->entry(fmt, &hpp, he);
+ advance_hpp(&hpp, ret);
+ }
- printed += fprintf(fp, "%s\n", rtrim(s));
+ strim(s);
+ printed += fprintf(fp, "%s\n", s);
- if (folded_sign == '-')
- printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp);
+ if (he->leaf && folded_sign == '-') {
+ printed += hist_browser__fprintf_callchain(browser, he, fp,
+ he->depth + 1);
+ }
return printed;
}
@@ -1140,15 +2192,21 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser,
static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
{
struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
- browser->hists,
browser->min_pcnt);
int printed = 0;
while (nd) {
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
- printed += hist_browser__fprintf_entry(browser, h, fp);
- nd = hists__filter_entries(rb_next(nd), browser->hists,
+ if (symbol_conf.report_hierarchy) {
+ printed += hist_browser__fprintf_hierarchy_entry(browser,
+ h, fp,
+ h->depth);
+ } else {
+ printed += hist_browser__fprintf_entry(browser, h, fp);
+ }
+
+ nd = hists__filter_entries(rb_hierarchy_next(nd),
browser->min_pcnt);
}
@@ -1176,7 +2234,7 @@ static int hist_browser__dump(struct hist_browser *browser)
fp = fopen(filename, "w");
if (fp == NULL) {
char bf[64];
- const char *err = strerror_r(errno, bf, sizeof(bf));
+ const char *err = str_error_r(errno, bf, sizeof(bf));
ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
return -1;
}
@@ -1189,21 +2247,64 @@ static int hist_browser__dump(struct hist_browser *browser)
return 0;
}
-static struct hist_browser *hist_browser__new(struct hists *hists)
+void hist_browser__init(struct hist_browser *browser,
+ struct hists *hists)
+{
+ struct perf_hpp_fmt *fmt;
+
+ browser->hists = hists;
+ browser->b.refresh = hist_browser__refresh;
+ browser->b.refresh_dimensions = hist_browser__refresh_dimensions;
+ browser->b.seek = ui_browser__hists_seek;
+ browser->b.use_navkeypressed = true;
+ browser->show_headers = symbol_conf.show_hist_headers;
+ hist_browser__set_title_space(browser);
+
+ if (symbol_conf.report_hierarchy) {
+ struct perf_hpp_list_node *fmt_node;
+
+ /* count overhead columns (in the first node) */
+ fmt_node = list_first_entry(&hists->hpp_formats,
+ struct perf_hpp_list_node, list);
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt)
+ ++browser->b.columns;
+
+ /* add a single column for whole hierarchy sort keys*/
+ ++browser->b.columns;
+ } else {
+ hists__for_each_format(hists, fmt)
+ ++browser->b.columns;
+ }
+
+ hists__reset_column_width(hists);
+}
+
+struct hist_browser *hist_browser__new(struct hists *hists)
{
struct hist_browser *browser = zalloc(sizeof(*browser));
+ if (browser)
+ hist_browser__init(browser, hists);
+
+ return browser;
+}
+
+static struct hist_browser *
+perf_evsel_browser__new(struct evsel *evsel,
+ struct hist_browser_timer *hbt,
+ struct perf_env *env)
+{
+ struct hist_browser *browser = hist_browser__new(evsel__hists(evsel));
+
if (browser) {
- browser->hists = hists;
- browser->b.refresh = hist_browser__refresh;
- browser->b.seek = ui_browser__hists_seek;
- browser->b.use_navkeypressed = true;
+ browser->hbt = hbt;
+ browser->env = env;
+ browser->title = hists_browser__scnprintf_title;
}
-
return browser;
}
-static void hist_browser__delete(struct hist_browser *browser)
+void hist_browser__delete(struct hist_browser *browser)
{
free(browser);
}
@@ -1218,48 +2319,40 @@ static struct thread *hist_browser__selected_thread(struct hist_browser *browser
return browser->he_selection->thread;
}
-static int hists__browser_title(struct hists *hists, char *bf, size_t size,
- const char *ev_name)
+static struct res_sample *hist_browser__selected_res_sample(struct hist_browser *browser)
{
- char unit;
- int printed;
- const struct dso *dso = hists->dso_filter;
- const struct thread *thread = hists->thread_filter;
- unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
- u64 nr_events = hists->stats.total_period;
- struct perf_evsel *evsel = hists_to_evsel(hists);
- char buf[512];
- size_t buflen = sizeof(buf);
-
- if (perf_evsel__is_group_event(evsel)) {
- struct perf_evsel *pos;
-
- perf_evsel__group_desc(evsel, buf, buflen);
- ev_name = buf;
+ return browser->he_selection ? browser->he_selection->res_samples : NULL;
+}
- for_each_group_member(pos, evsel) {
- nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
- nr_events += pos->hists.stats.total_period;
- }
- }
+/* Check whether the browser is for 'top' or 'report' */
+static inline bool is_report_browser(void *timer)
+{
+ return timer == NULL;
+}
- nr_samples = convert_unit(nr_samples, &unit);
- printed = scnprintf(bf, size,
- "Samples: %lu%c of event '%s', Event count (approx.): %lu",
- nr_samples, unit, ev_name, nr_events);
+static int hists_browser__scnprintf_title(struct hist_browser *browser, char *bf, size_t size)
+{
+ struct hist_browser_timer *hbt = browser->hbt;
+ int printed = __hists__scnprintf_title(browser->hists, bf, size, !is_report_browser(hbt));
+ if (!is_report_browser(hbt)) {
+ struct perf_top *top = hbt->arg;
- if (hists->uid_filter_str)
- printed += snprintf(bf + printed, size - printed,
- ", UID: %s", hists->uid_filter_str);
- if (thread)
printed += scnprintf(bf + printed, size - printed,
- ", Thread: %s(%d)",
- (thread->comm_set ? thread->comm : ""),
- thread->pid);
- if (dso)
+ " lost: %" PRIu64 "/%" PRIu64,
+ top->lost, top->lost_total);
+
printed += scnprintf(bf + printed, size - printed,
- ", DSO: %s", dso->short_name);
+ " drop: %" PRIu64 "/%" PRIu64,
+ top->drop, top->drop_total);
+
+ if (top->zero)
+ printed += scnprintf(bf + printed, size - printed, " [z]");
+
+ perf_top__reset_sample_counters(top);
+ }
+
+
return printed;
}
@@ -1267,16 +2360,8 @@ static inline void free_popup_options(char **options, int n)
{
int i;
- for (i = 0; i < n; ++i) {
- free(options[i]);
- options[i] = NULL;
- }
-}
-
-/* Check whether the browser is for 'top' or 'report' */
-static inline bool is_report_browser(void *timer)
-{
- return timer == NULL;
+ for (i = 0; i < n; ++i)
+ zfree(&options[i]);
}
/*
@@ -1302,7 +2387,7 @@ static int switch_data_file(void)
return ret;
memset(options, 0, sizeof(options));
- memset(options, 0, sizeof(abs_path));
+ memset(abs_path, 0, sizeof(abs_path));
while ((dent = readdir(pwd_dir))) {
char path[PATH_MAX];
@@ -1329,7 +2414,7 @@ static int switch_data_file(void)
abs_path[nr_options] = strdup(path);
if (!abs_path[nr_options]) {
- free(options[nr_options]);
+ zfree(&options[nr_options]);
ui__warning("Can't search all data files due to memory shortage.\n");
fclose(file);
break;
@@ -1349,7 +2434,7 @@ close_file_and_continue:
closedir(pwd_dir);
if (nr_options) {
- choice = ui__popup_menu(nr_options, options);
+ choice = ui__popup_menu(nr_options, options, NULL);
if (choice < nr_options && choice >= 0) {
tmp = strdup(abs_path[choice]);
if (tmp) {
@@ -1368,70 +2453,647 @@ close_file_and_continue:
return ret;
}
-static void hist_browser__update_pcnt_entries(struct hist_browser *hb)
+struct popup_action {
+ unsigned long time;
+ struct thread *thread;
+ int (*fn)(struct hist_browser *browser, struct popup_action *act);
+ struct map_symbol ms;
+ int socket;
+ enum rstype rstype;
+
+};
+
+static int
+do_annotate(struct hist_browser *browser, struct popup_action *act)
+{
+ struct evsel *evsel;
+ struct annotation *notes;
+ struct hist_entry *he;
+ int err;
+
+ if (!annotate_opts.objdump_path &&
+ perf_env__lookup_objdump(browser->env, &annotate_opts.objdump_path))
+ return 0;
+
+ notes = symbol__annotation(act->ms.sym);
+ if (!notes->src)
+ return 0;
+
+ if (browser->block_evsel)
+ evsel = browser->block_evsel;
+ else
+ evsel = hists_to_evsel(browser->hists);
+
+ he = hist_browser__selected_entry(browser);
+ err = __hist_entry__tui_annotate(he, &act->ms, evsel, browser->hbt, NO_ADDR);
+ /*
+ * offer option to annotate the other branch source or target
+ * (if they exists) when returning from annotate
+ */
+ if ((err == 'q' || err == CTRL('c')) && he->branch_info)
+ return 1;
+
+ ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
+ if (err)
+ ui_browser__handle_resize(&browser->b);
+ return 0;
+}
+
+static struct symbol *symbol__new_unresolved(u64 addr, struct map *map)
+{
+ struct annotated_source *src;
+ struct symbol *sym;
+ char name[64];
+
+ snprintf(name, sizeof(name), "%.*" PRIx64, BITS_PER_LONG / 4, addr);
+
+ sym = symbol__new(addr, ANNOTATION_DUMMY_LEN, 0, 0, name);
+ if (sym) {
+ src = symbol__hists(sym, 1);
+ if (!src) {
+ symbol__delete(sym);
+ return NULL;
+ }
+
+ dso__insert_symbol(map__dso(map), sym);
+ }
+
+ return sym;
+}
+
+static int
+add_annotate_opt(struct popup_action *act, char **optstr,
+ struct map_symbol *ms,
+ u64 addr)
+{
+ struct dso *dso;
+
+ if (!ms->map || (dso = map__dso(ms->map)) == NULL || dso__annotate_warned(dso))
+ return 0;
+
+ if (!ms->sym)
+ ms->sym = symbol__new_unresolved(addr, ms->map);
+
+ if (ms->sym == NULL || symbol__annotation(ms->sym)->src == NULL)
+ return 0;
+
+ if (asprintf(optstr, "Annotate %s", ms->sym->name) < 0)
+ return 0;
+
+ act->ms = *ms;
+ act->fn = do_annotate;
+ return 1;
+}
+
+static int
+do_annotate_type(struct hist_browser *browser, struct popup_action *act __maybe_unused)
+{
+ struct hist_entry *he = browser->he_selection;
+
+ hist_entry__annotate_data_tui(he, hists_to_evsel(browser->hists), browser->hbt);
+ ui_browser__handle_resize(&browser->b);
+ return 0;
+}
+
+static int
+add_annotate_type_opt(struct popup_action *act, char **optstr,
+ struct hist_entry *he)
+{
+ if (he == NULL || he->mem_type == NULL || he->mem_type->histograms == NULL)
+ return 0;
+
+ if (asprintf(optstr, "Annotate type %s", he->mem_type->self.type_name) < 0)
+ return 0;
+
+ act->fn = do_annotate_type;
+ return 1;
+}
+
+static int
+do_zoom_thread(struct hist_browser *browser, struct popup_action *act)
+{
+ struct thread *thread = act->thread;
+
+ if ((!hists__has(browser->hists, thread) &&
+ !hists__has(browser->hists, comm)) || thread == NULL)
+ return 0;
+
+ if (browser->hists->thread_filter) {
+ pstack__remove(browser->pstack, &browser->hists->thread_filter);
+ perf_hpp__set_elide(HISTC_THREAD, false);
+ thread__zput(browser->hists->thread_filter);
+ ui_helpline__pop();
+ } else {
+ const char *comm_set_str =
+ thread__comm_set(thread) ? thread__comm_str(thread) : "";
+
+ if (hists__has(browser->hists, thread)) {
+ ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s(%d) thread\"",
+ comm_set_str, thread__tid(thread));
+ } else {
+ ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s thread\"",
+ comm_set_str);
+ }
+
+ browser->hists->thread_filter = thread__get(thread);
+ perf_hpp__set_elide(HISTC_THREAD, false);
+ pstack__push(browser->pstack, &browser->hists->thread_filter);
+ }
+
+ hists__filter_by_thread(browser->hists);
+ hist_browser__reset(browser);
+ return 0;
+}
+
+static int
+add_thread_opt(struct hist_browser *browser, struct popup_action *act,
+ char **optstr, struct thread *thread)
+{
+ int ret;
+ const char *comm_set_str, *in_out;
+
+ if ((!hists__has(browser->hists, thread) &&
+ !hists__has(browser->hists, comm)) || thread == NULL)
+ return 0;
+
+ in_out = browser->hists->thread_filter ? "out of" : "into";
+ comm_set_str = thread__comm_set(thread) ? thread__comm_str(thread) : "";
+ if (hists__has(browser->hists, thread)) {
+ ret = asprintf(optstr, "Zoom %s %s(%d) thread",
+ in_out, comm_set_str, thread__tid(thread));
+ } else {
+ ret = asprintf(optstr, "Zoom %s %s thread", in_out, comm_set_str);
+ }
+ if (ret < 0)
+ return 0;
+
+ act->thread = thread;
+ act->fn = do_zoom_thread;
+ return 1;
+}
+
+static int hists_browser__zoom_map(struct hist_browser *browser, struct map *map)
+{
+ if (!hists__has(browser->hists, dso) || map == NULL)
+ return 0;
+
+ if (browser->hists->dso_filter) {
+ pstack__remove(browser->pstack, &browser->hists->dso_filter);
+ perf_hpp__set_elide(HISTC_DSO, false);
+ browser->hists->dso_filter = NULL;
+ ui_helpline__pop();
+ } else {
+ struct dso *dso = map__dso(map);
+ ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s DSO\"",
+ __map__is_kernel(map) ? "the Kernel" : dso__short_name(dso));
+ browser->hists->dso_filter = dso;
+ perf_hpp__set_elide(HISTC_DSO, true);
+ pstack__push(browser->pstack, &browser->hists->dso_filter);
+ }
+
+ hists__filter_by_dso(browser->hists);
+ hist_browser__reset(browser);
+ return 0;
+}
+
+static int
+do_zoom_dso(struct hist_browser *browser, struct popup_action *act)
+{
+ return hists_browser__zoom_map(browser, act->ms.map);
+}
+
+static int
+add_dso_opt(struct hist_browser *browser, struct popup_action *act,
+ char **optstr, struct map *map)
+{
+ if (!hists__has(browser->hists, dso) || map == NULL)
+ return 0;
+
+ if (asprintf(optstr, "Zoom %s %s DSO (use the 'k' hotkey to zoom directly into the kernel)",
+ browser->hists->dso_filter ? "out of" : "into",
+ __map__is_kernel(map) ? "the Kernel" : dso__short_name(map__dso(map))) < 0)
+ return 0;
+
+ act->ms.map = map;
+ act->fn = do_zoom_dso;
+ return 1;
+}
+
+static int do_toggle_callchain(struct hist_browser *browser, struct popup_action *act __maybe_unused)
+{
+ hist_browser__toggle_fold(browser);
+ return 0;
+}
+
+static int add_callchain_toggle_opt(struct hist_browser *browser, struct popup_action *act, char **optstr)
+{
+ char sym_name[512];
+
+ if (!hist_browser__selection_has_children(browser))
+ return 0;
+
+ if (asprintf(optstr, "%s [%s] callchain (one level, same as '+' hotkey, use 'e'/'c' for the whole main level entry)",
+ hist_browser__selection_unfolded(browser) ? "Collapse" : "Expand",
+ hist_browser__selection_sym_name(browser, sym_name, sizeof(sym_name))) < 0)
+ return 0;
+
+ act->fn = do_toggle_callchain;
+ return 1;
+}
+
+static int
+do_browse_map(struct hist_browser *browser __maybe_unused,
+ struct popup_action *act)
+{
+ map__browse(act->ms.map);
+ return 0;
+}
+
+static int
+add_map_opt(struct hist_browser *browser,
+ struct popup_action *act, char **optstr, struct map *map)
+{
+ if (!hists__has(browser->hists, dso) || map == NULL)
+ return 0;
+
+ if (asprintf(optstr, "Browse map details") < 0)
+ return 0;
+
+ act->ms.map = map;
+ act->fn = do_browse_map;
+ return 1;
+}
+
+static int
+do_run_script(struct hist_browser *browser,
+ struct popup_action *act)
+{
+ char *script_opt;
+ int len;
+ int n = 0;
+
+ len = 100;
+ if (act->thread)
+ len += strlen(thread__comm_str(act->thread));
+ else if (act->ms.sym)
+ len += strlen(act->ms.sym->name);
+ script_opt = malloc(len);
+ if (!script_opt)
+ return -1;
+
+ script_opt[0] = 0;
+ if (act->thread) {
+ n = scnprintf(script_opt, len, " -c %s ",
+ thread__comm_str(act->thread));
+ } else if (act->ms.sym) {
+ n = scnprintf(script_opt, len, " -S %s ",
+ act->ms.sym->name);
+ }
+
+ if (act->time) {
+ char start[32], end[32];
+ unsigned long starttime = act->time;
+ unsigned long endtime = act->time + symbol_conf.time_quantum;
+
+ if (starttime == endtime) { /* Display 1ms as fallback */
+ starttime -= 1*NSEC_PER_MSEC;
+ endtime += 1*NSEC_PER_MSEC;
+ }
+ timestamp__scnprintf_usec(starttime, start, sizeof start);
+ timestamp__scnprintf_usec(endtime, end, sizeof end);
+ n += snprintf(script_opt + n, len - n, " --time %s,%s", start, end);
+ }
+
+ script_browse(script_opt, hists_to_evsel(browser->hists));
+ free(script_opt);
+ return 0;
+}
+
+static int
+do_res_sample_script(struct hist_browser *browser,
+ struct popup_action *act)
+{
+ struct hist_entry *he;
+
+ he = hist_browser__selected_entry(browser);
+ res_sample_browse(he->res_samples, he->num_res, hists_to_evsel(browser->hists), act->rstype);
+ return 0;
+}
+
+static int
+add_script_opt_2(struct popup_action *act, char **optstr,
+ struct thread *thread, struct symbol *sym,
+ const char *tstr)
+{
+
+ if (thread) {
+ if (asprintf(optstr, "Run scripts for samples of thread [%s]%s",
+ thread__comm_str(thread), tstr) < 0)
+ return 0;
+ } else if (sym) {
+ if (asprintf(optstr, "Run scripts for samples of symbol [%s]%s",
+ sym->name, tstr) < 0)
+ return 0;
+ } else {
+ if (asprintf(optstr, "Run scripts for all samples%s", tstr) < 0)
+ return 0;
+ }
+
+ act->thread = thread;
+ act->ms.sym = sym;
+ act->fn = do_run_script;
+ return 1;
+}
+
+static int
+add_script_opt(struct hist_browser *browser,
+ struct popup_action *act, char **optstr,
+ struct thread *thread, struct symbol *sym)
+{
+ int n, j;
+ struct hist_entry *he;
+
+ n = add_script_opt_2(act, optstr, thread, sym, "");
+
+ he = hist_browser__selected_entry(browser);
+ if (sort_order && strstr(sort_order, "time")) {
+ char tstr[128];
+
+ optstr++;
+ act++;
+ j = sprintf(tstr, " in ");
+ j += timestamp__scnprintf_usec(he->time, tstr + j,
+ sizeof tstr - j);
+ j += sprintf(tstr + j, "-");
+ timestamp__scnprintf_usec(he->time + symbol_conf.time_quantum,
+ tstr + j, sizeof tstr - j);
+ n += add_script_opt_2(act, optstr, thread, sym, tstr);
+ act->time = he->time;
+ }
+ return n;
+}
+
+static int
+add_res_sample_opt(struct hist_browser *browser __maybe_unused,
+ struct popup_action *act, char **optstr,
+ struct res_sample *res_sample,
+ enum rstype type)
+{
+ if (!res_sample)
+ return 0;
+
+ if (asprintf(optstr, "Show context for individual samples %s",
+ type == A_ASM ? "with assembler" :
+ type == A_SOURCE ? "with source" : "") < 0)
+ return 0;
+
+ act->fn = do_res_sample_script;
+ act->rstype = type;
+ return 1;
+}
+
+static int
+do_switch_data(struct hist_browser *browser __maybe_unused,
+ struct popup_action *act __maybe_unused)
+{
+ if (switch_data_file()) {
+ ui__warning("Won't switch the data files due to\n"
+ "no valid data file get selected!\n");
+ return 0;
+ }
+
+ return K_SWITCH_INPUT_DATA;
+}
+
+static int
+add_switch_opt(struct hist_browser *browser,
+ struct popup_action *act, char **optstr)
+{
+ if (!is_report_browser(browser->hbt))
+ return 0;
+
+ if (asprintf(optstr, "Switch to another data file in PWD") < 0)
+ return 0;
+
+ act->fn = do_switch_data;
+ return 1;
+}
+
+static int
+do_exit_browser(struct hist_browser *browser __maybe_unused,
+ struct popup_action *act __maybe_unused)
+{
+ return 0;
+}
+
+static int
+add_exit_opt(struct hist_browser *browser __maybe_unused,
+ struct popup_action *act, char **optstr)
+{
+ if (asprintf(optstr, "Exit") < 0)
+ return 0;
+
+ act->fn = do_exit_browser;
+ return 1;
+}
+
+static int
+do_zoom_socket(struct hist_browser *browser, struct popup_action *act)
+{
+ if (!hists__has(browser->hists, socket) || act->socket < 0)
+ return 0;
+
+ if (browser->hists->socket_filter > -1) {
+ pstack__remove(browser->pstack, &browser->hists->socket_filter);
+ browser->hists->socket_filter = -1;
+ perf_hpp__set_elide(HISTC_SOCKET, false);
+ } else {
+ browser->hists->socket_filter = act->socket;
+ perf_hpp__set_elide(HISTC_SOCKET, true);
+ pstack__push(browser->pstack, &browser->hists->socket_filter);
+ }
+
+ hists__filter_by_socket(browser->hists);
+ hist_browser__reset(browser);
+ return 0;
+}
+
+static int
+add_socket_opt(struct hist_browser *browser, struct popup_action *act,
+ char **optstr, int socket_id)
+{
+ if (!hists__has(browser->hists, socket) || socket_id < 0)
+ return 0;
+
+ if (asprintf(optstr, "Zoom %s Processor Socket %d",
+ (browser->hists->socket_filter > -1) ? "out of" : "into",
+ socket_id) < 0)
+ return 0;
+
+ act->socket = socket_id;
+ act->fn = do_zoom_socket;
+ return 1;
+}
+
+static void hist_browser__update_nr_entries(struct hist_browser *hb)
{
u64 nr_entries = 0;
- struct rb_node *nd = rb_first(&hb->hists->entries);
+ struct rb_node *nd = rb_first_cached(&hb->hists->entries);
- while (nd) {
+ if (hb->min_pcnt == 0 && !symbol_conf.report_hierarchy) {
+ hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries;
+ return;
+ }
+
+ while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
nr_entries++;
- nd = hists__filter_entries(rb_next(nd), hb->hists,
- hb->min_pcnt);
+ nd = rb_hierarchy_next(nd);
}
- hb->nr_pcnt_entries = nr_entries;
+ hb->nr_non_filtered_entries = nr_entries;
+ hb->nr_hierarchy_entries = nr_entries;
}
-static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
- const char *helpline, const char *ev_name,
- bool left_exits,
- struct hist_browser_timer *hbt,
- float min_pcnt,
- struct perf_session_env *env)
+static void hist_browser__update_percent_limit(struct hist_browser *hb,
+ double percent)
{
- struct hists *hists = &evsel->hists;
- struct hist_browser *browser = hist_browser__new(hists);
- struct branch_info *bi;
- struct pstack *fstack;
- char *options[16];
+ struct hist_entry *he;
+ struct rb_node *nd = rb_first_cached(&hb->hists->entries);
+ u64 total = hists__total_period(hb->hists);
+ u64 min_callchain_hits = total * (percent / 100);
+
+ hb->min_pcnt = callchain_param.min_percent = percent;
+
+ while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
+ he = rb_entry(nd, struct hist_entry, rb_node);
+
+ if (he->has_no_entry) {
+ he->has_no_entry = false;
+ he->nr_rows = 0;
+ }
+
+ if (!he->leaf || !hist_entry__has_callchains(he) || !symbol_conf.use_callchain)
+ goto next;
+
+ if (callchain_param.mode == CHAIN_GRAPH_REL) {
+ total = he->stat.period;
+
+ if (symbol_conf.cumulate_callchain)
+ total = he->stat_acc->period;
+
+ min_callchain_hits = total * (percent / 100);
+ }
+
+ callchain_param.sort(&he->sorted_chain, he->callchain,
+ min_callchain_hits, &callchain_param);
+
+next:
+ nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD);
+
+ /* force to re-evaluate folding state of callchains */
+ he->init_have_children = false;
+ hist_entry__set_folding(he, hb, false);
+ }
+}
+
+static int evsel__hists_browse(struct evsel *evsel, int nr_events, const char *helpline,
+ bool left_exits, struct hist_browser_timer *hbt, float min_pcnt,
+ struct perf_env *env, bool warn_lost_event)
+{
+ struct hists *hists = evsel__hists(evsel);
+ struct hist_browser *browser = perf_evsel_browser__new(evsel, hbt, env);
+ struct branch_info *bi = NULL;
+#define MAX_OPTIONS 16
+ char *options[MAX_OPTIONS];
+ struct popup_action actions[MAX_OPTIONS];
int nr_options = 0;
int key = -1;
- char buf[64];
- char script_opt[64];
+ char buf[128];
int delay_secs = hbt ? hbt->refresh : 0;
+#define HIST_BROWSER_HELP_COMMON \
+ "h/?/F1 Show this window\n" \
+ "UP/DOWN/PGUP\n" \
+ "PGDN/SPACE Navigate\n" \
+ "q/ESC/CTRL+C Exit browser or go back to previous screen\n\n" \
+ "For multiple event sessions:\n\n" \
+ "TAB/UNTAB Switch events\n\n" \
+ "For symbolic views (--sort has sym):\n\n" \
+ "ENTER Zoom into DSO/Threads & Annotate current symbol\n" \
+ "ESC Zoom out\n" \
+ "+ Expand/Collapse one callchain level\n" \
+ "a Annotate current symbol\n" \
+ "C Collapse all callchains\n" \
+ "d Zoom into current DSO\n" \
+ "e Expand/Collapse main entry callchains\n" \
+ "E Expand all callchains\n" \
+ "F Toggle percentage of filtered entries\n" \
+ "H Display column headers\n" \
+ "k Zoom into the kernel map\n" \
+ "L Change percent limit\n" \
+ "m Display context menu\n" \
+ "S Zoom into current Processor Socket\n" \
+
+ /* help messages are sorted by lexical order of the hotkey */
+ static const char report_help[] = HIST_BROWSER_HELP_COMMON
+ "i Show header information\n"
+ "P Print histograms to perf.hist.N\n"
+ "r Run available scripts\n"
+ "s Switch to another data file in PWD\n"
+ "t Zoom into current Thread\n"
+ "V Verbose (DSO names in callchains, etc)\n"
+ "/ Filter symbol by name\n"
+ "0-9 Sort by event n in group";
+ static const char top_help[] = HIST_BROWSER_HELP_COMMON
+ "P Print histograms to perf.hist.N\n"
+ "t Zoom into current Thread\n"
+ "V Verbose (DSO names in callchains, etc)\n"
+ "z Toggle zeroing of samples\n"
+ "f Enable/Disable events\n"
+ "/ Filter symbol by name";
+
if (browser == NULL)
return -1;
- if (min_pcnt) {
+ /* reset abort key so that it can get Ctrl-C as a key */
+ SLang_reset_tty();
+ SLang_init_tty(0, 0, 0);
+ SLtty_set_suspend_state(true);
+
+ if (min_pcnt)
browser->min_pcnt = min_pcnt;
- hist_browser__update_pcnt_entries(browser);
- }
+ hist_browser__update_nr_entries(browser);
- fstack = pstack__new(2);
- if (fstack == NULL)
+ browser->pstack = pstack__new(3);
+ if (browser->pstack == NULL)
goto out;
ui_helpline__push(helpline);
memset(options, 0, sizeof(options));
+ memset(actions, 0, sizeof(actions));
- while (1) {
- const struct thread *thread = NULL;
- const struct dso *dso = NULL;
- int choice = 0,
- annotate = -2, zoom_dso = -2, zoom_thread = -2,
- annotate_f = -2, annotate_t = -2, browse_map = -2;
- int scripts_comm = -2, scripts_symbol = -2,
- scripts_all = -2, switch_data = -2;
+ if (symbol_conf.col_width_list_str)
+ perf_hpp__set_user_width(symbol_conf.col_width_list_str);
- nr_options = 0;
+ if (!is_report_browser(hbt))
+ browser->b.no_samples_msg = "Collecting samples...";
+
+ while (1) {
+ struct thread *thread = NULL;
+ struct map *map = NULL;
+ int choice;
+ int socked_id = -1;
- key = hist_browser__run(browser, ev_name, hbt);
+ key = 0; // reset key
+do_hotkey: // key came straight from options ui__popup_menu()
+ choice = nr_options = 0;
+ key = hist_browser__run(browser, helpline, warn_lost_event, key);
if (browser->he_selection != NULL) {
thread = hist_browser__selected_thread(browser);
- dso = browser->selection->map ? browser->selection->map->dso : NULL;
+ map = browser->selection->map;
+ socked_id = browser->he_selection->socket;
}
switch (key) {
case K_TAB:
@@ -1443,32 +3105,110 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
* go to the next or previous
*/
goto out_free_stack;
+ case '0' ... '9':
+ if (!symbol_conf.event_group ||
+ evsel->core.nr_members < 2) {
+ snprintf(buf, sizeof(buf),
+ "Sort by index only available with group events!");
+ helpline = buf;
+ continue;
+ }
+
+ if (key - '0' == symbol_conf.group_sort_idx)
+ continue;
+
+ symbol_conf.group_sort_idx = key - '0';
+
+ if (symbol_conf.group_sort_idx >= evsel->core.nr_members) {
+ snprintf(buf, sizeof(buf),
+ "Max event group index to sort is %d (index from 0 to %d)",
+ evsel->core.nr_members - 1,
+ evsel->core.nr_members - 1);
+ helpline = buf;
+ continue;
+ }
+
+ key = K_RELOAD;
+ goto out_free_stack;
case 'a':
- if (!sort__has_sym) {
+ if (!hists__has(hists, sym)) {
ui_browser__warning(&browser->b, delay_secs * 2,
"Annotation is only available for symbolic views, "
"include \"sym*\" in --sort to use it.");
continue;
}
- if (browser->selection == NULL ||
- browser->selection->sym == NULL ||
- browser->selection->map->dso->annotate_warned)
+ if (!browser->selection ||
+ !browser->selection->map ||
+ !map__dso(browser->selection->map) ||
+ dso__annotate_warned(map__dso(browser->selection->map))) {
continue;
- goto do_annotate;
+ }
+
+ if (!browser->selection->sym) {
+ if (!browser->he_selection)
+ continue;
+
+ if (sort__mode == SORT_MODE__BRANCH) {
+ bi = browser->he_selection->branch_info;
+ if (!bi || !bi->to.ms.map)
+ continue;
+
+ actions->ms.sym = symbol__new_unresolved(bi->to.al_addr, bi->to.ms.map);
+ actions->ms.map = bi->to.ms.map;
+ } else {
+ actions->ms.sym = symbol__new_unresolved(browser->he_selection->ip,
+ browser->selection->map);
+ actions->ms.map = browser->selection->map;
+ }
+
+ if (!actions->ms.sym)
+ continue;
+ } else {
+ if (symbol__annotation(browser->selection->sym)->src == NULL) {
+ ui_browser__warning(&browser->b, delay_secs * 2,
+ "No samples for the \"%s\" symbol.\n\n"
+ "Probably appeared just in a callchain",
+ browser->selection->sym->name);
+ continue;
+ }
+
+ actions->ms.map = browser->selection->map;
+ actions->ms.sym = browser->selection->sym;
+ }
+
+ do_annotate(browser, actions);
+ continue;
case 'P':
hist_browser__dump(browser);
continue;
case 'd':
- goto zoom_dso;
+ actions->ms.map = map;
+ do_zoom_dso(browser, actions);
+ continue;
+ case 'k':
+ if (browser->selection != NULL)
+ hists_browser__zoom_map(browser,
+ maps__machine(browser->selection->maps)->vmlinux_map);
+ continue;
case 'V':
- browser->show_dso = !browser->show_dso;
+ verbose = (verbose + 1) % 4;
+ browser->show_dso = verbose > 0;
+ ui_helpline__fpush("Verbosity level set to %d\n",
+ verbose);
continue;
case 't':
- goto zoom_thread;
+ actions->thread = thread;
+ do_zoom_thread(browser, actions);
+ continue;
+ case 'S':
+ actions->socket = socked_id;
+ do_zoom_socket(browser, actions);
+ continue;
case '/':
if (ui_browser__input_window("Symbol to show",
- "Please enter the name of symbol you want to see",
+ "Please enter the name of symbol you want to see.\n"
+ "To remove the filter later, press / + ENTER.",
buf, "ENTER: OK, ESC: Cancel",
delay_secs * 2) == K_ENTER) {
hists->symbol_filter_str = *buf ? buf : NULL;
@@ -1477,289 +3217,258 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
}
continue;
case 'r':
- if (is_report_browser(hbt))
- goto do_scripts;
+ if (is_report_browser(hbt)) {
+ actions->thread = NULL;
+ actions->ms.sym = NULL;
+ do_run_script(browser, actions);
+ }
continue;
case 's':
- if (is_report_browser(hbt))
- goto do_data_switch;
+ if (is_report_browser(hbt)) {
+ key = do_switch_data(browser, actions);
+ if (key == K_SWITCH_INPUT_DATA)
+ goto out_free_stack;
+ }
+ continue;
+ case 'i':
+ /* env->arch is NULL for live-mode (i.e. perf top) */
+ if (env->arch)
+ tui__header_window(evsel__session(evsel));
+ continue;
+ case 'F':
+ symbol_conf.filter_relative ^= 1;
+ continue;
+ case 'z':
+ if (!is_report_browser(hbt)) {
+ struct perf_top *top = hbt->arg;
+
+ top->zero = !top->zero;
+ }
+ continue;
+ case 'L':
+ if (ui_browser__input_window("Percent Limit",
+ "Please enter the value you want to hide entries under that percent.",
+ buf, "ENTER: OK, ESC: Cancel",
+ delay_secs * 2) == K_ENTER) {
+ char *end;
+ double new_percent = strtod(buf, &end);
+
+ if (new_percent < 0 || new_percent > 100) {
+ ui_browser__warning(&browser->b, delay_secs * 2,
+ "Invalid percent: %.2f", new_percent);
+ continue;
+ }
+
+ hist_browser__update_percent_limit(browser, new_percent);
+ hist_browser__reset(browser);
+ }
continue;
case K_F1:
case 'h':
case '?':
ui_browser__help_window(&browser->b,
- "h/?/F1 Show this window\n"
- "UP/DOWN/PGUP\n"
- "PGDN/SPACE Navigate\n"
- "q/ESC/CTRL+C Exit browser\n\n"
- "For multiple event sessions:\n\n"
- "TAB/UNTAB Switch events\n\n"
- "For symbolic views (--sort has sym):\n\n"
- "-> Zoom into DSO/Threads & Annotate current symbol\n"
- "<- Zoom out\n"
- "a Annotate current symbol\n"
- "C Collapse all callchains\n"
- "E Expand all callchains\n"
- "d Zoom into current DSO\n"
- "t Zoom into current Thread\n"
- "r Run available scripts('perf report' only)\n"
- "s Switch to another data file in PWD ('perf report' only)\n"
- "P Print histograms to perf.hist.N\n"
- "V Verbose (DSO names in callchains, etc)\n"
- "/ Filter symbol by name");
+ is_report_browser(hbt) ? report_help : top_help);
continue;
case K_ENTER:
case K_RIGHT:
+ case 'm':
/* menu */
break;
+ case K_ESC:
case K_LEFT: {
const void *top;
- if (pstack__empty(fstack)) {
+ if (pstack__empty(browser->pstack)) {
/*
* Go back to the perf_evsel_menu__run or other user
*/
if (left_exits)
goto out_free_stack;
+
+ if (key == K_ESC &&
+ ui_browser__dialog_yesno(&browser->b,
+ "Do you really want to exit?"))
+ goto out_free_stack;
+
continue;
}
- top = pstack__pop(fstack);
- if (top == &browser->hists->dso_filter)
- goto zoom_out_dso;
- if (top == &browser->hists->thread_filter)
- goto zoom_out_thread;
+ actions->ms.map = map;
+ top = pstack__peek(browser->pstack);
+ if (top == &browser->hists->dso_filter) {
+ /*
+ * No need to set actions->dso here since
+ * it's just to remove the current filter.
+ */
+ do_zoom_dso(browser, actions);
+ } else if (top == &browser->hists->thread_filter) {
+ actions->thread = thread;
+ do_zoom_thread(browser, actions);
+ } else if (top == &browser->hists->socket_filter) {
+ do_zoom_socket(browser, actions);
+ }
continue;
}
- case K_ESC:
- if (!left_exits &&
- !ui_browser__dialog_yesno(&browser->b,
- "Do you really want to exit?"))
- continue;
- /* Fall thru */
case 'q':
case CTRL('c'):
goto out_free_stack;
+ case 'f':
+ if (!is_report_browser(hbt)) {
+ struct perf_top *top = hbt->arg;
+
+ evlist__toggle_enable(top->evlist);
+ /*
+ * No need to refresh, resort/decay histogram
+ * entries if we are not collecting samples:
+ */
+ if (top->evlist->enabled) {
+ helpline = "Press 'f' to disable the events or 'h' to see other hotkeys";
+ hbt->refresh = delay_secs;
+ } else {
+ helpline = "Press 'f' again to re-enable the events";
+ hbt->refresh = 0;
+ }
+ continue;
+ }
+ /* Fall thru */
default:
+ helpline = "Press '?' for help on key bindings";
+ ui_browser__warn_unhandled_hotkey(&browser->b, key, delay_secs,
+ ", use 'h'/'?'/F1 to see actions");
continue;
}
- if (!sort__has_sym)
- goto add_exit_option;
+ if (!hists__has(hists, sym) || browser->selection == NULL)
+ goto skip_annotation;
if (sort__mode == SORT_MODE__BRANCH) {
- bi = browser->he_selection->branch_info;
- if (browser->selection != NULL &&
- bi &&
- bi->from.sym != NULL &&
- !bi->from.map->dso->annotate_warned &&
- asprintf(&options[nr_options], "Annotate %s",
- bi->from.sym->name) > 0)
- annotate_f = nr_options++;
-
- if (browser->selection != NULL &&
- bi &&
- bi->to.sym != NULL &&
- !bi->to.map->dso->annotate_warned &&
- (bi->to.sym != bi->from.sym ||
- bi->to.map->dso != bi->from.map->dso) &&
- asprintf(&options[nr_options], "Annotate %s",
- bi->to.sym->name) > 0)
- annotate_t = nr_options++;
- } else {
- if (browser->selection != NULL &&
- browser->selection->sym != NULL &&
- !browser->selection->map->dso->annotate_warned &&
- asprintf(&options[nr_options], "Annotate %s",
- browser->selection->sym->name) > 0)
- annotate = nr_options++;
+ if (browser->he_selection)
+ bi = browser->he_selection->branch_info;
+
+ if (bi == NULL)
+ goto skip_annotation;
+
+ nr_options += add_annotate_opt(&actions[nr_options],
+ &options[nr_options],
+ &bi->from.ms,
+ bi->from.al_addr);
+ if (bi->to.ms.sym != bi->from.ms.sym)
+ nr_options += add_annotate_opt(&actions[nr_options],
+ &options[nr_options],
+ &bi->to.ms,
+ bi->to.al_addr);
+ } else if (browser->he_selection) {
+ nr_options += add_annotate_opt(&actions[nr_options],
+ &options[nr_options],
+ browser->selection,
+ browser->he_selection->ip);
}
-
- if (thread != NULL &&
- asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
- (browser->hists->thread_filter ? "out of" : "into"),
- (thread->comm_set ? thread->comm : ""),
- thread->pid) > 0)
- zoom_thread = nr_options++;
-
- if (dso != NULL &&
- asprintf(&options[nr_options], "Zoom %s %s DSO",
- (browser->hists->dso_filter ? "out of" : "into"),
- (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
- zoom_dso = nr_options++;
-
- if (browser->selection != NULL &&
- browser->selection->map != NULL &&
- asprintf(&options[nr_options], "Browse map details") > 0)
- browse_map = nr_options++;
-
+skip_annotation:
+ nr_options += add_annotate_type_opt(&actions[nr_options],
+ &options[nr_options],
+ browser->he_selection);
+ nr_options += add_thread_opt(browser, &actions[nr_options],
+ &options[nr_options], thread);
+ nr_options += add_dso_opt(browser, &actions[nr_options],
+ &options[nr_options], map);
+ nr_options += add_callchain_toggle_opt(browser, &actions[nr_options], &options[nr_options]);
+ nr_options += add_map_opt(browser, &actions[nr_options],
+ &options[nr_options],
+ browser->selection ?
+ browser->selection->map : NULL);
+ nr_options += add_socket_opt(browser, &actions[nr_options],
+ &options[nr_options],
+ socked_id);
/* perf script support */
- if (browser->he_selection) {
- struct symbol *sym;
-
- if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]",
- browser->he_selection->thread->comm) > 0)
- scripts_comm = nr_options++;
-
- sym = browser->he_selection->ms.sym;
- if (sym && sym->namelen &&
- asprintf(&options[nr_options], "Run scripts for samples of symbol [%s]",
- sym->name) > 0)
- scripts_symbol = nr_options++;
- }
+ if (!is_report_browser(hbt))
+ goto skip_scripting;
- if (asprintf(&options[nr_options], "Run scripts for all samples") > 0)
- scripts_all = nr_options++;
-
- if (is_report_browser(hbt) && asprintf(&options[nr_options],
- "Switch to another data file in PWD") > 0)
- switch_data = nr_options++;
-add_exit_option:
- options[nr_options++] = (char *)"Exit";
-retry_popup_menu:
- choice = ui__popup_menu(nr_options, options);
-
- if (choice == nr_options - 1)
- break;
-
- if (choice == -1) {
- free_popup_options(options, nr_options - 1);
- continue;
- }
-
- if (choice == annotate || choice == annotate_t || choice == annotate_f) {
- struct hist_entry *he;
- int err;
-do_annotate:
- if (!objdump_path && perf_session_env__lookup_objdump(env))
- continue;
-
- he = hist_browser__selected_entry(browser);
- if (he == NULL)
- continue;
-
- /*
- * we stash the branch_info symbol + map into the
- * the ms so we don't have to rewrite all the annotation
- * code to use branch_info.
- * in branch mode, the ms struct is not used
- */
- if (choice == annotate_f) {
- he->ms.sym = he->branch_info->from.sym;
- he->ms.map = he->branch_info->from.map;
- } else if (choice == annotate_t) {
- he->ms.sym = he->branch_info->to.sym;
- he->ms.map = he->branch_info->to.map;
+ if (browser->he_selection) {
+ if (hists__has(hists, thread) && thread) {
+ nr_options += add_script_opt(browser,
+ &actions[nr_options],
+ &options[nr_options],
+ thread, NULL);
}
-
- /*
- * Don't let this be freed, say, by hists__decay_entry.
- */
- he->used = true;
- err = hist_entry__tui_annotate(he, evsel, hbt);
- he->used = false;
/*
- * offer option to annotate the other branch source or target
- * (if they exists) when returning from annotate
+ * Note that browser->selection != NULL
+ * when browser->he_selection is not NULL,
+ * so we don't need to check browser->selection
+ * before fetching browser->selection->sym like what
+ * we do before fetching browser->selection->map.
+ *
+ * See hist_browser__show_entry.
*/
- if ((err == 'q' || err == CTRL('c'))
- && annotate_t != -2 && annotate_f != -2)
- goto retry_popup_menu;
-
- ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
- if (err)
- ui_browser__handle_resize(&browser->b);
-
- } else if (choice == browse_map)
- map__browse(browser->selection->map);
- else if (choice == zoom_dso) {
-zoom_dso:
- if (browser->hists->dso_filter) {
- pstack__remove(fstack, &browser->hists->dso_filter);
-zoom_out_dso:
- ui_helpline__pop();
- browser->hists->dso_filter = NULL;
- sort_dso.elide = false;
- } else {
- if (dso == NULL)
- continue;
- ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
- dso->kernel ? "the Kernel" : dso->short_name);
- browser->hists->dso_filter = dso;
- sort_dso.elide = true;
- pstack__push(fstack, &browser->hists->dso_filter);
+ if (hists__has(hists, sym) && browser->selection->sym) {
+ nr_options += add_script_opt(browser,
+ &actions[nr_options],
+ &options[nr_options],
+ NULL, browser->selection->sym);
}
- hists__filter_by_dso(hists);
- hist_browser__reset(browser);
- } else if (choice == zoom_thread) {
-zoom_thread:
- if (browser->hists->thread_filter) {
- pstack__remove(fstack, &browser->hists->thread_filter);
-zoom_out_thread:
- ui_helpline__pop();
- browser->hists->thread_filter = NULL;
- sort_thread.elide = false;
- } else {
- ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
- thread->comm_set ? thread->comm : "",
- thread->pid);
- browser->hists->thread_filter = thread;
- sort_thread.elide = true;
- pstack__push(fstack, &browser->hists->thread_filter);
- }
- hists__filter_by_thread(hists);
- hist_browser__reset(browser);
}
- /* perf scripts support */
- else if (choice == scripts_all || choice == scripts_comm ||
- choice == scripts_symbol) {
-do_scripts:
- memset(script_opt, 0, 64);
-
- if (choice == scripts_comm)
- sprintf(script_opt, " -c %s ", browser->he_selection->thread->comm);
+ nr_options += add_script_opt(browser, &actions[nr_options],
+ &options[nr_options], NULL, NULL);
+ nr_options += add_res_sample_opt(browser, &actions[nr_options],
+ &options[nr_options],
+ hist_browser__selected_res_sample(browser),
+ A_NORMAL);
+ nr_options += add_res_sample_opt(browser, &actions[nr_options],
+ &options[nr_options],
+ hist_browser__selected_res_sample(browser),
+ A_ASM);
+ nr_options += add_res_sample_opt(browser, &actions[nr_options],
+ &options[nr_options],
+ hist_browser__selected_res_sample(browser),
+ A_SOURCE);
+ nr_options += add_switch_opt(browser, &actions[nr_options],
+ &options[nr_options]);
+skip_scripting:
+ nr_options += add_exit_opt(browser, &actions[nr_options],
+ &options[nr_options]);
- if (choice == scripts_symbol)
- sprintf(script_opt, " -S %s ", browser->he_selection->ms.sym->name);
+ do {
+ struct popup_action *act;
- script_browse(script_opt);
- }
- /* Switch to another data file */
- else if (choice == switch_data) {
-do_data_switch:
- if (!switch_data_file()) {
- key = K_SWITCH_INPUT_DATA;
+ choice = ui__popup_menu(nr_options, options, &key);
+ if (choice == -1)
break;
- } else
- ui__warning("Won't switch the data files due to\n"
- "no valid data file get selected!\n");
- }
+
+ if (choice == nr_options)
+ goto do_hotkey;
+
+ act = &actions[choice];
+ key = act->fn(browser, act);
+ } while (key == 1);
+
+ if (key == K_SWITCH_INPUT_DATA)
+ break;
}
out_free_stack:
- pstack__delete(fstack);
+ pstack__delete(browser->pstack);
out:
hist_browser__delete(browser);
- free_popup_options(options, nr_options - 1);
+ free_popup_options(options, MAX_OPTIONS);
return key;
}
-struct perf_evsel_menu {
+struct evsel_menu {
struct ui_browser b;
- struct perf_evsel *selection;
+ struct evsel *selection;
bool lost_events, lost_events_warned;
float min_pcnt;
- struct perf_session_env *env;
+ struct perf_env *env;
};
static void perf_evsel_menu__write(struct ui_browser *browser,
void *entry, int row)
{
- struct perf_evsel_menu *menu = container_of(browser,
- struct perf_evsel_menu, b);
- struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
+ struct evsel_menu *menu = container_of(browser,
+ struct evsel_menu, b);
+ struct evsel *evsel = list_entry(entry, struct evsel, core.node);
+ struct hists *hists = evsel__hists(evsel);
bool current_entry = ui_browser__is_current_entry(browser, row);
- unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
- const char *ev_name = perf_evsel__name(evsel);
+ unsigned long nr_events = hists->stats.nr_samples;
+ const char *ev_name = evsel__name(evsel);
char bf[256], unit;
const char *warn = " ";
size_t printed;
@@ -1767,22 +3476,23 @@ static void perf_evsel_menu__write(struct ui_browser *browser,
ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
HE_COLORSET_NORMAL);
- if (perf_evsel__is_group_event(evsel)) {
- struct perf_evsel *pos;
+ if (evsel__is_group_event(evsel)) {
+ struct evsel *pos;
- ev_name = perf_evsel__group_name(evsel);
+ ev_name = evsel__group_name(evsel);
for_each_group_member(pos, evsel) {
- nr_events += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
+ struct hists *pos_hists = evsel__hists(pos);
+ nr_events += pos_hists->stats.nr_samples;
}
}
nr_events = convert_unit(nr_events, &unit);
printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
unit, unit == ' ' ? "" : " ", ev_name);
- slsmg_printf("%s", bf);
+ ui_browser__printf(browser, "%s", bf);
- nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST];
+ nr_events = evsel->evlist->stats.nr_events[PERF_RECORD_LOST];
if (nr_events != 0) {
menu->lost_events = true;
if (!current_entry)
@@ -1793,19 +3503,20 @@ static void perf_evsel_menu__write(struct ui_browser *browser,
warn = bf;
}
- slsmg_write_nstring(warn, browser->width - printed);
+ ui_browser__write_nstring(browser, warn, browser->width - printed);
if (current_entry)
menu->selection = evsel;
}
-static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
+static int perf_evsel_menu__run(struct evsel_menu *menu,
int nr_events, const char *help,
- struct hist_browser_timer *hbt)
+ struct hist_browser_timer *hbt,
+ bool warn_lost_event)
{
- struct perf_evlist *evlist = menu->b.priv;
- struct perf_evsel *pos;
- const char *ev_name, *title = "Available samples";
+ struct evlist *evlist = menu->b.priv;
+ struct evsel *pos;
+ const char *title = "Available samples";
int delay_secs = hbt ? hbt->refresh : 0;
int key;
@@ -1818,9 +3529,12 @@ static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
switch (key) {
case K_TIMER:
- hbt->timer(hbt->arg);
+ if (hbt)
+ hbt->timer(hbt->arg);
- if (!menu->lost_events_warned && menu->lost_events) {
+ if (!menu->lost_events_warned &&
+ menu->lost_events &&
+ warn_lost_event) {
ui_browser__warn_lost_events(&menu->b);
menu->lost_events_warned = true;
}
@@ -1831,41 +3545,36 @@ static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
continue;
pos = menu->selection;
browse_hists:
- perf_evlist__set_selected(evlist, pos);
+ evlist__set_selected(evlist, pos);
/*
* Give the calling tool a chance to populate the non
* default evsel resorted hists tree.
*/
if (hbt)
hbt->timer(hbt->arg);
- ev_name = perf_evsel__name(pos);
- key = perf_evsel__hists_browse(pos, nr_events, help,
- ev_name, true, hbt,
- menu->min_pcnt,
- menu->env);
+ key = evsel__hists_browse(pos, nr_events, help, true, hbt,
+ menu->min_pcnt, menu->env,
+ warn_lost_event);
ui_browser__show_title(&menu->b, title);
switch (key) {
case K_TAB:
- if (pos->node.next == &evlist->entries)
- pos = list_entry(evlist->entries.next, struct perf_evsel, node);
+ if (pos->core.node.next == &evlist->core.entries)
+ pos = evlist__first(evlist);
else
- pos = list_entry(pos->node.next, struct perf_evsel, node);
+ pos = evsel__next(pos);
goto browse_hists;
case K_UNTAB:
- if (pos->node.prev == &evlist->entries)
- pos = list_entry(evlist->entries.prev, struct perf_evsel, node);
+ if (pos->core.node.prev == &evlist->core.entries)
+ pos = evlist__last(evlist);
else
- pos = list_entry(pos->node.prev, struct perf_evsel, node);
+ pos = evsel__prev(pos);
goto browse_hists;
- case K_ESC:
- if (!ui_browser__dialog_yesno(&menu->b,
- "Do you really want to exit?"))
- continue;
- /* Fall thru */
case K_SWITCH_INPUT_DATA:
+ case K_RELOAD:
case 'q':
case CTRL('c'):
goto out;
+ case K_ESC:
default:
continue;
}
@@ -1880,6 +3589,7 @@ browse_hists:
case CTRL('c'):
goto out;
default:
+ ui_browser__warn_unhandled_hotkey(&menu->b, key, delay_secs, NULL);
continue;
}
}
@@ -1889,27 +3599,25 @@ out:
return key;
}
-static bool filter_group_entries(struct ui_browser *self __maybe_unused,
+static bool filter_group_entries(struct ui_browser *browser __maybe_unused,
void *entry)
{
- struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
+ struct evsel *evsel = list_entry(entry, struct evsel, core.node);
- if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel))
+ if (symbol_conf.event_group && !evsel__is_group_leader(evsel))
return true;
return false;
}
-static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
- int nr_entries, const char *help,
- struct hist_browser_timer *hbt,
- float min_pcnt,
- struct perf_session_env *env)
+static int __evlist__tui_browse_hists(struct evlist *evlist, int nr_entries, const char *help,
+ struct hist_browser_timer *hbt, float min_pcnt, struct perf_env *env,
+ bool warn_lost_event)
{
- struct perf_evsel *pos;
- struct perf_evsel_menu menu = {
+ struct evsel *pos;
+ struct evsel_menu menu = {
.b = {
- .entries = &evlist->entries,
+ .entries = &evlist->core.entries,
.refresh = ui_browser__list_head_refresh,
.seek = ui_browser__list_head_seek,
.write = perf_evsel_menu__write,
@@ -1923,47 +3631,152 @@ static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
ui_helpline__push("Press ESC to exit");
- list_for_each_entry(pos, &evlist->entries, node) {
- const char *ev_name = perf_evsel__name(pos);
+ evlist__for_each_entry(evlist, pos) {
+ const char *ev_name = evsel__name(pos);
size_t line_len = strlen(ev_name) + 7;
if (menu.b.width < line_len)
menu.b.width = line_len;
}
- return perf_evsel_menu__run(&menu, nr_entries, help, hbt);
+ return perf_evsel_menu__run(&menu, nr_entries, help,
+ hbt, warn_lost_event);
+}
+
+static bool evlist__single_entry(struct evlist *evlist)
+{
+ int nr_entries = evlist->core.nr_entries;
+
+ if (nr_entries == 1)
+ return true;
+
+ if (nr_entries == 2) {
+ struct evsel *last = evlist__last(evlist);
+
+ if (evsel__is_dummy_event(last))
+ return true;
+ }
+
+ return false;
}
-int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
- struct hist_browser_timer *hbt,
- float min_pcnt,
- struct perf_session_env *env)
+int evlist__tui_browse_hists(struct evlist *evlist, const char *help, struct hist_browser_timer *hbt,
+ float min_pcnt, struct perf_env *env, bool warn_lost_event)
{
- int nr_entries = evlist->nr_entries;
+ int nr_entries = evlist->core.nr_entries;
-single_entry:
- if (nr_entries == 1) {
- struct perf_evsel *first = list_entry(evlist->entries.next,
- struct perf_evsel, node);
- const char *ev_name = perf_evsel__name(first);
+ if (evlist__single_entry(evlist)) {
+single_entry: {
+ struct evsel *first = evlist__first(evlist);
- return perf_evsel__hists_browse(first, nr_entries, help,
- ev_name, false, hbt, min_pcnt,
- env);
+ return evsel__hists_browse(first, nr_entries, help, false, hbt, min_pcnt,
+ env, warn_lost_event);
+ }
}
if (symbol_conf.event_group) {
- struct perf_evsel *pos;
+ struct evsel *pos;
nr_entries = 0;
- list_for_each_entry(pos, &evlist->entries, node)
- if (perf_evsel__is_group_leader(pos))
+ evlist__for_each_entry(evlist, pos) {
+ if (evsel__is_group_leader(pos))
nr_entries++;
+ }
if (nr_entries == 1)
goto single_entry;
}
- return __perf_evlist__tui_browse_hists(evlist, nr_entries, help,
- hbt, min_pcnt, env);
+ return __evlist__tui_browse_hists(evlist, nr_entries, help, hbt, min_pcnt, env,
+ warn_lost_event);
+}
+
+static int block_hists_browser__title(struct hist_browser *browser, char *bf,
+ size_t size)
+{
+ struct hists *hists = evsel__hists(browser->block_evsel);
+ const char *evname = evsel__name(browser->block_evsel);
+ unsigned long nr_samples = hists->stats.nr_samples;
+ int ret;
+
+ ret = scnprintf(bf, size, "# Samples: %lu", nr_samples);
+ if (evname)
+ scnprintf(bf + ret, size - ret, " of event '%s'", evname);
+
+ return 0;
+}
+
+int block_hists_tui_browse(struct block_hist *bh, struct evsel *evsel,
+ float min_percent, struct perf_env *env)
+{
+ struct hists *hists = &bh->block_hists;
+ struct hist_browser *browser;
+ int key = -1;
+ struct popup_action action;
+ char *br_cntr_text = NULL;
+ static const char help[] =
+ " q/ESC Quit \n"
+ " B Branch counter abbr list (Optional)\n";
+
+ browser = hist_browser__new(hists);
+ if (!browser)
+ return -1;
+
+ browser->block_evsel = evsel;
+ browser->title = block_hists_browser__title;
+ browser->min_pcnt = min_percent;
+ browser->env = env;
+
+ /* reset abort key so that it can get Ctrl-C as a key */
+ SLang_reset_tty();
+ SLang_init_tty(0, 0, 0);
+ SLtty_set_suspend_state(true);
+
+ memset(&action, 0, sizeof(action));
+
+ if (!annotation_br_cntr_abbr_list(&br_cntr_text, evsel, false))
+ annotate_opts.show_br_cntr = true;
+
+ while (1) {
+ key = hist_browser__run(browser, "? - help", true, 0);
+
+ switch (key) {
+ case 'q':
+ case K_ESC:
+ goto out;
+ case '?':
+ ui_browser__help_window(&browser->b, help);
+ break;
+ case 'a':
+ case K_ENTER:
+ if (!browser->selection ||
+ !browser->selection->sym) {
+ continue;
+ }
+
+ action.ms.map = browser->selection->map;
+ action.ms.sym = browser->selection->sym;
+ do_annotate(browser, &action);
+ continue;
+ case 'B':
+ if (br_cntr_text) {
+ ui__question_window("Branch counter abbr list",
+ br_cntr_text, "Press any key...", 0);
+ } else {
+ ui__question_window("Branch counter abbr list",
+ "\n The branch counter is not available.\n",
+ "Press any key...", 0);
+ }
+ continue;
+ default:
+ ui_browser__warn_unhandled_hotkey(&browser->b, key, 0,
+ ", use '?' to see actions");
+ continue;
+ }
+ }
+
+out:
+ hist_browser__delete(browser);
+ free(br_cntr_text);
+ return 0;
}
diff --git a/tools/perf/ui/browsers/hists.h b/tools/perf/ui/browsers/hists.h
new file mode 100644
index 000000000000..de46f6c56b0e
--- /dev/null
+++ b/tools/perf/ui/browsers/hists.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _PERF_UI_BROWSER_HISTS_H_
+#define _PERF_UI_BROWSER_HISTS_H_ 1
+
+#include "ui/browser.h"
+
+struct evsel;
+
+struct hist_browser {
+ struct ui_browser b;
+ struct hists *hists;
+ struct hist_entry *he_selection;
+ struct map_symbol *selection;
+ struct hist_browser_timer *hbt;
+ struct pstack *pstack;
+ struct perf_env *env;
+ struct evsel *block_evsel;
+ int print_seq;
+ bool show_dso;
+ bool show_headers;
+ float min_pcnt;
+ u64 nr_non_filtered_entries;
+ u64 nr_hierarchy_entries;
+ u64 nr_callchain_rows;
+ bool c2c_filter;
+
+ /* Get title string. */
+ int (*title)(struct hist_browser *browser,
+ char *bf, size_t size);
+};
+
+struct hist_browser *hist_browser__new(struct hists *hists);
+void hist_browser__delete(struct hist_browser *browser);
+int hist_browser__run(struct hist_browser *browser, const char *help,
+ bool warn_lost_event, int key);
+void hist_browser__init(struct hist_browser *browser,
+ struct hists *hists);
+#endif /* _PERF_UI_BROWSER_HISTS_H_ */
diff --git a/tools/perf/ui/browsers/map.c b/tools/perf/ui/browsers/map.c
index 95c7cfb8f2c6..c61ba3174a24 100644
--- a/tools/perf/ui/browsers/map.c
+++ b/tools/perf/ui/browsers/map.c
@@ -1,47 +1,51 @@
-#include "../libslang.h"
+// SPDX-License-Identifier: GPL-2.0
#include <elf.h>
#include <inttypes.h>
#include <sys/ttydefaults.h>
+#include <stdlib.h>
#include <string.h>
#include <linux/bitops.h>
-#include "../../util/util.h"
#include "../../util/debug.h"
+#include "../../util/map.h"
+#include "../../util/dso.h"
#include "../../util/symbol.h"
#include "../browser.h"
#include "../helpline.h"
#include "../keysyms.h"
#include "map.h"
+#include <linux/ctype.h>
+
struct map_browser {
struct ui_browser b;
struct map *map;
u8 addrlen;
};
-static void map_browser__write(struct ui_browser *self, void *nd, int row)
+static void map_browser__write(struct ui_browser *browser, void *nd, int row)
{
struct symbol *sym = rb_entry(nd, struct symbol, rb_node);
- struct map_browser *mb = container_of(self, struct map_browser, b);
- bool current_entry = ui_browser__is_current_entry(self, row);
+ struct map_browser *mb = container_of(browser, struct map_browser, b);
+ bool current_entry = ui_browser__is_current_entry(browser, row);
int width;
- ui_browser__set_percent_color(self, 0, current_entry);
- slsmg_printf("%*" PRIx64 " %*" PRIx64 " %c ",
- mb->addrlen, sym->start, mb->addrlen, sym->end,
- sym->binding == STB_GLOBAL ? 'g' :
- sym->binding == STB_LOCAL ? 'l' : 'w');
- width = self->width - ((mb->addrlen * 2) + 4);
+ ui_browser__set_percent_color(browser, 0, current_entry);
+ ui_browser__printf(browser, "%*" PRIx64 " %*" PRIx64 " %c ",
+ mb->addrlen, sym->start, mb->addrlen, sym->end,
+ sym->binding == STB_GLOBAL ? 'g' :
+ sym->binding == STB_LOCAL ? 'l' : 'w');
+ width = browser->width - ((mb->addrlen * 2) + 4);
if (width > 0)
- slsmg_write_nstring(sym->name, width);
+ ui_browser__write_nstring(browser, sym->name, width);
}
/* FIXME uber-kludgy, see comment on cmd_report... */
-static u32 *symbol__browser_index(struct symbol *self)
+static u32 *symbol__browser_index(struct symbol *browser)
{
- return ((void *)self) - sizeof(struct rb_node) - sizeof(u32);
+ return ((void *)browser) - sizeof(struct rb_node) - sizeof(u32);
}
-static int map_browser__search(struct map_browser *self)
+static int map_browser__search(struct map_browser *browser)
{
char target[512];
struct symbol *sym;
@@ -53,39 +57,41 @@ static int map_browser__search(struct map_browser *self)
if (target[0] == '0' && tolower(target[1]) == 'x') {
u64 addr = strtoull(target, NULL, 16);
- sym = map__find_symbol(self->map, addr, NULL);
+ sym = map__find_symbol(browser->map, addr);
} else
- sym = map__find_symbol_by_name(self->map, target, NULL);
+ sym = map__find_symbol_by_name(browser->map, target);
if (sym != NULL) {
u32 *idx = symbol__browser_index(sym);
- self->b.top = &sym->rb_node;
- self->b.index = self->b.top_idx = *idx;
+ browser->b.top = &sym->rb_node;
+ browser->b.index = browser->b.top_idx = *idx;
} else
ui_helpline__fpush("%s not found!", target);
return 0;
}
-static int map_browser__run(struct map_browser *self)
+static int map_browser__run(struct map_browser *browser)
{
int key;
- if (ui_browser__show(&self->b, self->map->dso->long_name,
- "Press <- or ESC to exit, %s / to search",
- verbose ? "" : "restart with -v to use") < 0)
+ if (ui_browser__show(&browser->b, dso__long_name(map__dso(browser->map)),
+ "Press ESC to exit, %s / to search",
+ verbose > 0 ? "" : "restart with -v to use") < 0)
return -1;
while (1) {
- key = ui_browser__run(&self->b, 0);
+ key = ui_browser__run(&browser->b, 0);
switch (key) {
case '/':
- if (verbose)
- map_browser__search(self);
+ if (verbose > 0)
+ map_browser__search(browser);
+ /* fall thru */
default:
- break;
+ ui_browser__warn_unhandled_hotkey(&browser->b, key, 0, NULL);
+ continue;
case K_LEFT:
case K_ESC:
case 'q':
@@ -94,20 +100,20 @@ static int map_browser__run(struct map_browser *self)
}
}
out:
- ui_browser__hide(&self->b);
+ ui_browser__hide(&browser->b);
return key;
}
-int map__browse(struct map *self)
+int map__browse(struct map *map)
{
struct map_browser mb = {
.b = {
- .entries = &self->dso->symbols[self->type],
+ .entries = dso__symbols(map__dso(map)),
.refresh = ui_browser__rb_tree_refresh,
.seek = ui_browser__rb_tree_seek,
.write = map_browser__write,
},
- .map = self,
+ .map = map,
};
struct rb_node *nd;
char tmp[BITS_PER_LONG / 4];
@@ -118,7 +124,7 @@ int map__browse(struct map *self)
if (maxaddr < pos->end)
maxaddr = pos->end;
- if (verbose) {
+ if (verbose > 0) {
u32 *idx = symbol__browser_index(pos);
*idx = mb.b.nr_entries;
}
diff --git a/tools/perf/ui/browsers/map.h b/tools/perf/ui/browsers/map.h
index df8581a43e17..0ed7dbb3a373 100644
--- a/tools/perf/ui/browsers/map.h
+++ b/tools/perf/ui/browsers/map.h
@@ -1,6 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_MAP_BROWSER_H_
#define _PERF_UI_MAP_BROWSER_H_ 1
struct map;
-int map__browse(struct map *self);
+int map__browse(struct map *map);
#endif /* _PERF_UI_MAP_BROWSER_H_ */
diff --git a/tools/perf/ui/browsers/res_sample.c b/tools/perf/ui/browsers/res_sample.c
new file mode 100644
index 000000000000..5f60e515b12e
--- /dev/null
+++ b/tools/perf/ui/browsers/res_sample.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Display a menu with individual samples to browse with perf script */
+#include "hist.h"
+#include "evsel.h"
+#include "hists.h"
+#include "sort.h"
+#include "config.h"
+#include "time-utils.h"
+#include "../util.h"
+#include "../../util/util.h" // perf_exe()
+#include "../../perf.h"
+#include <stdlib.h>
+#include <string.h>
+#include <linux/time64.h>
+#include <linux/zalloc.h>
+
+static u64 context_len = 10 * NSEC_PER_MSEC;
+
+static int res_sample_config(const char *var, const char *value, void *data __maybe_unused)
+{
+ if (!strcmp(var, "samples.context"))
+ return perf_config_u64(&context_len, var, value);
+ return 0;
+}
+
+void res_sample_init(void)
+{
+ perf_config(res_sample_config, NULL);
+}
+
+int res_sample_browse(struct res_sample *res_samples, int num_res,
+ struct evsel *evsel, enum rstype rstype)
+{
+ char **names;
+ int i, n;
+ int choice;
+ char *cmd;
+ char pbuf[256], tidbuf[32], cpubuf[32];
+ const char *perf = perf_exe(pbuf, sizeof pbuf);
+ char trange[128], tsample[64];
+ struct res_sample *r;
+ char extra_format[256];
+
+ names = calloc(num_res, sizeof(char *));
+ if (!names)
+ return -1;
+ for (i = 0; i < num_res; i++) {
+ char tbuf[64];
+
+ timestamp__scnprintf_nsec(res_samples[i].time, tbuf, sizeof tbuf);
+ if (asprintf(&names[i], "%s: CPU %d tid %d", tbuf,
+ res_samples[i].cpu, res_samples[i].tid) < 0) {
+ while (--i >= 0)
+ zfree(&names[i]);
+ free(names);
+ return -1;
+ }
+ }
+ choice = ui__popup_menu(num_res, names, NULL);
+ for (i = 0; i < num_res; i++)
+ zfree(&names[i]);
+ free(names);
+
+ if (choice < 0 || choice >= num_res)
+ return -1;
+ r = &res_samples[choice];
+
+ n = timestamp__scnprintf_nsec(r->time - context_len, trange, sizeof trange);
+ trange[n++] = ',';
+ timestamp__scnprintf_nsec(r->time + context_len, trange + n, sizeof trange - n);
+
+ timestamp__scnprintf_nsec(r->time, tsample, sizeof tsample);
+
+ attr_to_script(extra_format, &evsel->core.attr);
+
+ if (asprintf(&cmd, "%s script %s%s --time %s %s%s %s%s --ns %s %s %s %s %s | less +/%s",
+ perf,
+ input_name ? "-i " : "",
+ input_name ? input_name : "",
+ trange,
+ r->cpu >= 0 ? "--cpu " : "",
+ r->cpu >= 0 ? (sprintf(cpubuf, "%d", r->cpu), cpubuf) : "",
+ r->tid ? "--tid " : "",
+ r->tid ? (sprintf(tidbuf, "%d", r->tid), tidbuf) : "",
+ extra_format,
+ rstype == A_ASM ? "-F +disasm" :
+ rstype == A_SOURCE ? "-F +srcline,+srccode" : "",
+ symbol_conf.inline_name ? "--inline" : "",
+ "--show-lost-events ",
+ r->tid ? "--show-switch-events --show-task-events " : "",
+ tsample) < 0)
+ return -1;
+ run_script(cmd);
+ free(cmd);
+ return 0;
+}
diff --git a/tools/perf/ui/browsers/scripts.c b/tools/perf/ui/browsers/scripts.c
index 12f009e61e94..1e8c2c2f952d 100644
--- a/tools/perf/ui/browsers/scripts.c
+++ b/tools/perf/ui/browsers/scripts.c
@@ -1,33 +1,19 @@
-#include <elf.h>
-#include <inttypes.h>
-#include <sys/ttydefaults.h>
-#include <string.h>
-#include "../../util/sort.h"
-#include "../../util/util.h"
+// SPDX-License-Identifier: GPL-2.0
+#include "../../util/util.h" // perf_exe()
+#include "../util.h"
+#include "../../util/evlist.h"
#include "../../util/hist.h"
#include "../../util/debug.h"
+#include "../../util/session.h"
#include "../../util/symbol.h"
#include "../browser.h"
-#include "../helpline.h"
#include "../libslang.h"
-
-/* 2048 lines should be enough for a script output */
-#define MAX_LINES 2048
-
-/* 160 bytes for one output line */
-#define AVERAGE_LINE_LEN 160
-
-struct script_line {
- struct list_head node;
- char line[AVERAGE_LINE_LEN];
-};
-
-struct perf_script_browser {
- struct ui_browser b;
- struct list_head entries;
- const char *script_name;
- int nr_lines;
-};
+#include "config.h"
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/zalloc.h>
+#include <subcmd/exec-cmd.h>
+#include <stdlib.h>
#define SCRIPT_NAMELEN 128
#define SCRIPT_MAX_NO 64
@@ -39,150 +25,343 @@ struct perf_script_browser {
*/
#define SCRIPT_FULLPATH_LEN 256
-/*
- * When success, will copy the full path of the selected script
- * into the buffer pointed by script_name, and return 0.
- * Return -1 on failure.
- */
-static int list_scripts(char *script_name)
-{
- char *buf, *names[SCRIPT_MAX_NO], *paths[SCRIPT_MAX_NO];
- int i, num, choice, ret = -1;
+struct script_config {
+ const char **names;
+ char **paths;
+ int index;
+ const char *perf;
+ char extra_format[256];
+};
- /* Preset the script name to SCRIPT_NAMELEN */
- buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN));
- if (!buf)
- return ret;
+void attr_to_script(char *extra_format, struct perf_event_attr *attr)
+{
+ extra_format[0] = 0;
+ if (attr->read_format & PERF_FORMAT_GROUP)
+ strcat(extra_format, " -F +metric");
+ if (attr->sample_type & PERF_SAMPLE_BRANCH_STACK)
+ strcat(extra_format, " -F +brstackinsn --xed");
+ if (attr->sample_type & PERF_SAMPLE_REGS_INTR)
+ strcat(extra_format, " -F +iregs");
+ if (attr->sample_type & PERF_SAMPLE_REGS_USER)
+ strcat(extra_format, " -F +uregs");
+ if (attr->sample_type & PERF_SAMPLE_PHYS_ADDR)
+ strcat(extra_format, " -F +phys_addr");
+}
- for (i = 0; i < SCRIPT_MAX_NO; i++) {
- names[i] = buf + i * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN);
- paths[i] = names[i] + SCRIPT_NAMELEN;
- }
+static int add_script_option(const char *name, const char *opt,
+ struct script_config *c)
+{
+ c->names[c->index] = name;
+ if (asprintf(&c->paths[c->index],
+ "%s script %s -F +metric %s %s",
+ c->perf, opt, symbol_conf.inline_name ? " --inline" : "",
+ c->extra_format) < 0)
+ return -1;
+ c->index++;
+ return 0;
+}
- num = find_scripts(names, paths);
- if (num > 0) {
- choice = ui__popup_menu(num, names);
- if (choice < num && choice >= 0) {
- strcpy(script_name, paths[choice]);
- ret = 0;
- }
- }
+static int scripts_config(const char *var, const char *value, void *data)
+{
+ struct script_config *c = data;
- free(buf);
- return ret;
+ if (!strstarts(var, "scripts."))
+ return -1;
+ if (c->index >= SCRIPT_MAX_NO)
+ return -1;
+ c->names[c->index] = strdup(var + 7);
+ if (!c->names[c->index])
+ return -1;
+ if (asprintf(&c->paths[c->index], "%s %s", value,
+ c->extra_format) < 0)
+ return -1;
+ c->index++;
+ return 0;
}
-static void script_browser__write(struct ui_browser *browser,
- void *entry, int row)
+/*
+ * Some scripts specify the required events in their "xxx-record" file,
+ * this function will check if the events in perf.data match those
+ * mentioned in the "xxx-record".
+ *
+ * Fixme: All existing "xxx-record" are all in good formats "-e event ",
+ * which is covered well now. And new parsing code should be added to
+ * cover the future complex formats like event groups etc.
+ */
+static int check_ev_match(int dir_fd, const char *scriptname, struct perf_session *session)
{
- struct script_line *sline = list_entry(entry, struct script_line, node);
- bool current_entry = ui_browser__is_current_entry(browser, row);
+ char line[BUFSIZ];
+ FILE *fp;
+
+ {
+ char filename[NAME_MAX + 5];
+ int fd;
+
+ scnprintf(filename, sizeof(filename), "bin/%s-record", scriptname);
+ fd = openat(dir_fd, filename, O_RDONLY);
+ if (fd == -1)
+ return -1;
+ fp = fdopen(fd, "r");
+ if (!fp)
+ return -1;
+ }
- ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
- HE_COLORSET_NORMAL);
+ while (fgets(line, sizeof(line), fp)) {
+ char *p = skip_spaces(line);
+
+ if (*p == '#')
+ continue;
+
+ while (strlen(p)) {
+ int match, len;
+ struct evsel *pos;
+ char evname[128];
+
+ p = strstr(p, "-e");
+ if (!p)
+ break;
+
+ p += 2;
+ p = skip_spaces(p);
+ len = strcspn(p, " \t");
+ if (!len)
+ break;
+
+ snprintf(evname, len + 1, "%s", p);
+
+ match = 0;
+ evlist__for_each_entry(session->evlist, pos) {
+ if (evsel__name_is(pos, evname)) {
+ match = 1;
+ break;
+ }
+ }
+
+ if (!match) {
+ fclose(fp);
+ return -1;
+ }
+ }
+ }
- slsmg_write_nstring(sline->line, browser->width);
+ fclose(fp);
+ return 0;
}
-static int script_browser__run(struct perf_script_browser *self)
+/*
+ * Return -1 if none is found, otherwise the actual scripts number.
+ *
+ * Currently the only user of this function is the script browser, which
+ * will list all statically runnable scripts, select one, execute it and
+ * show the output in a perf browser.
+ */
+static int find_scripts(char **scripts_array, char **scripts_path_array, int num,
+ int pathlen)
{
- int key;
-
- if (ui_browser__show(&self->b, self->script_name,
- "Press <- or ESC to exit") < 0)
+ struct dirent *script_dirent, *lang_dirent;
+ int scripts_dir_fd, lang_dir_fd;
+ DIR *scripts_dir, *lang_dir;
+ struct perf_session *session;
+ struct perf_data data = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+ char *temp;
+ int i = 0;
+ const char *exec_path = get_argv_exec_path();
+
+ session = perf_session__new(&data, NULL);
+ if (IS_ERR(session))
+ return PTR_ERR(session);
+
+ {
+ char scripts_path[PATH_MAX];
+
+ snprintf(scripts_path, sizeof(scripts_path), "%s/scripts", exec_path);
+ scripts_dir_fd = open(scripts_path, O_DIRECTORY);
+ pr_err("Failed to open directory '%s'", scripts_path);
+ if (scripts_dir_fd == -1) {
+ perf_session__delete(session);
+ return -1;
+ }
+ }
+ scripts_dir = fdopendir(scripts_dir_fd);
+ if (!scripts_dir) {
+ close(scripts_dir_fd);
+ perf_session__delete(session);
return -1;
+ }
- while (1) {
- key = ui_browser__run(&self->b, 0);
-
- /* We can add some special key handling here if needed */
- break;
+ while ((lang_dirent = readdir(scripts_dir)) != NULL) {
+ if (lang_dirent->d_type != DT_DIR &&
+ (lang_dirent->d_type == DT_UNKNOWN &&
+ !is_directory_at(scripts_dir_fd, lang_dirent->d_name)))
+ continue;
+ if (!strcmp(lang_dirent->d_name, ".") || !strcmp(lang_dirent->d_name, ".."))
+ continue;
+
+#ifndef HAVE_LIBPERL_SUPPORT
+ if (strstr(lang_dirent->d_name, "perl"))
+ continue;
+#endif
+#ifndef HAVE_LIBPYTHON_SUPPORT
+ if (strstr(lang_dirent->d_name, "python"))
+ continue;
+#endif
+
+ lang_dir_fd = openat(scripts_dir_fd, lang_dirent->d_name, O_DIRECTORY);
+ if (lang_dir_fd == -1)
+ continue;
+ lang_dir = fdopendir(lang_dir_fd);
+ if (!lang_dir) {
+ close(lang_dir_fd);
+ continue;
+ }
+ while ((script_dirent = readdir(lang_dir)) != NULL) {
+ if (script_dirent->d_type == DT_DIR)
+ continue;
+ if (script_dirent->d_type == DT_UNKNOWN &&
+ is_directory_at(lang_dir_fd, script_dirent->d_name))
+ continue;
+ /* Skip those real time scripts: xxxtop.p[yl] */
+ if (strstr(script_dirent->d_name, "top."))
+ continue;
+ if (i >= num)
+ break;
+ scnprintf(scripts_path_array[i], pathlen, "%s/scripts/%s/%s",
+ exec_path,
+ lang_dirent->d_name,
+ script_dirent->d_name);
+ temp = strchr(script_dirent->d_name, '.');
+ snprintf(scripts_array[i],
+ (temp - script_dirent->d_name) + 1,
+ "%s", script_dirent->d_name);
+
+ if (check_ev_match(lang_dir_fd, scripts_array[i], session))
+ continue;
+
+ i++;
+ }
+ closedir(lang_dir);
}
- ui_browser__hide(&self->b);
- return key;
+ closedir(scripts_dir);
+ perf_session__delete(session);
+ return i;
}
-
-int script_browse(const char *script_opt)
+/*
+ * When success, will copy the full path of the selected script
+ * into the buffer pointed by script_name, and return 0.
+ * Return -1 on failure.
+ */
+static int list_scripts(char *script_name, bool *custom,
+ struct evsel *evsel)
{
- char cmd[SCRIPT_FULLPATH_LEN*2], script_name[SCRIPT_FULLPATH_LEN];
- char *line = NULL;
- size_t len = 0;
- ssize_t retlen;
- int ret = -1, nr_entries = 0;
- FILE *fp;
- void *buf;
- struct script_line *sline;
-
- struct perf_script_browser script = {
- .b = {
- .refresh = ui_browser__list_head_refresh,
- .seek = ui_browser__list_head_seek,
- .write = script_browser__write,
- },
- .script_name = script_name,
+ char *buf, *paths[SCRIPT_MAX_NO], *names[SCRIPT_MAX_NO];
+ int i, num, choice;
+ int ret = 0;
+ int max_std, custom_perf;
+ char pbuf[256];
+ const char *perf = perf_exe(pbuf, sizeof pbuf);
+ struct script_config scriptc = {
+ .names = (const char **)names,
+ .paths = paths,
+ .perf = perf
};
- INIT_LIST_HEAD(&script.entries);
+ script_name[0] = 0;
- /* Save each line of the output in one struct script_line object. */
- buf = zalloc((sizeof(*sline)) * MAX_LINES);
+ /* Preset the script name to SCRIPT_NAMELEN */
+ buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN));
if (!buf)
return -1;
- sline = buf;
-
- memset(script_name, 0, SCRIPT_FULLPATH_LEN);
- if (list_scripts(script_name))
- goto exit;
-
- sprintf(cmd, "perf script -s %s ", script_name);
- if (script_opt)
- strcat(cmd, script_opt);
-
- if (input_name) {
- strcat(cmd, " -i ");
- strcat(cmd, input_name);
+ if (evsel)
+ attr_to_script(scriptc.extra_format, &evsel->core.attr);
+ add_script_option("Show individual samples", "", &scriptc);
+ add_script_option("Show individual samples with assembler", "-F +disasm",
+ &scriptc);
+ add_script_option("Show individual samples with source", "-F +srcline,+srccode",
+ &scriptc);
+ perf_config(scripts_config, &scriptc);
+ custom_perf = scriptc.index;
+ add_script_option("Show samples with custom perf script arguments", "", &scriptc);
+ i = scriptc.index;
+ max_std = i;
+
+ for (; i < SCRIPT_MAX_NO; i++) {
+ names[i] = buf + (i - max_std) * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN);
+ paths[i] = names[i] + SCRIPT_NAMELEN;
}
- strcat(cmd, " 2>&1");
-
- fp = popen(cmd, "r");
- if (!fp)
- goto exit;
-
- while ((retlen = getline(&line, &len, fp)) != -1) {
- strncpy(sline->line, line, AVERAGE_LINE_LEN);
-
- /* If one output line is very large, just cut it short */
- if (retlen >= AVERAGE_LINE_LEN) {
- sline->line[AVERAGE_LINE_LEN - 1] = '\0';
- sline->line[AVERAGE_LINE_LEN - 2] = '\n';
+ num = find_scripts(names + max_std, paths + max_std, SCRIPT_MAX_NO - max_std,
+ SCRIPT_FULLPATH_LEN);
+ if (num < 0)
+ num = 0;
+ choice = ui__popup_menu(num + max_std, (char * const *)names, NULL);
+ if (choice < 0) {
+ ret = -1;
+ goto out;
+ }
+ if (choice == custom_perf) {
+ char script_args[50];
+ int key = ui_browser__input_window("perf script command",
+ "Enter perf script command line (without perf script prefix)",
+ script_args, "", 0);
+ if (key != K_ENTER) {
+ ret = -1;
+ goto out;
}
- list_add_tail(&sline->node, &script.entries);
+ sprintf(script_name, "%s script %s", perf, script_args);
+ } else if (choice < num + max_std) {
+ strcpy(script_name, paths[choice]);
+ }
+ *custom = choice >= max_std;
+
+out:
+ free(buf);
+ for (i = 0; i < max_std; i++)
+ zfree(&paths[i]);
+ return ret;
+}
- if (script.b.width < retlen)
- script.b.width = retlen;
+void run_script(char *cmd)
+{
+ pr_debug("Running %s\n", cmd);
+ SLang_reset_tty();
+ if (system(cmd) < 0)
+ pr_warning("Cannot run %s\n", cmd);
+ /*
+ * SLang doesn't seem to reset the whole terminal, so be more
+ * forceful to get back to the original state.
+ */
+ printf("\033[c\033[H\033[J");
+ fflush(stdout);
+ SLang_init_tty(0, 0, 0);
+ SLtty_set_suspend_state(true);
+ SLsmg_refresh();
+}
- if (nr_entries++ >= MAX_LINES - 1)
- break;
- sline++;
- }
+int script_browse(const char *script_opt, struct evsel *evsel)
+{
+ char *cmd, script_name[SCRIPT_FULLPATH_LEN];
+ bool custom = false;
- if (script.b.width > AVERAGE_LINE_LEN)
- script.b.width = AVERAGE_LINE_LEN;
+ memset(script_name, 0, SCRIPT_FULLPATH_LEN);
+ if (list_scripts(script_name, &custom, evsel))
+ return -1;
- if (line)
- free(line);
- pclose(fp);
+ if (asprintf(&cmd, "%s%s %s %s%s 2>&1 | less",
+ custom ? "perf script -s " : "",
+ script_name,
+ script_opt ? script_opt : "",
+ input_name ? "-i " : "",
+ input_name ? input_name : "") < 0)
+ return -1;
- script.nr_lines = nr_entries;
- script.b.nr_entries = nr_entries;
- script.b.entries = &script.entries;
+ run_script(cmd);
+ free(cmd);
- ret = script_browser__run(&script);
-exit:
- free(buf);
- return ret;
+ return 0;
}