summaryrefslogtreecommitdiff
path: root/tools/perf/ui
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/ui')
-rw-r--r--tools/perf/ui/Build13
-rw-r--r--tools/perf/ui/browser.c218
-rw-r--r--tools/perf/ui/browser.h57
-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
-rw-r--r--tools/perf/ui/gtk/Build14
-rw-r--r--tools/perf/ui/gtk/annotate.c95
-rw-r--r--tools/perf/ui/gtk/browser.c7
-rw-r--r--tools/perf/ui/gtk/gtk.h22
-rw-r--r--tools/perf/ui/gtk/helpline.c5
-rw-r--r--tools/perf/ui/gtk/hists.c599
-rw-r--r--tools/perf/ui/gtk/progress.c22
-rw-r--r--tools/perf/ui/gtk/setup.c7
-rw-r--r--tools/perf/ui/gtk/util.c12
-rw-r--r--tools/perf/ui/helpline.c12
-rw-r--r--tools/perf/ui/helpline.h4
-rw-r--r--tools/perf/ui/hist.c1404
-rw-r--r--tools/perf/ui/keysyms.c44
-rw-r--r--tools/perf/ui/keysyms.h4
-rw-r--r--tools/perf/ui/libslang.h19
-rw-r--r--tools/perf/ui/progress.c43
-rw-r--r--tools/perf/ui/progress.h30
-rw-r--r--tools/perf/ui/setup.c106
-rw-r--r--tools/perf/ui/stdio/hist.c752
-rw-r--r--tools/perf/ui/tui/Build4
-rw-r--r--tools/perf/ui/tui/helpline.c16
-rw-r--r--tools/perf/ui/tui/progress.c69
-rw-r--r--tools/perf/ui/tui/setup.c83
-rw-r--r--tools/perf/ui/tui/tui.h7
-rw-r--r--tools/perf/ui/tui/util.c77
-rw-r--r--tools/perf/ui/ui.h26
-rw-r--r--tools/perf/ui/util.c10
-rw-r--r--tools/perf/ui/util.h5
41 files changed, 7660 insertions, 2420 deletions
diff --git a/tools/perf/ui/Build b/tools/perf/ui/Build
new file mode 100644
index 000000000000..6005f813c9e3
--- /dev/null
+++ b/tools/perf/ui/Build
@@ -0,0 +1,13 @@
+perf-ui-y += setup.o
+perf-ui-y += helpline.o
+perf-ui-y += progress.o
+perf-ui-y += util.o
+perf-ui-y += hist.o
+perf-ui-y += stdio/hist.o
+
+CFLAGS_setup.o += -DLIBDIR="BUILD_STR($(LIBDIR))"
+
+perf-ui-$(CONFIG_SLANG) += browser.o
+perf-ui-$(CONFIG_SLANG) += keysyms.o
+perf-ui-$(CONFIG_SLANG) += browsers/
+perf-ui-$(CONFIG_SLANG) += tui/
diff --git a/tools/perf/ui/browser.c b/tools/perf/ui/browser.c
index bbc782e364b0..dc88427b4ae5 100644
--- a/tools/perf/ui/browser.c
+++ b/tools/perf/ui/browser.c
@@ -1,18 +1,21 @@
-#include "../util.h"
-#include "../cache.h"
-#include "../../perf.h"
+// SPDX-License-Identifier: GPL-2.0
+#include "../util/string2.h"
+#include "../util/config.h"
#include "libslang.h"
#include "ui.h"
#include "util.h"
#include <linux/compiler.h>
#include <linux/list.h>
#include <linux/rbtree.h>
+#include <linux/string.h>
#include <stdlib.h>
#include <sys/ttydefaults.h>
#include "browser.h"
#include "helpline.h"
#include "keysyms.h"
-#include "../color.h"
+#include "../util/color.h"
+#include <linux/ctype.h>
+#include <linux/zalloc.h>
static int ui_browser__percent_color(struct ui_browser *browser,
double percent, bool current)
@@ -41,11 +44,36 @@ void ui_browser__set_percent_color(struct ui_browser *browser,
ui_browser__set_color(browser, color);
}
-void ui_browser__gotorc(struct ui_browser *browser, int y, int x)
+void ui_browser__gotorc_title(struct ui_browser *browser, int y, int x)
{
SLsmg_gotorc(browser->y + y, browser->x + x);
}
+void ui_browser__gotorc(struct ui_browser *browser, int y, int x)
+{
+ SLsmg_gotorc(browser->y + y + browser->extra_title_lines, browser->x + x);
+}
+
+void ui_browser__write_nstring(struct ui_browser *browser __maybe_unused, const char *msg,
+ unsigned int width)
+{
+ SLsmg_write_nstring(msg, width);
+}
+
+void ui_browser__vprintf(struct ui_browser *browser __maybe_unused, const char *fmt, va_list args)
+{
+ SLsmg_vprintf(fmt, args);
+}
+
+void ui_browser__printf(struct ui_browser *browser __maybe_unused, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ ui_browser__vprintf(browser, fmt, args);
+ va_end(args);
+}
+
static struct list_head *
ui_browser__list_head_filter_entries(struct ui_browser *browser,
struct list_head *pos)
@@ -150,7 +178,7 @@ unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser)
while (nd != NULL) {
ui_browser__gotorc(browser, row, 0);
browser->write(browser, nd, row);
- if (++row == browser->height)
+ if (++row == browser->rows)
break;
nd = rb_next(nd);
}
@@ -166,7 +194,8 @@ bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row)
void ui_browser__refresh_dimensions(struct ui_browser *browser)
{
browser->width = SLtt_Screen_Cols - 1;
- browser->height = SLtt_Screen_Rows - 2;
+ browser->height = browser->rows = SLtt_Screen_Rows - 2;
+ browser->rows -= browser->extra_title_lines;
browser->y = 1;
browser->x = 0;
}
@@ -174,7 +203,7 @@ void ui_browser__refresh_dimensions(struct ui_browser *browser)
void ui_browser__handle_resize(struct ui_browser *browser)
{
ui__refresh_dimensions(false);
- ui_browser__show(browser, browser->title, ui_helpline__current);
+ ui_browser__show(browser, browser->title ?: "", ui_helpline__current);
ui_browser__refresh(browser);
}
@@ -194,7 +223,7 @@ int ui_browser__warning(struct ui_browser *browser, int timeout,
ui_helpline__vpush(format, args);
va_end(args);
} else {
- while ((key == ui__question_window("Warning!", text,
+ while ((key = ui__question_window("Warning!", text,
"Press any key...",
timeout)) == K_RESIZE)
ui_browser__handle_resize(browser);
@@ -204,6 +233,14 @@ int ui_browser__warning(struct ui_browser *browser, int timeout,
return key;
}
+int ui_browser__warn_unhandled_hotkey(struct ui_browser *browser, int key, int timeout, const char *help)
+{
+ char kname[32];
+
+ key_name(key, kname, sizeof(kname));
+ return ui_browser__warning(browser, timeout, "\n'%s' key not associated%s!\n", kname, help ?: "");
+}
+
int ui_browser__help_window(struct ui_browser *browser, const char *text)
{
int key;
@@ -234,14 +271,14 @@ void __ui_browser__show_title(struct ui_browser *browser, const char *title)
{
SLsmg_gotorc(0, 0);
ui_browser__set_color(browser, HE_COLORSET_ROOT);
- slsmg_write_nstring(title, browser->width + 1);
+ ui_browser__write_nstring(browser, title, browser->width + 1);
}
void ui_browser__show_title(struct ui_browser *browser, const char *title)
{
- pthread_mutex_lock(&ui__lock);
+ mutex_lock(&ui__lock);
__ui_browser__show_title(browser, title);
- pthread_mutex_unlock(&ui__lock);
+ mutex_unlock(&ui__lock);
}
int ui_browser__show(struct ui_browser *browser, const char *title,
@@ -250,38 +287,41 @@ int ui_browser__show(struct ui_browser *browser, const char *title,
int err;
va_list ap;
- ui_browser__refresh_dimensions(browser);
+ if (browser->refresh_dimensions == NULL)
+ browser->refresh_dimensions = ui_browser__refresh_dimensions;
- pthread_mutex_lock(&ui__lock);
+ browser->refresh_dimensions(browser);
+
+ mutex_lock(&ui__lock);
__ui_browser__show_title(browser, title);
- browser->title = title;
- free(browser->helpline);
- browser->helpline = NULL;
+ free(browser->title);
+ browser->title = strdup(title);
+ zfree(&browser->helpline);
va_start(ap, helpline);
err = vasprintf(&browser->helpline, helpline, ap);
va_end(ap);
if (err > 0)
ui_helpline__push(browser->helpline);
- pthread_mutex_unlock(&ui__lock);
+ mutex_unlock(&ui__lock);
return err ? 0 : -1;
}
-void ui_browser__hide(struct ui_browser *browser __maybe_unused)
+void ui_browser__hide(struct ui_browser *browser)
{
- pthread_mutex_lock(&ui__lock);
+ mutex_lock(&ui__lock);
ui_helpline__pop();
- free(browser->helpline);
- browser->helpline = NULL;
- pthread_mutex_unlock(&ui__lock);
+ zfree(&browser->helpline);
+ zfree(&browser->title);
+ mutex_unlock(&ui__lock);
}
static void ui_browser__scrollbar_set(struct ui_browser *browser)
{
int height = browser->height, h = 0, pct = 0,
col = browser->width,
- row = browser->y - 1;
+ row = 0;
if (browser->nr_entries > 1) {
pct = ((browser->index * (browser->height - 1)) /
@@ -312,17 +352,19 @@ static int __ui_browser__refresh(struct ui_browser *browser)
else
width += 1;
- SLsmg_fill_region(browser->y + row, browser->x,
- browser->height - row, width, ' ');
+ SLsmg_fill_region(browser->y + row + browser->extra_title_lines, browser->x,
+ browser->rows - row, width, ' ');
+ if (browser->nr_entries == 0 && browser->no_samples_msg)
+ __ui__info_window(NULL, browser->no_samples_msg, NULL);
return 0;
}
int ui_browser__refresh(struct ui_browser *browser)
{
- pthread_mutex_lock(&ui__lock);
+ mutex_lock(&ui__lock);
__ui_browser__refresh(browser);
- pthread_mutex_unlock(&ui__lock);
+ mutex_unlock(&ui__lock);
return 0;
}
@@ -358,10 +400,10 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs)
while (1) {
off_t offset;
- pthread_mutex_lock(&ui__lock);
+ mutex_lock(&ui__lock);
err = __ui_browser__refresh(browser);
SLsmg_refresh();
- pthread_mutex_unlock(&ui__lock);
+ mutex_unlock(&ui__lock);
if (err < 0)
break;
@@ -369,7 +411,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs)
if (key == K_RESIZE) {
ui__refresh_dimensions(false);
- ui_browser__refresh_dimensions(browser);
+ browser->refresh_dimensions(browser);
__ui_browser__show_title(browser, browser->title);
ui_helpline__puts(browser->helpline);
continue;
@@ -377,6 +419,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs)
if (browser->use_navkeypressed && !browser->navkeypressed) {
if (key == K_DOWN || key == K_UP ||
+ (browser->columns && (key == K_LEFT || key == K_RIGHT)) ||
key == K_PGDN || key == K_PGUP ||
key == K_HOME || key == K_END ||
key == ' ') {
@@ -391,7 +434,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs)
if (browser->index == browser->nr_entries - 1)
break;
++browser->index;
- if (browser->index == browser->top_idx + browser->height) {
+ if (browser->index == browser->top_idx + browser->rows) {
++browser->top_idx;
browser->seek(browser, +1, SEEK_CUR);
}
@@ -405,12 +448,26 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs)
browser->seek(browser, -1, SEEK_CUR);
}
break;
+ case K_RIGHT:
+ if (!browser->columns)
+ goto out;
+ if (browser->horiz_scroll < browser->columns - 1)
+ ++browser->horiz_scroll;
+ break;
+ case K_LEFT:
+ if (!browser->columns)
+ goto out;
+ if (browser->horiz_scroll != 0)
+ --browser->horiz_scroll;
+ else
+ goto out;
+ break;
case K_PGDN:
case ' ':
- if (browser->top_idx + browser->height > browser->nr_entries - 1)
+ if (browser->top_idx + browser->rows > browser->nr_entries - 1)
break;
- offset = browser->height;
+ offset = browser->rows;
if (browser->index + offset > browser->nr_entries - 1)
offset = browser->nr_entries - 1 - browser->index;
browser->index += offset;
@@ -421,10 +478,10 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs)
if (browser->top_idx == 0)
break;
- if (browser->top_idx < browser->height)
+ if (browser->top_idx < browser->rows)
offset = browser->top_idx;
else
- offset = browser->height;
+ offset = browser->rows;
browser->index -= offset;
browser->top_idx -= offset;
@@ -434,7 +491,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs)
ui_browser__reset_index(browser);
break;
case K_END:
- offset = browser->height - 1;
+ offset = browser->rows - 1;
if (offset >= browser->nr_entries)
offset = browser->nr_entries - 1;
@@ -443,6 +500,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs)
browser->seek(browser, -offset, SEEK_END);
break;
default:
+ out:
return key;
}
}
@@ -464,7 +522,7 @@ unsigned int ui_browser__list_head_refresh(struct ui_browser *browser)
if (!browser->filter || !browser->filter(browser, pos)) {
ui_browser__gotorc(browser, row, 0);
browser->write(browser, pos, row);
- if (++row == browser->height)
+ if (++row == browser->rows)
break;
}
}
@@ -498,11 +556,11 @@ static struct ui_browser_colorset {
.colorset = HE_COLORSET_SELECTED,
.name = "selected",
.fg = "black",
- .bg = "lightgray",
+ .bg = "yellow",
},
{
- .colorset = HE_COLORSET_CODE,
- .name = "code",
+ .colorset = HE_COLORSET_JUMP_ARROWS,
+ .name = "jump_arrows",
.fg = "blue",
.bg = "default",
},
@@ -531,7 +589,7 @@ static int ui_browser__color_config(const char *var, const char *value,
int i;
/* same dir for all commands */
- if (prefixcmp(var, "colors.") != 0)
+ if (!strstarts(var, "colors.") != 0)
return 0;
for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) {
@@ -549,7 +607,7 @@ static int ui_browser__color_config(const char *var, const char *value,
break;
*bg = '\0';
- while (isspace(*++bg));
+ bg = skip_spaces(bg + 1);
ui_browser__colorsets[i].bg = bg;
ui_browser__colorsets[i].fg = fg;
return 0;
@@ -566,14 +624,16 @@ void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence)
browser->top = browser->entries;
break;
case SEEK_CUR:
- browser->top = browser->top + browser->top_idx + offset;
+ browser->top = (char **)browser->top + offset;
break;
case SEEK_END:
- browser->top = browser->top + browser->nr_entries + offset;
+ browser->top = (char **)browser->entries + browser->nr_entries - 1 + offset;
break;
default:
return;
}
+ assert((char **)browser->top < (char **)browser->entries + browser->nr_entries);
+ assert((char **)browser->top >= (char **)browser->entries);
}
unsigned int ui_browser__argv_refresh(struct ui_browser *browser)
@@ -585,11 +645,13 @@ unsigned int ui_browser__argv_refresh(struct ui_browser *browser)
browser->top = browser->entries;
pos = (char **)browser->top;
- while (idx < browser->nr_entries) {
+ while (idx < browser->nr_entries &&
+ row < (unsigned)SLtt_Screen_Rows - 1) {
+ assert(pos < (char **)browser->entries + browser->nr_entries);
if (!browser->filter || !browser->filter(browser, *pos)) {
ui_browser__gotorc(browser, row, 0);
browser->write(browser, pos, row);
- if (++row == browser->height)
+ if (++row == browser->rows)
break;
}
@@ -625,7 +687,7 @@ static void __ui_browser__line_arrow_up(struct ui_browser *browser,
SLsmg_set_char_set(1);
- if (start < browser->top_idx + browser->height) {
+ if (start < browser->top_idx + browser->rows) {
row = start - browser->top_idx;
ui_browser__gotorc(browser, row, column);
SLsmg_write_char(SLSMG_LLCORN_CHAR);
@@ -635,7 +697,7 @@ static void __ui_browser__line_arrow_up(struct ui_browser *browser,
if (row-- == 0)
goto out;
} else
- row = browser->height - 1;
+ row = browser->rows - 1;
if (end > browser->top_idx)
end_row = end - browser->top_idx;
@@ -672,21 +734,21 @@ static void __ui_browser__line_arrow_down(struct ui_browser *browser,
ui_browser__gotorc(browser, row, column + 1);
SLsmg_draw_hline(2);
- if (row++ == 0)
+ if (++row == 0)
goto out;
} else
row = 0;
- if (end >= browser->top_idx + browser->height)
- end_row = browser->height - 1;
+ if (end >= browser->top_idx + browser->rows)
+ end_row = browser->rows - 1;
else
- end_row = end - browser->top_idx;;
+ end_row = end - browser->top_idx;
ui_browser__gotorc(browser, row, column);
SLsmg_draw_vline(end_row - row + 1);
ui_browser__gotorc(browser, end_row, column);
- if (end < browser->top_idx + browser->height) {
+ if (end < browser->top_idx + browser->rows) {
SLsmg_write_char(SLSMG_LLCORN_CHAR);
ui_browser__gotorc(browser, end_row, column + 1);
SLsmg_write_char(SLSMG_HLINE_CHAR);
@@ -706,6 +768,50 @@ void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column,
__ui_browser__line_arrow_down(browser, column, start, end);
}
+void ui_browser__mark_fused(struct ui_browser *browser, unsigned int column,
+ unsigned int row, int diff, bool arrow_down)
+{
+ int end_row;
+
+ if (diff <= 0)
+ return;
+
+ SLsmg_set_char_set(1);
+
+ if (arrow_down) {
+ if (row + diff <= browser->top_idx)
+ return;
+
+ end_row = row + diff - browser->top_idx;
+ ui_browser__gotorc(browser, end_row, column - 1);
+ SLsmg_write_char(SLSMG_LTEE_CHAR);
+
+ while (--end_row >= 0 && end_row > (int)(row - browser->top_idx)) {
+ ui_browser__gotorc(browser, end_row, column - 1);
+ SLsmg_draw_vline(1);
+ }
+
+ end_row = (int)(row - browser->top_idx);
+ if (end_row >= 0) {
+ ui_browser__gotorc(browser, end_row, column - 1);
+ SLsmg_write_char(SLSMG_ULCORN_CHAR);
+ ui_browser__gotorc(browser, end_row, column);
+ SLsmg_draw_hline(2);
+ }
+ } else {
+ if (row < browser->top_idx)
+ return;
+
+ end_row = row - browser->top_idx;
+ ui_browser__gotorc(browser, end_row, column - 1);
+ SLsmg_write_char(SLSMG_LTEE_CHAR);
+ ui_browser__gotorc(browser, end_row, column);
+ SLsmg_draw_hline(2);
+ }
+
+ SLsmg_set_char_set(0);
+}
+
void ui_browser__init(void)
{
int i = 0;
@@ -714,8 +820,6 @@ void ui_browser__init(void)
while (ui_browser__colorsets[i].name) {
struct ui_browser_colorset *c = &ui_browser__colorsets[i++];
- sltt_set_color(c->colorset, c->name, c->fg, c->bg);
+ SLtt_set_color(c->colorset, c->name, c->fg, c->bg);
}
-
- annotate_browser__init();
}
diff --git a/tools/perf/ui/browser.h b/tools/perf/ui/browser.h
index 404ff66a3e36..9d4404f9b87f 100644
--- a/tools/perf/ui/browser.h
+++ b/tools/perf/ui/browser.h
@@ -1,52 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_BROWSER_H_
#define _PERF_UI_BROWSER_H_ 1
-#include <stdbool.h>
+#include <linux/types.h>
+#include <stdarg.h>
#include <sys/types.h>
-#include "../types.h"
#define HE_COLORSET_TOP 50
#define HE_COLORSET_MEDIUM 51
#define HE_COLORSET_NORMAL 52
#define HE_COLORSET_SELECTED 53
-#define HE_COLORSET_CODE 54
+#define HE_COLORSET_JUMP_ARROWS 54
#define HE_COLORSET_ADDR 55
#define HE_COLORSET_ROOT 56
struct ui_browser {
u64 index, top_idx;
void *top, *entries;
- u16 y, x, width, height;
+ u16 y, x, width, height, rows, columns, horiz_scroll;
+ u8 extra_title_lines;
int current_color;
void *priv;
- const char *title;
+ char *title;
char *helpline;
- unsigned int (*refresh)(struct ui_browser *self);
- void (*write)(struct ui_browser *self, void *entry, int row);
- void (*seek)(struct ui_browser *self, off_t offset, int whence);
- bool (*filter)(struct ui_browser *self, void *entry);
+ const char *no_samples_msg;
+ void (*refresh_dimensions)(struct ui_browser *browser);
+ unsigned int (*refresh)(struct ui_browser *browser);
+ void (*write)(struct ui_browser *browser, void *entry, int row);
+ void (*seek)(struct ui_browser *browser, off_t offset, int whence);
+ bool (*filter)(struct ui_browser *browser, void *entry);
u32 nr_entries;
bool navkeypressed;
bool use_navkeypressed;
};
int ui_browser__set_color(struct ui_browser *browser, int color);
-void ui_browser__set_percent_color(struct ui_browser *self,
+void ui_browser__set_percent_color(struct ui_browser *browser,
double percent, bool current);
-bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row);
-void ui_browser__refresh_dimensions(struct ui_browser *self);
-void ui_browser__reset_index(struct ui_browser *self);
+bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row);
+void ui_browser__refresh_dimensions(struct ui_browser *browser);
+void ui_browser__reset_index(struct ui_browser *browser);
-void ui_browser__gotorc(struct ui_browser *self, int y, int x);
+void ui_browser__gotorc_title(struct ui_browser *browser, int y, int x);
+void ui_browser__gotorc(struct ui_browser *browser, int y, int x);
+void ui_browser__write_nstring(struct ui_browser *browser, const char *msg,
+ unsigned int width);
+void ui_browser__vprintf(struct ui_browser *browser, const char *fmt, va_list args);
+void ui_browser__printf(struct ui_browser *browser, const char *fmt, ...);
void ui_browser__write_graph(struct ui_browser *browser, int graph);
void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column,
u64 start, u64 end);
+void ui_browser__mark_fused(struct ui_browser *browser, unsigned int column,
+ unsigned int row, int diff, bool arrow_down);
void __ui_browser__show_title(struct ui_browser *browser, const char *title);
void ui_browser__show_title(struct ui_browser *browser, const char *title);
-int ui_browser__show(struct ui_browser *self, const char *title,
+int ui_browser__show(struct ui_browser *browser, const char *title,
const char *helpline, ...);
-void ui_browser__hide(struct ui_browser *self);
-int ui_browser__refresh(struct ui_browser *self);
+void ui_browser__hide(struct ui_browser *browser);
+int ui_browser__refresh(struct ui_browser *browser);
int ui_browser__run(struct ui_browser *browser, int delay_secs);
void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries);
void ui_browser__handle_resize(struct ui_browser *browser);
@@ -55,20 +66,22 @@ void __ui_browser__vline(struct ui_browser *browser, unsigned int column,
int ui_browser__warning(struct ui_browser *browser, int timeout,
const char *format, ...);
+int ui_browser__warn_unhandled_hotkey(struct ui_browser *browser, int key, int timeout, const char *help);
int ui_browser__help_window(struct ui_browser *browser, const char *text);
bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text);
int ui_browser__input_window(const char *title, const char *text, char *input,
const char *exit_msg, int delay_sec);
+struct perf_session;
+int tui__header_window(struct perf_session *session);
void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence);
unsigned int ui_browser__argv_refresh(struct ui_browser *browser);
-void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence);
-unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self);
+void ui_browser__rb_tree_seek(struct ui_browser *browser, off_t offset, int whence);
+unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser);
-void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whence);
-unsigned int ui_browser__list_head_refresh(struct ui_browser *self);
+void ui_browser__list_head_seek(struct ui_browser *browser, off_t offset, int whence);
+unsigned int ui_browser__list_head_refresh(struct ui_browser *browser);
void ui_browser__init(void);
-void annotate_browser__init(void);
#endif /* _PERF_UI_BROWSER_H_ */
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;
}
diff --git a/tools/perf/ui/gtk/Build b/tools/perf/ui/gtk/Build
new file mode 100644
index 000000000000..eef708c502f4
--- /dev/null
+++ b/tools/perf/ui/gtk/Build
@@ -0,0 +1,14 @@
+CFLAGS_gtk += -fPIC $(GTK_CFLAGS) -Wno-deprecated-declarations
+
+gtk-y += browser.o
+gtk-y += hists.o
+gtk-y += setup.o
+gtk-y += util.o
+gtk-y += helpline.o
+gtk-y += progress.o
+gtk-y += annotate.o
+gtk-y += zalloc.o
+
+$(OUTPUT)ui/gtk/zalloc.o: ../lib/zalloc.c FORCE
+ $(call rule_mkdir)
+ $(call if_changed_dep,cc_o_c)
diff --git a/tools/perf/ui/gtk/annotate.c b/tools/perf/ui/gtk/annotate.c
index f538794615db..8920e298420a 100644
--- a/tools/perf/ui/gtk/annotate.c
+++ b/tools/perf/ui/gtk/annotate.c
@@ -1,9 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
#include "gtk.h"
+#include "util/sort.h"
#include "util/debug.h"
#include "util/annotate.h"
+#include "util/evlist.h"
#include "util/evsel.h"
+#include "util/map.h"
+#include "util/dso.h"
+#include "util/symbol.h"
#include "ui/helpline.h"
-
+#include <inttypes.h>
+#include <signal.h>
enum {
ANN_COL__PERCENT,
@@ -20,23 +27,31 @@ static const char *const col_names[] = {
};
static int perf_gtk__get_percent(char *buf, size_t size, struct symbol *sym,
- struct disasm_line *dl, int evidx)
+ struct disasm_line *dl, const struct evsel *evsel)
{
+ struct annotation *notes;
struct sym_hist *symhist;
+ struct sym_hist_entry *entry;
double percent = 0.0;
const char *markup;
int ret = 0;
+ u64 nr_samples = 0;
strcpy(buf, "");
- if (dl->offset == (s64) -1)
+ if (dl->al.offset == (s64) -1)
return 0;
- symhist = annotation__histogram(symbol__annotation(sym), evidx);
- if (!symbol_conf.event_group && !symhist->addr[dl->offset])
+ notes = symbol__annotation(sym);
+ symhist = annotation__histogram(notes, evsel);
+ entry = annotated_source__hist_entry(notes->src, evsel, dl->al.offset);
+ if (entry)
+ nr_samples = entry->nr_samples;
+
+ if (!symbol_conf.event_group && nr_samples == 0)
return 0;
- percent = 100.0 * symhist->addr[dl->offset] / symhist->sum;
+ percent = 100.0 * nr_samples / symhist->nr_samples;
markup = perf_gtk__get_percent_color(percent);
if (markup)
@@ -48,23 +63,23 @@ static int perf_gtk__get_percent(char *buf, size_t size, struct symbol *sym,
return ret;
}
-static int perf_gtk__get_offset(char *buf, size_t size, struct symbol *sym,
- struct map *map, struct disasm_line *dl)
+static int perf_gtk__get_offset(char *buf, size_t size, struct map_symbol *ms,
+ struct disasm_line *dl)
{
- u64 start = map__rip_2objdump(map, sym->start);
+ u64 start = map__rip_2objdump(ms->map, ms->sym->start);
strcpy(buf, "");
- if (dl->offset == (s64) -1)
+ if (dl->al.offset == (s64) -1)
return 0;
- return scnprintf(buf, size, "%"PRIx64, start + dl->offset);
+ return scnprintf(buf, size, "%"PRIx64, start + dl->al.offset);
}
static int perf_gtk__get_line(char *buf, size_t size, struct disasm_line *dl)
{
int ret = 0;
- char *line = g_markup_escape_text(dl->line, -1);
+ char *line = g_markup_escape_text(dl->al.line, -1);
const char *markup = "<span fgcolor='gray'>";
strcpy(buf, "");
@@ -72,7 +87,7 @@ static int perf_gtk__get_line(char *buf, size_t size, struct disasm_line *dl)
if (!line)
return 0;
- if (dl->offset != (s64) -1)
+ if (dl->al.offset != (s64) -1)
markup = NULL;
if (markup)
@@ -85,10 +100,11 @@ static int perf_gtk__get_line(char *buf, size_t size, struct disasm_line *dl)
return ret;
}
-static int perf_gtk__annotate_symbol(GtkWidget *window, struct symbol *sym,
- struct map *map, struct perf_evsel *evsel,
+static int perf_gtk__annotate_symbol(GtkWidget *window, struct map_symbol *ms,
+ struct evsel *evsel,
struct hist_browser_timer *hbt __maybe_unused)
{
+ struct symbol *sym = ms->sym;
struct disasm_line *pos, *n;
struct annotation *notes;
GType col_types[MAX_ANN_COLS];
@@ -117,28 +133,29 @@ static int perf_gtk__annotate_symbol(GtkWidget *window, struct symbol *sym,
gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
g_object_unref(GTK_TREE_MODEL(store));
- list_for_each_entry(pos, &notes->src->source, node) {
+ list_for_each_entry(pos, &notes->src->source, al.node) {
GtkTreeIter iter;
int ret = 0;
gtk_list_store_append(store, &iter);
- if (perf_evsel__is_group_event(evsel)) {
- for (i = 0; i < evsel->nr_members; i++) {
+ if (evsel__is_group_event(evsel)) {
+ struct evsel *cur_evsel;
+
+ for_each_group_evsel(cur_evsel, evsel__leader(evsel)) {
ret += perf_gtk__get_percent(s + ret,
sizeof(s) - ret,
sym, pos,
- evsel->idx + i);
+ cur_evsel);
ret += scnprintf(s + ret, sizeof(s) - ret, " ");
}
} else {
- ret = perf_gtk__get_percent(s, sizeof(s), sym, pos,
- evsel->idx);
+ ret = perf_gtk__get_percent(s, sizeof(s), sym, pos, evsel);
}
if (ret)
gtk_list_store_set(store, &iter, ANN_COL__PERCENT, s, -1);
- if (perf_gtk__get_offset(s, sizeof(s), sym, map, pos))
+ if (perf_gtk__get_offset(s, sizeof(s), ms, pos))
gtk_list_store_set(store, &iter, ANN_COL__OFFSET, s, -1);
if (perf_gtk__get_line(s, sizeof(s), pos))
gtk_list_store_set(store, &iter, ANN_COL__LINE, s, -1);
@@ -146,31 +163,40 @@ static int perf_gtk__annotate_symbol(GtkWidget *window, struct symbol *sym,
gtk_container_add(GTK_CONTAINER(window), view);
- list_for_each_entry_safe(pos, n, &notes->src->source, node) {
- list_del(&pos->node);
+ list_for_each_entry_safe(pos, n, &notes->src->source, al.node) {
+ list_del_init(&pos->al.node);
disasm_line__free(pos);
}
return 0;
}
-int symbol__gtk_annotate(struct symbol *sym, struct map *map,
- struct perf_evsel *evsel,
- struct hist_browser_timer *hbt)
+static int symbol__gtk_annotate(struct map_symbol *ms, struct evsel *evsel,
+ struct hist_browser_timer *hbt)
{
+ struct dso *dso = map__dso(ms->map);
+ struct symbol *sym = ms->sym;
GtkWidget *window;
GtkWidget *notebook;
GtkWidget *scrolled_window;
GtkWidget *tab_label;
+ int err;
- if (map->dso->annotate_warned)
+ if (dso__annotate_warned(dso))
return -1;
- if (symbol__annotate(sym, map, 0) < 0) {
- ui__error("%s", ui_helpline__current);
+ err = symbol__annotate(ms, evsel, NULL);
+ if (err) {
+ char msg[BUFSIZ];
+
+ dso__set_annotate_warned(dso);
+ symbol__strerror_disassemble(ms, err, msg, sizeof(msg));
+ ui__error("Couldn't annotate %s: %s\n", sym->name, msg);
return -1;
}
+ symbol__calc_percent(sym, evsel);
+
if (perf_gtk__is_active_context(pgctx)) {
window = pgctx->main_window;
notebook = pgctx->notebook;
@@ -222,10 +248,17 @@ int symbol__gtk_annotate(struct symbol *sym, struct map *map,
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window,
tab_label);
- perf_gtk__annotate_symbol(scrolled_window, sym, map, evsel, hbt);
+ perf_gtk__annotate_symbol(scrolled_window, ms, evsel, hbt);
return 0;
}
+int hist_entry__gtk_annotate(struct hist_entry *he,
+ struct evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ return symbol__gtk_annotate(&he->ms, evsel, hbt);
+}
+
void perf_gtk__show_annotations(void)
{
GtkWidget *window;
diff --git a/tools/perf/ui/gtk/browser.c b/tools/perf/ui/gtk/browser.c
index c95012cdb438..d2dadf3873fb 100644
--- a/tools/perf/ui/gtk/browser.c
+++ b/tools/perf/ui/gtk/browser.c
@@ -1,10 +1,9 @@
-#include "../evlist.h"
-#include "../cache.h"
+// SPDX-License-Identifier: GPL-2.0
+#include "gtk.h"
#include "../evsel.h"
#include "../sort.h"
#include "../hist.h"
#include "../helpline.h"
-#include "gtk.h"
#include <signal.h>
@@ -43,7 +42,7 @@ const char *perf_gtk__get_percent_color(double percent)
return NULL;
}
-#ifdef HAVE_GTK_INFO_BAR
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
GtkWidget *perf_gtk__setup_info_bar(void)
{
GtkWidget *info_bar;
diff --git a/tools/perf/ui/gtk/gtk.h b/tools/perf/ui/gtk/gtk.h
index 3d96785ef155..a2b497f03fd6 100644
--- a/tools/perf/ui/gtk/gtk.h
+++ b/tools/perf/ui/gtk/gtk.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_GTK_H_
#define _PERF_GTK_H_ 1
@@ -12,7 +13,7 @@ struct perf_gtk_context {
GtkWidget *main_window;
GtkWidget *notebook;
-#ifdef HAVE_GTK_INFO_BAR
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
GtkWidget *info_bar;
GtkWidget *message_label;
#endif
@@ -20,6 +21,9 @@ struct perf_gtk_context {
guint statbar_ctx_id;
};
+int perf_gtk__init(void);
+void perf_gtk__exit(bool wait_for_ok);
+
extern struct perf_gtk_context *pgctx;
static inline bool perf_gtk__is_active_context(struct perf_gtk_context *ctx)
@@ -31,7 +35,7 @@ struct perf_gtk_context *perf_gtk__activate_context(GtkWidget *window);
int perf_gtk__deactivate_context(struct perf_gtk_context **ctx);
void perf_gtk__init_helpline(void);
-void perf_gtk__init_progress(void);
+void gtk_ui_progress__init(void);
void perf_gtk__init_hpp(void);
void perf_gtk__signal(int sig);
@@ -39,7 +43,7 @@ void perf_gtk__resize_window(GtkWidget *window);
const char *perf_gtk__get_percent_color(double percent);
GtkWidget *perf_gtk__setup_statusbar(void);
-#ifdef HAVE_GTK_INFO_BAR
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
GtkWidget *perf_gtk__setup_info_bar(void);
#else
static inline GtkWidget *perf_gtk__setup_info_bar(void)
@@ -48,4 +52,16 @@ static inline GtkWidget *perf_gtk__setup_info_bar(void)
}
#endif
+struct evsel;
+struct evlist;
+struct hist_entry;
+struct hist_browser_timer;
+
+int evlist__gtk_browse_hists(struct evlist *evlist, const char *help,
+ struct hist_browser_timer *hbt, float min_pcnt);
+int hist_entry__gtk_annotate(struct hist_entry *he,
+ struct evsel *evsel,
+ struct hist_browser_timer *hbt);
+void perf_gtk__show_annotations(void);
+
#endif /* _PERF_GTK_H_ */
diff --git a/tools/perf/ui/gtk/helpline.c b/tools/perf/ui/gtk/helpline.c
index 3388cbd12186..8ea95898a5bd 100644
--- a/tools/perf/ui/gtk/helpline.c
+++ b/tools/perf/ui/gtk/helpline.c
@@ -1,10 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "gtk.h"
#include <stdio.h>
#include <string.h>
+#include <linux/kernel.h>
-#include "gtk.h"
#include "../ui.h"
#include "../helpline.h"
-#include "../../util/debug.h"
static void gtk_helpline_pop(void)
{
diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c
index 9708dd5fb8f3..bae21f336ae6 100644
--- a/tools/perf/ui/gtk/hists.c
+++ b/tools/perf/ui/gtk/hists.c
@@ -1,23 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "gtk.h"
#include "../evlist.h"
-#include "../cache.h"
+#include "../callchain.h"
#include "../evsel.h"
#include "../sort.h"
#include "../hist.h"
#include "../helpline.h"
-#include "gtk.h"
+#include "../string2.h"
+#include <signal.h>
+#include <stdlib.h>
+#include <linux/string.h>
#define MAX_COLUMNS 32
-static int __percent_color_snprintf(char *buf, size_t size, double percent)
+static int __percent_color_snprintf(struct perf_hpp *hpp, const char *fmt, ...)
{
int ret = 0;
+ int len;
+ va_list args;
+ double percent;
const char *markup;
+ char *buf = hpp->buf;
+ size_t size = hpp->size;
+
+ va_start(args, fmt);
+ len = va_arg(args, int);
+ percent = va_arg(args, double);
+ va_end(args);
markup = perf_gtk__get_percent_color(percent);
if (markup)
ret += scnprintf(buf, size, markup);
- ret += scnprintf(buf + ret, size - ret, " %6.2f%%", percent);
+ ret += scnprintf(buf + ret, size - ret, fmt, len, percent);
if (markup)
ret += scnprintf(buf + ret, size - ret, "</span>");
@@ -25,76 +40,32 @@ static int __percent_color_snprintf(char *buf, size_t size, double percent)
return ret;
}
-
-static int __hpp__color_fmt(struct perf_hpp *hpp, struct hist_entry *he,
- u64 (*get_field)(struct hist_entry *))
-{
- int ret;
- double percent = 0.0;
- struct hists *hists = he->hists;
- struct perf_evsel *evsel = hists_to_evsel(hists);
-
- if (hists->stats.total_period)
- percent = 100.0 * get_field(he) / hists->stats.total_period;
-
- ret = __percent_color_snprintf(hpp->buf, hpp->size, percent);
-
- if (perf_evsel__is_group_event(evsel)) {
- int prev_idx, idx_delta;
- struct hist_entry *pair;
- int nr_members = evsel->nr_members;
-
- prev_idx = perf_evsel__group_idx(evsel);
-
- list_for_each_entry(pair, &he->pairs.head, pairs.node) {
- u64 period = get_field(pair);
- u64 total = pair->hists->stats.total_period;
-
- evsel = hists_to_evsel(pair->hists);
- idx_delta = perf_evsel__group_idx(evsel) - prev_idx - 1;
-
- while (idx_delta--) {
- /*
- * zero-fill group members in the middle which
- * have no sample
- */
- ret += __percent_color_snprintf(hpp->buf + ret,
- hpp->size - ret,
- 0.0);
- }
-
- percent = 100.0 * period / total;
- ret += __percent_color_snprintf(hpp->buf + ret,
- hpp->size - ret,
- percent);
-
- prev_idx = perf_evsel__group_idx(evsel);
- }
-
- idx_delta = nr_members - prev_idx - 1;
-
- while (idx_delta--) {
- /*
- * zero-fill group members at last which have no sample
- */
- ret += __percent_color_snprintf(hpp->buf + ret,
- hpp->size - ret,
- 0.0);
- }
- }
- return ret;
-}
-
#define __HPP_COLOR_PERCENT_FN(_type, _field) \
static u64 he_get_##_field(struct hist_entry *he) \
{ \
return he->stat._field; \
} \
\
-static int perf_gtk__hpp_color_##_type(struct perf_hpp *hpp, \
+static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
+{ \
+ return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \
+ __percent_color_snprintf, true); \
+}
+
+#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
+static u64 he_get_acc_##_field(struct hist_entry *he) \
+{ \
+ return he->stat_acc->_field; \
+} \
+ \
+static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, \
struct hist_entry *he) \
{ \
- return __hpp__color_fmt(hpp, he, he_get_##_field); \
+ return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \
+ __percent_color_snprintf, true); \
}
__HPP_COLOR_PERCENT_FN(overhead, period)
@@ -102,16 +73,13 @@ __HPP_COLOR_PERCENT_FN(overhead_sys, period_sys)
__HPP_COLOR_PERCENT_FN(overhead_us, period_us)
__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys)
__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
+__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period)
#undef __HPP_COLOR_PERCENT_FN
void perf_gtk__init_hpp(void)
{
- perf_hpp__column_enable(PERF_HPP__OVERHEAD);
-
- perf_hpp__init();
-
perf_hpp__format[PERF_HPP__OVERHEAD].color =
perf_gtk__hpp_color_overhead;
perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
@@ -122,6 +90,204 @@ void perf_gtk__init_hpp(void)
perf_gtk__hpp_color_overhead_guest_sys;
perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
perf_gtk__hpp_color_overhead_guest_us;
+ perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
+ perf_gtk__hpp_color_overhead_acc;
+}
+
+static void perf_gtk__add_callchain_flat(struct rb_root *root, GtkTreeStore *store,
+ GtkTreeIter *parent, int col, u64 total)
+{
+ struct rb_node *nd;
+ bool has_single_node = (rb_first(root) == rb_last(root));
+
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ struct callchain_node *node;
+ struct callchain_list *chain;
+ GtkTreeIter iter, new_parent;
+ bool need_new_parent;
+
+ node = rb_entry(nd, struct callchain_node, rb_node);
+
+ new_parent = *parent;
+ need_new_parent = !has_single_node;
+
+ callchain_node__make_parent_list(node);
+
+ list_for_each_entry(chain, &node->parent_val, list) {
+ char buf[128];
+
+ gtk_tree_store_append(store, &iter, &new_parent);
+
+ callchain_node__scnprintf_value(node, buf, sizeof(buf), total);
+ gtk_tree_store_set(store, &iter, 0, buf, -1);
+
+ callchain_list__sym_name(chain, buf, sizeof(buf), false);
+ gtk_tree_store_set(store, &iter, col, buf, -1);
+
+ if (need_new_parent) {
+ /*
+ * Only show the top-most symbol in a callchain
+ * if it's not the only callchain.
+ */
+ new_parent = iter;
+ need_new_parent = false;
+ }
+ }
+
+ list_for_each_entry(chain, &node->val, list) {
+ char buf[128];
+
+ gtk_tree_store_append(store, &iter, &new_parent);
+
+ callchain_node__scnprintf_value(node, buf, sizeof(buf), total);
+ gtk_tree_store_set(store, &iter, 0, buf, -1);
+
+ callchain_list__sym_name(chain, buf, sizeof(buf), false);
+ gtk_tree_store_set(store, &iter, col, buf, -1);
+
+ if (need_new_parent) {
+ /*
+ * Only show the top-most symbol in a callchain
+ * if it's not the only callchain.
+ */
+ new_parent = iter;
+ need_new_parent = false;
+ }
+ }
+ }
+}
+
+static void perf_gtk__add_callchain_folded(struct rb_root *root, GtkTreeStore *store,
+ GtkTreeIter *parent, int col, u64 total)
+{
+ struct rb_node *nd;
+
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ struct callchain_node *node;
+ struct callchain_list *chain;
+ GtkTreeIter iter;
+ char buf[64];
+ char *str, *str_alloc = NULL;
+ bool first = true;
+
+ node = rb_entry(nd, struct callchain_node, rb_node);
+
+ callchain_node__make_parent_list(node);
+
+ list_for_each_entry(chain, &node->parent_val, list) {
+ char name[1024];
+
+ callchain_list__sym_name(chain, name, sizeof(name), false);
+
+ if (asprintf(&str, "%s%s%s",
+ first ? "" : str_alloc,
+ first ? "" : symbol_conf.field_sep ?: "; ",
+ name) < 0)
+ return;
+
+ first = false;
+ free(str_alloc);
+ str_alloc = str;
+ }
+
+ list_for_each_entry(chain, &node->val, list) {
+ char name[1024];
+
+ callchain_list__sym_name(chain, name, sizeof(name), false);
+
+ if (asprintf(&str, "%s%s%s",
+ first ? "" : str_alloc,
+ first ? "" : symbol_conf.field_sep ?: "; ",
+ name) < 0)
+ return;
+
+ first = false;
+ free(str_alloc);
+ str_alloc = str;
+ }
+
+ gtk_tree_store_append(store, &iter, parent);
+
+ callchain_node__scnprintf_value(node, buf, sizeof(buf), total);
+ gtk_tree_store_set(store, &iter, 0, buf, -1);
+
+ gtk_tree_store_set(store, &iter, col, str, -1);
+
+ free(str_alloc);
+ }
+}
+
+static void perf_gtk__add_callchain_graph(struct rb_root *root, GtkTreeStore *store,
+ GtkTreeIter *parent, int col, u64 total)
+{
+ struct rb_node *nd;
+ bool has_single_node = (rb_first(root) == rb_last(root));
+
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ struct callchain_node *node;
+ struct callchain_list *chain;
+ GtkTreeIter iter, new_parent;
+ bool need_new_parent;
+ u64 child_total;
+
+ node = rb_entry(nd, struct callchain_node, rb_node);
+
+ new_parent = *parent;
+ need_new_parent = !has_single_node && (node->val_nr > 1);
+
+ list_for_each_entry(chain, &node->val, list) {
+ char buf[128];
+
+ gtk_tree_store_append(store, &iter, &new_parent);
+
+ callchain_node__scnprintf_value(node, buf, sizeof(buf), total);
+ gtk_tree_store_set(store, &iter, 0, buf, -1);
+
+ callchain_list__sym_name(chain, buf, sizeof(buf), false);
+ gtk_tree_store_set(store, &iter, col, buf, -1);
+
+ if (need_new_parent) {
+ /*
+ * Only show the top-most symbol in a callchain
+ * if it's not the only callchain.
+ */
+ new_parent = iter;
+ need_new_parent = false;
+ }
+ }
+
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ child_total = node->children_hit;
+ else
+ child_total = total;
+
+ /* Now 'iter' contains info of the last callchain_list */
+ perf_gtk__add_callchain_graph(&node->rb_root, store, &iter, col,
+ child_total);
+ }
+}
+
+static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store,
+ GtkTreeIter *parent, int col, u64 total)
+{
+ if (callchain_param.mode == CHAIN_FLAT)
+ perf_gtk__add_callchain_flat(root, store, parent, col, total);
+ else if (callchain_param.mode == CHAIN_FOLDED)
+ perf_gtk__add_callchain_folded(root, store, parent, col, total);
+ else
+ perf_gtk__add_callchain_graph(root, store, parent, col, total);
+}
+
+static void on_row_activated(GtkTreeView *view, GtkTreePath *path,
+ GtkTreeViewColumn *col __maybe_unused,
+ gpointer user_data __maybe_unused)
+{
+ bool expanded = gtk_tree_view_row_expanded(view, path);
+
+ if (expanded)
+ gtk_tree_view_collapse_row(view, path);
+ else
+ gtk_tree_view_expand_row(view, path, FALSE);
}
static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
@@ -130,33 +296,25 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
struct perf_hpp_fmt *fmt;
GType col_types[MAX_COLUMNS];
GtkCellRenderer *renderer;
- struct sort_entry *se;
- GtkListStore *store;
+ GtkTreeStore *store;
struct rb_node *nd;
GtkWidget *view;
int col_idx;
+ int sym_col = -1;
int nr_cols;
char s[512];
struct perf_hpp hpp = {
.buf = s,
.size = sizeof(s),
- .ptr = hists_to_evsel(hists),
};
nr_cols = 0;
- perf_hpp__for_each_format(fmt)
+ hists__for_each_format(hists, fmt)
col_types[nr_cols++] = G_TYPE_STRING;
- list_for_each_entry(se, &hist_entry__sort_list, list) {
- if (se->elide)
- continue;
-
- col_types[nr_cols++] = G_TYPE_STRING;
- }
-
- store = gtk_list_store_newv(nr_cols, col_types);
+ store = gtk_tree_store_newv(nr_cols, col_types);
view = gtk_tree_view_new();
@@ -164,74 +322,278 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
col_idx = 0;
- perf_hpp__for_each_format(fmt) {
- fmt->header(&hpp);
+ hists__for_each_format(hists, fmt) {
+ if (perf_hpp__should_skip(fmt, hists))
+ continue;
+
+ /*
+ * XXX no way to determine where symcol column is..
+ * Just use last column for now.
+ */
+ if (perf_hpp__is_sort_entry(fmt))
+ sym_col = col_idx;
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
- -1, ltrim(s),
+ -1, fmt->name,
renderer, "markup",
col_idx++, NULL);
}
- list_for_each_entry(se, &hist_entry__sort_list, list) {
- if (se->elide)
- continue;
+ for (col_idx = 0; col_idx < nr_cols; col_idx++) {
+ GtkTreeViewColumn *column;
- gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
- -1, se->se_header,
- renderer, "text",
- col_idx++, NULL);
+ column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+
+ if (col_idx == sym_col) {
+ gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view),
+ column);
+ }
}
gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
g_object_unref(GTK_TREE_MODEL(store));
- for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ for (nd = rb_first_cached(&hists->entries); nd; nd = rb_next(nd)) {
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
GtkTreeIter iter;
- float percent = h->stat.period * 100.0 /
- hists->stats.total_period;
+ u64 total = hists__total_period(h->hists);
+ float percent;
if (h->filtered)
continue;
+ percent = hist_entry__get_percent_limit(h);
+ if (percent < min_pcnt)
+ continue;
+
+ gtk_tree_store_append(store, &iter, NULL);
+
+ col_idx = 0;
+
+ hists__for_each_format(hists, fmt) {
+ if (perf_hpp__should_skip(fmt, h->hists))
+ continue;
+
+ if (fmt->color)
+ fmt->color(fmt, &hpp, h);
+ else
+ fmt->entry(fmt, &hpp, h);
+
+ gtk_tree_store_set(store, &iter, col_idx++, s, -1);
+ }
+
+ if (hist_entry__has_callchains(h) &&
+ symbol_conf.use_callchain && hists__has(hists, sym)) {
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ total = symbol_conf.cumulate_callchain ?
+ h->stat_acc->period : h->stat.period;
+
+ perf_gtk__add_callchain(&h->sorted_chain, store, &iter,
+ sym_col, total);
+ }
+ }
+
+ gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
+
+ g_signal_connect(view, "row-activated",
+ G_CALLBACK(on_row_activated), NULL);
+ gtk_container_add(GTK_CONTAINER(window), view);
+}
+
+static void perf_gtk__add_hierarchy_entries(struct hists *hists,
+ struct rb_root_cached *root,
+ GtkTreeStore *store,
+ GtkTreeIter *parent,
+ struct perf_hpp *hpp,
+ float min_pcnt)
+{
+ int col_idx = 0;
+ struct rb_node *node;
+ struct hist_entry *he;
+ struct perf_hpp_fmt *fmt;
+ struct perf_hpp_list_node *fmt_node;
+ u64 total = hists__total_period(hists);
+ int size;
+
+ for (node = rb_first_cached(root); node; node = rb_next(node)) {
+ GtkTreeIter iter;
+ float percent;
+ char *bf;
+
+ he = rb_entry(node, struct hist_entry, rb_node);
+ if (he->filtered)
+ continue;
+
+ percent = hist_entry__get_percent_limit(he);
if (percent < min_pcnt)
continue;
- gtk_list_store_append(store, &iter);
+ gtk_tree_store_append(store, &iter, parent);
col_idx = 0;
- perf_hpp__for_each_format(fmt) {
+ /* 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 (fmt->color)
+ fmt->color(fmt, hpp, he);
+ else
+ fmt->entry(fmt, hpp, he);
+
+ gtk_tree_store_set(store, &iter, col_idx++, hpp->buf, -1);
+ }
+
+ bf = hpp->buf;
+ size = hpp->size;
+ perf_hpp_list__for_each_format(he->hpp_list, fmt) {
+ int ret;
+
if (fmt->color)
- fmt->color(&hpp, h);
+ ret = fmt->color(fmt, hpp, he);
else
- fmt->entry(&hpp, h);
+ ret = fmt->entry(fmt, hpp, he);
- gtk_list_store_set(store, &iter, col_idx++, s, -1);
+ snprintf(hpp->buf + ret, hpp->size - ret, " ");
+ advance_hpp(hpp, ret + 2);
}
- list_for_each_entry(se, &hist_entry__sort_list, list) {
- if (se->elide)
+ gtk_tree_store_set(store, &iter, col_idx, strim(bf), -1);
+
+ if (!he->leaf) {
+ hpp->buf = bf;
+ hpp->size = size;
+
+ perf_gtk__add_hierarchy_entries(hists, &he->hroot_out,
+ store, &iter, hpp,
+ min_pcnt);
+
+ if (!hist_entry__has_hierarchy_children(he, min_pcnt)) {
+ char buf[32];
+ GtkTreeIter child;
+
+ snprintf(buf, sizeof(buf), "no entry >= %.2f%%",
+ min_pcnt);
+
+ gtk_tree_store_append(store, &child, &iter);
+ gtk_tree_store_set(store, &child, col_idx, buf, -1);
+ }
+ }
+
+ if (he->leaf && hist_entry__has_callchains(he) && symbol_conf.use_callchain) {
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ total = symbol_conf.cumulate_callchain ?
+ he->stat_acc->period : he->stat.period;
+
+ perf_gtk__add_callchain(&he->sorted_chain, store, &iter,
+ col_idx, total);
+ }
+ }
+
+}
+
+static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists,
+ float min_pcnt)
+{
+ struct perf_hpp_fmt *fmt;
+ struct perf_hpp_list_node *fmt_node;
+ GType col_types[MAX_COLUMNS];
+ GtkCellRenderer *renderer;
+ GtkTreeStore *store;
+ GtkWidget *view;
+ int col_idx;
+ int nr_cols = 0;
+ char s[512];
+ char buf[512];
+ bool first_node, first_col;
+ struct perf_hpp hpp = {
+ .buf = s,
+ .size = sizeof(s),
+ };
+
+ hists__for_each_format(hists, fmt) {
+ if (perf_hpp__is_sort_entry(fmt) ||
+ perf_hpp__is_dynamic_entry(fmt))
+ break;
+
+ col_types[nr_cols++] = G_TYPE_STRING;
+ }
+ col_types[nr_cols++] = G_TYPE_STRING;
+
+ store = gtk_tree_store_newv(nr_cols, col_types);
+ view = gtk_tree_view_new();
+ renderer = gtk_cell_renderer_text_new();
+
+ col_idx = 0;
+
+ /* 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) {
+ gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
+ -1, fmt->name,
+ renderer, "markup",
+ col_idx++, NULL);
+ }
+
+ /* construct merged column header since sort keys share single column */
+ buf[0] = '\0';
+ first_node = true;
+ list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
+ if (!first_node)
+ strcat(buf, " / ");
+ first_node = false;
+
+ first_col = true;
+ perf_hpp_list__for_each_format(&fmt_node->hpp ,fmt) {
+ if (perf_hpp__should_skip(fmt, hists))
continue;
- se->se_snprintf(h, s, ARRAY_SIZE(s),
- hists__col_len(hists, se->se_width_idx));
+ if (!first_col)
+ strcat(buf, "+");
+ first_col = false;
+
+ fmt->header(fmt, &hpp, hists, 0, NULL);
+ strcat(buf, strim(hpp.buf));
+ }
+ }
+
+ gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
+ -1, buf,
+ renderer, "markup",
+ col_idx++, NULL);
+
+ for (col_idx = 0; col_idx < nr_cols; col_idx++) {
+ GtkTreeViewColumn *column;
- gtk_list_store_set(store, &iter, col_idx++, s, -1);
+ column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+
+ if (col_idx == 0) {
+ gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view),
+ column);
}
}
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
+ g_object_unref(GTK_TREE_MODEL(store));
+
+ perf_gtk__add_hierarchy_entries(hists, &hists->entries, store,
+ NULL, &hpp, min_pcnt);
+
+ gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
+
+ g_signal_connect(view, "row-activated",
+ G_CALLBACK(on_row_activated), NULL);
gtk_container_add(GTK_CONTAINER(window), view);
}
-int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist,
- const char *help,
- struct hist_browser_timer *hbt __maybe_unused,
- float min_pcnt)
+int evlist__gtk_browse_hists(struct evlist *evlist, const char *help,
+ struct hist_browser_timer *hbt __maybe_unused, float min_pcnt)
{
- struct perf_evsel *pos;
+ struct evsel *pos;
GtkWidget *vbox;
GtkWidget *notebook;
GtkWidget *info_bar;
@@ -269,20 +631,20 @@ int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist,
gtk_container_add(GTK_CONTAINER(window), vbox);
- list_for_each_entry(pos, &evlist->entries, node) {
- struct hists *hists = &pos->hists;
- const char *evname = perf_evsel__name(pos);
+ evlist__for_each_entry(evlist, pos) {
+ struct hists *hists = evsel__hists(pos);
+ const char *evname = evsel__name(pos);
GtkWidget *scrolled_window;
GtkWidget *tab_label;
char buf[512];
size_t size = sizeof(buf);
if (symbol_conf.event_group) {
- if (!perf_evsel__is_group_leader(pos))
+ if (!evsel__is_group_leader(pos))
continue;
- if (pos->nr_members > 1) {
- perf_evsel__group_desc(pos, buf, size);
+ if (pos->core.nr_members > 1) {
+ evsel__group_desc(pos, buf, size);
evname = buf;
}
}
@@ -293,7 +655,10 @@ int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist,
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
- perf_gtk__show_hists(scrolled_window, hists, min_pcnt);
+ if (symbol_conf.report_hierarchy)
+ perf_gtk__show_hierarchy(scrolled_window, hists, min_pcnt);
+ else
+ perf_gtk__show_hists(scrolled_window, hists, min_pcnt);
tab_label = gtk_label_new(evname);
diff --git a/tools/perf/ui/gtk/progress.c b/tools/perf/ui/gtk/progress.c
index 482bcf3df9b7..eea6fcde518a 100644
--- a/tools/perf/ui/gtk/progress.c
+++ b/tools/perf/ui/gtk/progress.c
@@ -1,20 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
#include <inttypes.h>
#include "gtk.h"
#include "../progress.h"
-#include "util.h"
static GtkWidget *dialog;
static GtkWidget *progress;
-static void gtk_progress_update(u64 curr, u64 total, const char *title)
+static void gtk_ui_progress__update(struct ui_progress *p)
{
- double fraction = total ? 1.0 * curr / total : 0.0;
+ double fraction = p->total ? 1.0 * p->curr / p->total : 0.0;
char buf[1024];
if (dialog == NULL) {
GtkWidget *vbox = gtk_vbox_new(TRUE, 5);
- GtkWidget *label = gtk_label_new(title);
+ GtkWidget *label = gtk_label_new(p->title);
dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
progress = gtk_progress_bar_new();
@@ -32,7 +32,7 @@ static void gtk_progress_update(u64 curr, u64 total, const char *title)
}
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
- snprintf(buf, sizeof(buf), "%"PRIu64" / %"PRIu64, curr, total);
+ snprintf(buf, sizeof(buf), "%"PRIu64" / %"PRIu64, p->curr, p->total);
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress), buf);
/* we didn't call gtk_main yet, so do it manually */
@@ -40,7 +40,7 @@ static void gtk_progress_update(u64 curr, u64 total, const char *title)
gtk_main_iteration();
}
-static void gtk_progress_finish(void)
+static void gtk_ui_progress__finish(void)
{
/* this will also destroy all of its children */
gtk_widget_destroy(dialog);
@@ -48,12 +48,12 @@ static void gtk_progress_finish(void)
dialog = NULL;
}
-static struct ui_progress gtk_progress_fns = {
- .update = gtk_progress_update,
- .finish = gtk_progress_finish,
+static struct ui_progress_ops gtk_ui_progress__ops = {
+ .update = gtk_ui_progress__update,
+ .finish = gtk_ui_progress__finish,
};
-void perf_gtk__init_progress(void)
+void gtk_ui_progress__init(void)
{
- progress_fns = &gtk_progress_fns;
+ ui_progress__ops = &gtk_ui_progress__ops;
}
diff --git a/tools/perf/ui/gtk/setup.c b/tools/perf/ui/gtk/setup.c
index 6c2dd2e423f3..f5eee4d66873 100644
--- a/tools/perf/ui/gtk/setup.c
+++ b/tools/perf/ui/gtk/setup.c
@@ -1,6 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
#include "gtk.h"
-#include "../../util/cache.h"
-#include "../../util/debug.h"
+#include <linux/compiler.h>
+#include "../util.h"
extern struct perf_error_ops perf_gtk_eops;
@@ -8,7 +9,7 @@ int perf_gtk__init(void)
{
perf_error__register(&perf_gtk_eops);
perf_gtk__init_helpline();
- perf_gtk__init_progress();
+ gtk_ui_progress__init();
perf_gtk__init_hpp();
return gtk_init_check(NULL, NULL) ? 0 : -1;
diff --git a/tools/perf/ui/gtk/util.c b/tools/perf/ui/gtk/util.c
index c06942a41c78..c47f5c387838 100644
--- a/tools/perf/ui/gtk/util.c
+++ b/tools/perf/ui/gtk/util.c
@@ -1,9 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
#include "../util.h"
-#include "../../util/debug.h"
#include "gtk.h"
+#include <stdlib.h>
#include <string.h>
-
+#include <linux/zalloc.h>
struct perf_gtk_context *pgctx;
@@ -23,8 +24,7 @@ int perf_gtk__deactivate_context(struct perf_gtk_context **ctx)
if (!perf_gtk__is_active_context(*ctx))
return -1;
- free(*ctx);
- *ctx = NULL;
+ zfree(ctx);
return 0;
}
@@ -53,7 +53,7 @@ static int perf_gtk__error(const char *format, va_list args)
return 0;
}
-#ifdef HAVE_GTK_INFO_BAR
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
static int perf_gtk__warning_info_bar(const char *format, va_list args)
{
char *msg;
@@ -105,7 +105,7 @@ static int perf_gtk__warning_statusbar(const char *format, va_list args)
struct perf_error_ops perf_gtk_eops = {
.error = perf_gtk__error,
-#ifdef HAVE_GTK_INFO_BAR
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
.warning = perf_gtk__warning_info_bar,
#else
.warning = perf_gtk__warning_statusbar,
diff --git a/tools/perf/ui/helpline.c b/tools/perf/ui/helpline.c
index 700fb3cfa1c7..911182b3f5e6 100644
--- a/tools/perf/ui/helpline.c
+++ b/tools/perf/ui/helpline.c
@@ -1,8 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "../debug.h"
#include "helpline.h"
#include "ui.h"
@@ -71,3 +71,13 @@ int ui_helpline__vshow(const char *fmt, va_list ap)
{
return helpline_fns->show(fmt, ap);
}
+
+void ui_helpline__printf(const char *fmt, ...)
+{
+ va_list ap;
+
+ ui_helpline__pop();
+ va_start(ap, fmt);
+ ui_helpline__vpush(fmt, ap);
+ va_end(ap);
+}
diff --git a/tools/perf/ui/helpline.h b/tools/perf/ui/helpline.h
index 46181f4fc07e..2165a098dee8 100644
--- a/tools/perf/ui/helpline.h
+++ b/tools/perf/ui/helpline.h
@@ -1,11 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_HELPLINE_H_
#define _PERF_UI_HELPLINE_H_ 1
#include <stdio.h>
#include <stdarg.h>
-#include "../util/cache.h"
-
struct ui_helpline {
void (*pop)(void);
void (*push)(const char *msg);
@@ -21,6 +20,7 @@ void ui_helpline__push(const char *msg);
void ui_helpline__vpush(const char *fmt, va_list ap);
void ui_helpline__fpush(const char *fmt, ...);
void ui_helpline__puts(const char *msg);
+void ui_helpline__printf(const char *fmt, ...);
int ui_helpline__vshow(const char *fmt, va_list ap);
extern char ui_helpline__current[512];
diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c
index 4bf91b09d62d..e58327595d37 100644
--- a/tools/perf/ui/hist.c
+++ b/tools/perf/ui/hist.c
@@ -1,486 +1,1037 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <errno.h>
+#include <inttypes.h>
#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/compiler.h>
+#include "../util/callchain.h"
+#include "../util/debug.h"
#include "../util/hist.h"
-#include "../util/util.h"
#include "../util/sort.h"
#include "../util/evsel.h"
+#include "../util/evlist.h"
+#include "../util/mem-events.h"
+#include "../util/string2.h"
+#include "../util/thread.h"
+#include "../util/util.h"
/* hist period print (hpp) functions */
-typedef int (*hpp_snprint_fn)(char *buf, size_t size, const char *fmt, ...);
+#define hpp__call_print_fn(hpp, fn, fmt, ...) \
+({ \
+ int __ret = fn(hpp, fmt, ##__VA_ARGS__); \
+ advance_hpp(hpp, __ret); \
+ __ret; \
+})
-static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
- u64 (*get_field)(struct hist_entry *),
- const char *fmt, hpp_snprint_fn print_fn,
- bool fmt_percent)
+static int __hpp__fmt_print(struct perf_hpp *hpp, struct hists *hists, u64 val,
+ int nr_samples, const char *fmt, int len,
+ hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype)
{
- int ret;
- struct hists *hists = he->hists;
- struct perf_evsel *evsel = hists_to_evsel(hists);
-
- if (fmt_percent) {
+ if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY) {
double percent = 0.0;
+ u64 total = fmtype == PERF_HPP_FMT_TYPE__PERCENT ? hists__total_period(hists) :
+ hists__total_latency(hists);
- if (hists->stats.total_period)
- percent = 100.0 * get_field(he) /
- hists->stats.total_period;
+ if (total)
+ percent = 100.0 * val / total;
- ret = print_fn(hpp->buf, hpp->size, fmt, percent);
- } else
- ret = print_fn(hpp->buf, hpp->size, fmt, get_field(he));
+ return hpp__call_print_fn(hpp, print_fn, fmt, len, percent);
+ }
- if (perf_evsel__is_group_event(evsel)) {
- int prev_idx, idx_delta;
- struct hist_entry *pair;
- int nr_members = evsel->nr_members;
+ if (fmtype == PERF_HPP_FMT_TYPE__AVERAGE) {
+ double avg = nr_samples ? (1.0 * val / nr_samples) : 0;
- prev_idx = perf_evsel__group_idx(evsel);
+ return hpp__call_print_fn(hpp, print_fn, fmt, len, avg);
+ }
- list_for_each_entry(pair, &he->pairs.head, pairs.node) {
- u64 period = get_field(pair);
- u64 total = pair->hists->stats.total_period;
+ return hpp__call_print_fn(hpp, print_fn, fmt, len, val);
+}
- if (!total)
- continue;
+struct hpp_fmt_value {
+ struct hists *hists;
+ u64 val;
+ int samples;
+};
- evsel = hists_to_evsel(pair->hists);
- idx_delta = perf_evsel__group_idx(evsel) - prev_idx - 1;
+static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
+ hpp_field_fn get_field, const char *fmt, int len,
+ hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype)
+{
+ int ret = 0;
+ struct hists *hists = he->hists;
+ struct evsel *evsel = hists_to_evsel(hists);
+ struct evsel *pos;
+ char *buf = hpp->buf;
+ size_t size = hpp->size;
+ int i = 0, nr_members = 1;
+ struct hpp_fmt_value *values;
+
+ if (evsel__is_group_event(evsel))
+ nr_members = evsel->core.nr_members;
+
+ values = calloc(nr_members, sizeof(*values));
+ if (values == NULL)
+ return 0;
- while (idx_delta--) {
- /*
- * zero-fill group members in the middle which
- * have no sample
- */
- ret += print_fn(hpp->buf + ret, hpp->size - ret,
- fmt, 0);
- }
+ values[0].hists = evsel__hists(evsel);
+ values[0].val = get_field(he);
+ values[0].samples = he->stat.nr_events;
- if (fmt_percent)
- ret += print_fn(hpp->buf + ret, hpp->size - ret,
- fmt, 100.0 * period / total);
- else
- ret += print_fn(hpp->buf + ret, hpp->size - ret,
- fmt, period);
+ if (evsel__is_group_event(evsel)) {
+ struct hist_entry *pair;
- prev_idx = perf_evsel__group_idx(evsel);
- }
+ for_each_group_member(pos, evsel)
+ values[++i].hists = evsel__hists(pos);
- idx_delta = nr_members - prev_idx - 1;
+ list_for_each_entry(pair, &he->pairs.head, pairs.node) {
+ for (i = 0; i < nr_members; i++) {
+ if (values[i].hists != pair->hists)
+ continue;
- while (idx_delta--) {
- /*
- * zero-fill group members at last which have no sample
- */
- ret += print_fn(hpp->buf + ret, hpp->size - ret,
- fmt, 0);
+ values[i].val = get_field(pair);
+ values[i].samples = pair->stat.nr_events;
+ break;
+ }
}
}
+
+ for (i = 0; i < nr_members; i++) {
+ if (symbol_conf.skip_empty &&
+ values[i].hists->stats.nr_samples == 0)
+ continue;
+
+ ret += __hpp__fmt_print(hpp, values[i].hists, values[i].val,
+ values[i].samples, fmt, len,
+ print_fn, fmtype);
+ }
+
+ free(values);
+
+ /*
+ * Restore original buf and size as it's where caller expects
+ * the result will be saved.
+ */
+ hpp->buf = buf;
+ hpp->size = size;
+
return ret;
}
-#define __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
-static int hpp__header_##_type(struct perf_hpp *hpp) \
-{ \
- int len = _min_width; \
- \
- if (symbol_conf.event_group) { \
- struct perf_evsel *evsel = hpp->ptr; \
- \
- len = max(len, evsel->nr_members * _unit_width); \
- } \
- return scnprintf(hpp->buf, hpp->size, "%*s", len, _str); \
-}
-
-#define __HPP_WIDTH_FN(_type, _min_width, _unit_width) \
-static int hpp__width_##_type(struct perf_hpp *hpp __maybe_unused) \
-{ \
- int len = _min_width; \
- \
- if (symbol_conf.event_group) { \
- struct perf_evsel *evsel = hpp->ptr; \
- \
- len = max(len, evsel->nr_members * _unit_width); \
- } \
- return len; \
-}
-
-#define __HPP_COLOR_PERCENT_FN(_type, _field) \
-static u64 he_get_##_field(struct hist_entry *he) \
-{ \
- return he->stat._field; \
-} \
- \
-static int hpp__color_##_type(struct perf_hpp *hpp, struct hist_entry *he) \
-{ \
- return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \
- (hpp_snprint_fn)percent_color_snprintf, true); \
-}
+int hpp__fmt(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he, hpp_field_fn get_field,
+ const char *fmtstr, hpp_snprint_fn print_fn,
+ enum perf_hpp_fmt_type fmtype)
+{
+ int len = max(fmt->user_len ?: fmt->len, (int)strlen(fmt->name));
-#define __HPP_ENTRY_PERCENT_FN(_type, _field) \
-static int hpp__entry_##_type(struct perf_hpp *hpp, struct hist_entry *he) \
-{ \
- const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%"; \
- return __hpp__fmt(hpp, he, he_get_##_field, fmt, \
- scnprintf, true); \
+ if (symbol_conf.field_sep) {
+ return __hpp__fmt(hpp, he, get_field, fmtstr, 1,
+ print_fn, fmtype);
+ }
+
+ if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY)
+ len -= 2; /* 2 for a space and a % sign */
+ else
+ len -= 1;
+
+ return __hpp__fmt(hpp, he, get_field, fmtstr, len, print_fn, fmtype);
}
-#define __HPP_ENTRY_RAW_FN(_type, _field) \
-static u64 he_get_raw_##_field(struct hist_entry *he) \
-{ \
- return he->stat._field; \
-} \
- \
-static int hpp__entry_##_type(struct perf_hpp *hpp, struct hist_entry *he) \
-{ \
- const char *fmt = symbol_conf.field_sep ? " %"PRIu64 : " %11"PRIu64; \
- return __hpp__fmt(hpp, he, he_get_raw_##_field, fmt, scnprintf, false); \
+int hpp__fmt_acc(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he, hpp_field_fn get_field,
+ const char *fmtstr, hpp_snprint_fn print_fn,
+ enum perf_hpp_fmt_type fmtype)
+{
+ if (!symbol_conf.cumulate_callchain) {
+ int len = fmt->user_len ?: fmt->len;
+ return snprintf(hpp->buf, hpp->size, " %*s", len - 1, "N/A");
+ }
+
+ return hpp__fmt(fmt, hpp, he, get_field, fmtstr, print_fn, fmtype);
}
-#define HPP_PERCENT_FNS(_type, _str, _field, _min_width, _unit_width) \
-__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
-__HPP_WIDTH_FN(_type, _min_width, _unit_width) \
-__HPP_COLOR_PERCENT_FN(_type, _field) \
-__HPP_ENTRY_PERCENT_FN(_type, _field)
+int hpp__fmt_mem_stat(struct perf_hpp_fmt *fmt __maybe_unused, struct perf_hpp *hpp,
+ struct hist_entry *he, enum mem_stat_type mst,
+ const char *fmtstr, hpp_snprint_fn print_fn)
+{
+ struct hists *hists = he->hists;
+ int mem_stat_idx = -1;
+ char *buf = hpp->buf;
+ size_t size = hpp->size;
+ u64 total = 0;
+ int ret = 0;
+
+ for (int i = 0; i < hists->nr_mem_stats; i++) {
+ if (hists->mem_stat_types[i] == mst) {
+ mem_stat_idx = i;
+ break;
+ }
+ }
+ assert(mem_stat_idx != -1);
-#define HPP_RAW_FNS(_type, _str, _field, _min_width, _unit_width) \
-__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
-__HPP_WIDTH_FN(_type, _min_width, _unit_width) \
-__HPP_ENTRY_RAW_FN(_type, _field)
+ for (int i = 0; i < MEM_STAT_LEN; i++)
+ total += hists->mem_stat_total[mem_stat_idx].entries[i];
+ assert(total != 0);
+ for (int i = 0; i < MEM_STAT_LEN; i++) {
+ u64 val = he->mem_stat[mem_stat_idx].entries[i];
-HPP_PERCENT_FNS(overhead, "Overhead", period, 8, 8)
-HPP_PERCENT_FNS(overhead_sys, "sys", period_sys, 8, 8)
-HPP_PERCENT_FNS(overhead_us, "usr", period_us, 8, 8)
-HPP_PERCENT_FNS(overhead_guest_sys, "guest sys", period_guest_sys, 9, 8)
-HPP_PERCENT_FNS(overhead_guest_us, "guest usr", period_guest_us, 9, 8)
+ if (hists->mem_stat_total[mem_stat_idx].entries[i] == 0)
+ continue;
-HPP_RAW_FNS(samples, "Samples", nr_events, 12, 12)
-HPP_RAW_FNS(period, "Period", period, 12, 12)
+ ret += hpp__call_print_fn(hpp, print_fn, fmtstr, 100.0 * val / total);
+ }
+ /*
+ * Restore original buf and size as it's where caller expects
+ * the result will be saved.
+ */
+ hpp->buf = buf;
+ hpp->size = size;
-static int hpp__header_baseline(struct perf_hpp *hpp)
-{
- return scnprintf(hpp->buf, hpp->size, "Baseline");
+ return ret;
}
-static int hpp__width_baseline(struct perf_hpp *hpp __maybe_unused)
+static int field_cmp(u64 field_a, u64 field_b)
{
- return 8;
+ if (field_a > field_b)
+ return 1;
+ if (field_a < field_b)
+ return -1;
+ return 0;
}
-static double baseline_percent(struct hist_entry *he)
+static int hist_entry__new_pair(struct hist_entry *a, struct hist_entry *b,
+ hpp_field_fn get_field, int nr_members,
+ u64 **fields_a, u64 **fields_b)
{
- struct hist_entry *pair = hist_entry__next_pair(he);
- struct hists *pair_hists = pair ? pair->hists : NULL;
- double percent = 0.0;
+ u64 *fa = calloc(nr_members, sizeof(*fa)),
+ *fb = calloc(nr_members, sizeof(*fb));
+ struct hist_entry *pair;
- if (pair) {
- u64 total_period = pair_hists->stats.total_period;
- u64 base_period = pair->stat.period;
+ if (!fa || !fb)
+ goto out_free;
- percent = 100.0 * base_period / total_period;
+ list_for_each_entry(pair, &a->pairs.head, pairs.node) {
+ struct evsel *evsel = hists_to_evsel(pair->hists);
+ fa[evsel__group_idx(evsel)] = get_field(pair);
}
- return percent;
+ list_for_each_entry(pair, &b->pairs.head, pairs.node) {
+ struct evsel *evsel = hists_to_evsel(pair->hists);
+ fb[evsel__group_idx(evsel)] = get_field(pair);
+ }
+
+ *fields_a = fa;
+ *fields_b = fb;
+ return 0;
+out_free:
+ free(fa);
+ free(fb);
+ *fields_a = *fields_b = NULL;
+ return -1;
}
-static int hpp__color_baseline(struct perf_hpp *hpp, struct hist_entry *he)
+static int __hpp__group_sort_idx(struct hist_entry *a, struct hist_entry *b,
+ hpp_field_fn get_field, int idx)
{
- double percent = baseline_percent(he);
+ struct evsel *evsel = hists_to_evsel(a->hists);
+ u64 *fields_a, *fields_b;
+ int cmp, nr_members, ret, i;
+
+ cmp = field_cmp(get_field(a), get_field(b));
+ if (!evsel__is_group_event(evsel))
+ return cmp;
+
+ nr_members = evsel->core.nr_members;
+ if (idx < 1 || idx >= nr_members)
+ return cmp;
+
+ ret = hist_entry__new_pair(a, b, get_field, nr_members, &fields_a, &fields_b);
+ if (ret) {
+ ret = cmp;
+ goto out;
+ }
- if (hist_entry__has_pairs(he) || symbol_conf.field_sep)
- return percent_color_snprintf(hpp->buf, hpp->size, " %6.2f%%", percent);
- else
- return scnprintf(hpp->buf, hpp->size, " ");
+ ret = field_cmp(fields_a[idx], fields_b[idx]);
+ if (ret)
+ goto out;
+
+ for (i = 1; i < nr_members; i++) {
+ if (i != idx) {
+ ret = field_cmp(fields_a[i], fields_b[i]);
+ if (ret)
+ goto out;
+ }
+ }
+
+out:
+ free(fields_a);
+ free(fields_b);
+
+ return ret;
}
-static int hpp__entry_baseline(struct perf_hpp *hpp, struct hist_entry *he)
+static int __hpp__sort(struct hist_entry *a, struct hist_entry *b,
+ hpp_field_fn get_field)
{
- double percent = baseline_percent(he);
- const char *fmt = symbol_conf.field_sep ? "%.2f" : " %6.2f%%";
+ s64 ret;
+ int i, nr_members;
+ struct evsel *evsel;
+ u64 *fields_a, *fields_b;
+
+ if (symbol_conf.group_sort_idx && symbol_conf.event_group) {
+ return __hpp__group_sort_idx(a, b, get_field,
+ symbol_conf.group_sort_idx);
+ }
- if (hist_entry__has_pairs(he) || symbol_conf.field_sep)
- return scnprintf(hpp->buf, hpp->size, fmt, percent);
- else
- return scnprintf(hpp->buf, hpp->size, " ");
+ ret = field_cmp(get_field(a), get_field(b));
+ if (ret || !symbol_conf.event_group)
+ return ret;
+
+ evsel = hists_to_evsel(a->hists);
+ if (!evsel__is_group_event(evsel))
+ return ret;
+
+ nr_members = evsel->core.nr_members;
+ i = hist_entry__new_pair(a, b, get_field, nr_members, &fields_a, &fields_b);
+ if (i)
+ goto out;
+
+ for (i = 1; i < nr_members; i++) {
+ ret = field_cmp(fields_a[i], fields_b[i]);
+ if (ret)
+ break;
+ }
+
+out:
+ free(fields_a);
+ free(fields_b);
+
+ return ret;
}
-static int hpp__header_period_baseline(struct perf_hpp *hpp)
+static int __hpp__sort_acc(struct hist_entry *a, struct hist_entry *b,
+ hpp_field_fn get_field)
{
- const char *fmt = symbol_conf.field_sep ? "%s" : "%12s";
+ s64 ret = 0;
- return scnprintf(hpp->buf, hpp->size, fmt, "Period Base");
+ if (symbol_conf.cumulate_callchain) {
+ /*
+ * Put caller above callee when they have equal period.
+ */
+ ret = field_cmp(get_field(a), get_field(b));
+ if (ret)
+ return ret;
+
+ if ((a->thread == NULL ? NULL : RC_CHK_ACCESS(a->thread)) !=
+ (b->thread == NULL ? NULL : RC_CHK_ACCESS(b->thread)) ||
+ !hist_entry__has_callchains(a) || !symbol_conf.use_callchain)
+ return 0;
+
+ ret = b->callchain->max_depth - a->callchain->max_depth;
+ if (callchain_param.order == ORDER_CALLER)
+ ret = -ret;
+ }
+ return ret;
}
-static int hpp__width_period_baseline(struct perf_hpp *hpp __maybe_unused)
+static bool perf_hpp__is_mem_stat_entry(struct perf_hpp_fmt *fmt);
+
+static enum mem_stat_type hpp__mem_stat_type(struct perf_hpp_fmt *fmt)
{
- return 12;
+ if (!perf_hpp__is_mem_stat_entry(fmt))
+ return -1;
+
+ switch (fmt->idx) {
+ case PERF_HPP__MEM_STAT_OP:
+ return PERF_MEM_STAT_OP;
+ case PERF_HPP__MEM_STAT_CACHE:
+ return PERF_MEM_STAT_CACHE;
+ case PERF_HPP__MEM_STAT_MEMORY:
+ return PERF_MEM_STAT_MEMORY;
+ case PERF_HPP__MEM_STAT_SNOOP:
+ return PERF_MEM_STAT_SNOOP;
+ case PERF_HPP__MEM_STAT_DTLB:
+ return PERF_MEM_STAT_DTLB;
+ default:
+ break;
+ }
+ pr_debug("Should not reach here\n");
+ return -1;
}
-static int hpp__entry_period_baseline(struct perf_hpp *hpp, struct hist_entry *he)
+static int64_t hpp__sort_mem_stat(struct perf_hpp_fmt *fmt __maybe_unused,
+ struct hist_entry *a, struct hist_entry *b)
{
- struct hist_entry *pair = hist_entry__next_pair(he);
- u64 period = pair ? pair->stat.period : 0;
- const char *fmt = symbol_conf.field_sep ? "%" PRIu64 : "%12" PRIu64;
-
- return scnprintf(hpp->buf, hpp->size, fmt, period);
+ return a->stat.period - b->stat.period;
}
-static int hpp__header_delta(struct perf_hpp *hpp)
+static int hpp__width_fn(struct perf_hpp_fmt *fmt,
+ struct perf_hpp *hpp __maybe_unused,
+ struct hists *hists)
{
- const char *fmt = symbol_conf.field_sep ? "%s" : "%7s";
+ int len = fmt->user_len ?: fmt->len;
+ struct evsel *evsel = hists_to_evsel(hists);
+
+ if (symbol_conf.event_group) {
+ int nr = 0;
+ struct evsel *pos;
+
+ for_each_group_evsel(pos, evsel) {
+ if (!symbol_conf.skip_empty ||
+ evsel__hists(pos)->stats.nr_samples)
+ nr++;
+ }
+
+ len = max(len, nr * fmt->len);
+ }
- return scnprintf(hpp->buf, hpp->size, fmt, "Delta");
+ if (len < (int)strlen(fmt->name))
+ len = strlen(fmt->name);
+
+ return len;
}
-static int hpp__width_delta(struct perf_hpp *hpp __maybe_unused)
+static int hpp__header_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hists *hists, int line,
+ int *span __maybe_unused)
{
- return 7;
+ int len = hpp__width_fn(fmt, hpp, hists);
+ const char *hdr = "";
+
+ if (line == hists->hpp_list->nr_header_lines - 1)
+ hdr = fmt->name;
+
+ return scnprintf(hpp->buf, hpp->size, "%*s", len, hdr);
}
-static int hpp__entry_delta(struct perf_hpp *hpp, struct hist_entry *he)
+static int hpp__header_mem_stat_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hists *hists, int line,
+ int *span __maybe_unused)
{
- struct hist_entry *pair = hist_entry__next_pair(he);
- const char *fmt = symbol_conf.field_sep ? "%s" : "%7.7s";
- char buf[32] = " ";
- double diff = 0.0;
+ char *buf = hpp->buf;
+ int ret = 0;
+ int len;
+ enum mem_stat_type mst = hpp__mem_stat_type(fmt);
+ int mem_stat_idx = -1;
+
+ for (int i = 0; i < hists->nr_mem_stats; i++) {
+ if (hists->mem_stat_types[i] == mst) {
+ mem_stat_idx = i;
+ break;
+ }
+ }
+ assert(mem_stat_idx != -1);
- if (pair) {
- if (he->diff.computed)
- diff = he->diff.period_ratio_delta;
- else
- diff = perf_diff__compute_delta(he, pair);
- } else
- diff = perf_diff__period_percent(he, he->stat.period);
+ if (line == 0) {
+ int left, right;
- if (fabs(diff) >= 0.01)
- scnprintf(buf, sizeof(buf), "%+4.2F%%", diff);
+ len = 0;
+ /* update fmt->len for acutally used columns only */
+ for (int i = 0; i < MEM_STAT_LEN; i++) {
+ if (hists->mem_stat_total[mem_stat_idx].entries[i])
+ len += MEM_STAT_PRINT_LEN;
+ }
+ fmt->len = len;
- return scnprintf(hpp->buf, hpp->size, fmt, buf);
-}
+ /* print header directly if single column only */
+ if (len == MEM_STAT_PRINT_LEN)
+ return scnprintf(hpp->buf, hpp->size, "%*s", len, fmt->name);
-static int hpp__header_ratio(struct perf_hpp *hpp)
-{
- const char *fmt = symbol_conf.field_sep ? "%s" : "%14s";
+ left = (len - strlen(fmt->name)) / 2 - 1;
+ right = len - left - strlen(fmt->name) - 2;
- return scnprintf(hpp->buf, hpp->size, fmt, "Ratio");
+ if (left < 0)
+ left = 0;
+ if (right < 0)
+ right = 0;
+
+ return scnprintf(hpp->buf, hpp->size, "%.*s %s %.*s",
+ left, graph_dotted_line, fmt->name, right, graph_dotted_line);
+ }
+
+
+ len = hpp->size;
+ for (int i = 0; i < MEM_STAT_LEN; i++) {
+ int printed;
+
+ if (hists->mem_stat_total[mem_stat_idx].entries[i] == 0)
+ continue;
+
+ printed = scnprintf(buf, len, "%*s", MEM_STAT_PRINT_LEN,
+ mem_stat_name(mst, i));
+ ret += printed;
+ buf += printed;
+ len -= printed;
+ }
+ return ret;
}
-static int hpp__width_ratio(struct perf_hpp *hpp __maybe_unused)
+int hpp_color_scnprintf(struct perf_hpp *hpp, const char *fmt, ...)
{
- return 14;
+ va_list args;
+ ssize_t ssize = hpp->size;
+ double percent;
+ int ret, len;
+
+ va_start(args, fmt);
+ len = va_arg(args, int);
+ percent = va_arg(args, double);
+ ret = percent_color_len_snprintf(hpp->buf, hpp->size, fmt, len, percent);
+ va_end(args);
+
+ return (ret >= ssize) ? (ssize - 1) : ret;
}
-static int hpp__entry_ratio(struct perf_hpp *hpp, struct hist_entry *he)
+static int hpp_entry_scnprintf(struct perf_hpp *hpp, const char *fmt, ...)
{
- struct hist_entry *pair = hist_entry__next_pair(he);
- const char *fmt = symbol_conf.field_sep ? "%s" : "%14s";
- char buf[32] = " ";
- double ratio = 0.0;
+ va_list args;
+ ssize_t ssize = hpp->size;
+ int ret;
- if (pair) {
- if (he->diff.computed)
- ratio = he->diff.period_ratio;
- else
- ratio = perf_diff__compute_ratio(he, pair);
- }
+ va_start(args, fmt);
+ ret = vsnprintf(hpp->buf, hpp->size, fmt, args);
+ va_end(args);
- if (ratio > 0.0)
- scnprintf(buf, sizeof(buf), "%+14.6F", ratio);
+ return (ret >= ssize) ? (ssize - 1) : ret;
+}
- return scnprintf(hpp->buf, hpp->size, fmt, buf);
+#define __HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \
+static u64 he_get_##_field(struct hist_entry *he) \
+{ \
+ return he->stat._field; \
+} \
+ \
+static int hpp__color_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \
+ hpp_color_scnprintf, _fmttype); \
}
-static int hpp__header_wdiff(struct perf_hpp *hpp)
-{
- const char *fmt = symbol_conf.field_sep ? "%s" : "%14s";
+#define __HPP_ENTRY_PERCENT_FN(_type, _field, _fmttype) \
+static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \
+ hpp_entry_scnprintf, _fmttype); \
+}
- return scnprintf(hpp->buf, hpp->size, fmt, "Weighted diff");
+#define __HPP_SORT_FN(_type, _field) \
+static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct hist_entry *a, struct hist_entry *b) \
+{ \
+ return __hpp__sort(a, b, he_get_##_field); \
}
-static int hpp__width_wdiff(struct perf_hpp *hpp __maybe_unused)
-{
- return 14;
+#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field, _fmttype) \
+static u64 he_get_acc_##_field(struct hist_entry *he) \
+{ \
+ return he->stat_acc->_field; \
+} \
+ \
+static int hpp__color_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \
+ hpp_color_scnprintf, _fmttype); \
}
-static int hpp__entry_wdiff(struct perf_hpp *hpp, struct hist_entry *he)
-{
- struct hist_entry *pair = hist_entry__next_pair(he);
- const char *fmt = symbol_conf.field_sep ? "%s" : "%14s";
- char buf[32] = " ";
- s64 wdiff = 0;
+#define __HPP_ENTRY_ACC_PERCENT_FN(_type, _field, _fmttype) \
+static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \
+ hpp_entry_scnprintf, _fmttype); \
+}
- if (pair) {
- if (he->diff.computed)
- wdiff = he->diff.wdiff;
- else
- wdiff = perf_diff__compute_wdiff(he, pair);
- }
+#define __HPP_SORT_ACC_FN(_type, _field) \
+static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct hist_entry *a, struct hist_entry *b) \
+{ \
+ return __hpp__sort_acc(a, b, he_get_acc_##_field); \
+}
- if (wdiff != 0)
- scnprintf(buf, sizeof(buf), "%14ld", wdiff);
+#define __HPP_ENTRY_RAW_FN(_type, _field) \
+static u64 he_get_raw_##_field(struct hist_entry *he) \
+{ \
+ return he->stat._field; \
+} \
+ \
+static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ return hpp__fmt(fmt, hpp, he, he_get_raw_##_field, " %*"PRIu64, \
+ hpp_entry_scnprintf, PERF_HPP_FMT_TYPE__RAW); \
+}
+
+#define __HPP_SORT_RAW_FN(_type, _field) \
+static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct hist_entry *a, struct hist_entry *b) \
+{ \
+ return __hpp__sort(a, b, he_get_raw_##_field); \
+}
+
+#define __HPP_ENTRY_AVERAGE_FN(_type, _field) \
+static u64 he_get_##_field(struct hist_entry *he) \
+{ \
+ return he->stat._field; \
+} \
+ \
+static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.1f", \
+ hpp_entry_scnprintf, PERF_HPP_FMT_TYPE__AVERAGE); \
+}
+
+#define __HPP_SORT_AVERAGE_FN(_type, _field) \
+static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct hist_entry *a, struct hist_entry *b) \
+{ \
+ return __hpp__sort(a, b, he_get_##_field); \
+}
- return scnprintf(hpp->buf, hpp->size, fmt, buf);
+#define __HPP_COLOR_MEM_STAT_FN(_name, _type) \
+static int 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_color_scnprintf); \
+}
+
+#define __HPP_ENTRY_MEM_STAT_FN(_name, _type) \
+static int hpp__entry_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_entry_scnprintf); \
}
-static int hpp__header_formula(struct perf_hpp *hpp)
+#define HPP_PERCENT_FNS(_type, _field, _fmttype) \
+__HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \
+__HPP_ENTRY_PERCENT_FN(_type, _field, _fmttype) \
+__HPP_SORT_FN(_type, _field)
+
+#define HPP_PERCENT_ACC_FNS(_type, _field, _fmttype) \
+__HPP_COLOR_ACC_PERCENT_FN(_type, _field, _fmttype) \
+__HPP_ENTRY_ACC_PERCENT_FN(_type, _field, _fmttype) \
+__HPP_SORT_ACC_FN(_type, _field)
+
+#define HPP_RAW_FNS(_type, _field) \
+__HPP_ENTRY_RAW_FN(_type, _field) \
+__HPP_SORT_RAW_FN(_type, _field)
+
+#define HPP_AVERAGE_FNS(_type, _field) \
+__HPP_ENTRY_AVERAGE_FN(_type, _field) \
+__HPP_SORT_AVERAGE_FN(_type, _field)
+
+#define HPP_MEM_STAT_FNS(_name, _type) \
+__HPP_COLOR_MEM_STAT_FN(_name, _type) \
+__HPP_ENTRY_MEM_STAT_FN(_name, _type)
+
+HPP_PERCENT_FNS(overhead, period, PERF_HPP_FMT_TYPE__PERCENT)
+HPP_PERCENT_FNS(latency, latency, PERF_HPP_FMT_TYPE__LATENCY)
+HPP_PERCENT_FNS(overhead_sys, period_sys, PERF_HPP_FMT_TYPE__PERCENT)
+HPP_PERCENT_FNS(overhead_us, period_us, PERF_HPP_FMT_TYPE__PERCENT)
+HPP_PERCENT_FNS(overhead_guest_sys, period_guest_sys, PERF_HPP_FMT_TYPE__PERCENT)
+HPP_PERCENT_FNS(overhead_guest_us, period_guest_us, PERF_HPP_FMT_TYPE__PERCENT)
+HPP_PERCENT_ACC_FNS(overhead_acc, period, PERF_HPP_FMT_TYPE__PERCENT)
+HPP_PERCENT_ACC_FNS(latency_acc, latency, PERF_HPP_FMT_TYPE__LATENCY)
+
+HPP_RAW_FNS(samples, nr_events)
+HPP_RAW_FNS(period, period)
+
+HPP_AVERAGE_FNS(weight1, weight1)
+HPP_AVERAGE_FNS(weight2, weight2)
+HPP_AVERAGE_FNS(weight3, weight3)
+
+HPP_MEM_STAT_FNS(op, OP)
+HPP_MEM_STAT_FNS(cache, CACHE)
+HPP_MEM_STAT_FNS(memory, MEMORY)
+HPP_MEM_STAT_FNS(snoop, SNOOP)
+HPP_MEM_STAT_FNS(dtlb, DTLB)
+
+static int64_t hpp__nop_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
+ struct hist_entry *a __maybe_unused,
+ struct hist_entry *b __maybe_unused)
{
- const char *fmt = symbol_conf.field_sep ? "%s" : "%70s";
+ return 0;
+}
- return scnprintf(hpp->buf, hpp->size, fmt, "Formula");
+static bool perf_hpp__is_mem_stat_entry(struct perf_hpp_fmt *fmt)
+{
+ return fmt->sort == hpp__sort_mem_stat;
}
-static int hpp__width_formula(struct perf_hpp *hpp __maybe_unused)
+static bool perf_hpp__is_hpp_entry(struct perf_hpp_fmt *a)
{
- return 70;
+ return a->header == hpp__header_fn;
}
-static int hpp__entry_formula(struct perf_hpp *hpp, struct hist_entry *he)
+static bool hpp__equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
{
- struct hist_entry *pair = hist_entry__next_pair(he);
- const char *fmt = symbol_conf.field_sep ? "%s" : "%-70s";
- char buf[96] = " ";
+ if (!perf_hpp__is_hpp_entry(a) || !perf_hpp__is_hpp_entry(b))
+ return false;
+
+ return a->idx == b->idx;
+}
- if (pair)
- perf_diff__formula(he, pair, buf, sizeof(buf));
+static bool hpp__equal_mem_stat(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
+{
+ if (!perf_hpp__is_mem_stat_entry(a) || !perf_hpp__is_mem_stat_entry(b))
+ return false;
- return scnprintf(hpp->buf, hpp->size, fmt, buf);
+ return a->entry == b->entry;
}
-#define HPP__COLOR_PRINT_FNS(_name) \
+#define HPP__COLOR_PRINT_FNS(_name, _fn, _idx) \
+ { \
+ .name = _name, \
+ .header = hpp__header_fn, \
+ .width = hpp__width_fn, \
+ .color = hpp__color_ ## _fn, \
+ .entry = hpp__entry_ ## _fn, \
+ .cmp = hpp__nop_cmp, \
+ .collapse = hpp__nop_cmp, \
+ .sort = hpp__sort_ ## _fn, \
+ .idx = PERF_HPP__ ## _idx, \
+ .equal = hpp__equal, \
+ }
+
+#define HPP__COLOR_ACC_PRINT_FNS(_name, _fn, _idx) \
+ { \
+ .name = _name, \
+ .header = hpp__header_fn, \
+ .width = hpp__width_fn, \
+ .color = hpp__color_ ## _fn, \
+ .entry = hpp__entry_ ## _fn, \
+ .cmp = hpp__nop_cmp, \
+ .collapse = hpp__nop_cmp, \
+ .sort = hpp__sort_ ## _fn, \
+ .idx = PERF_HPP__ ## _idx, \
+ .equal = hpp__equal, \
+ }
+
+#define HPP__PRINT_FNS(_name, _fn, _idx) \
{ \
- .header = hpp__header_ ## _name, \
- .width = hpp__width_ ## _name, \
- .color = hpp__color_ ## _name, \
- .entry = hpp__entry_ ## _name \
+ .name = _name, \
+ .header = hpp__header_fn, \
+ .width = hpp__width_fn, \
+ .entry = hpp__entry_ ## _fn, \
+ .cmp = hpp__nop_cmp, \
+ .collapse = hpp__nop_cmp, \
+ .sort = hpp__sort_ ## _fn, \
+ .idx = PERF_HPP__ ## _idx, \
+ .equal = hpp__equal, \
}
-#define HPP__PRINT_FNS(_name) \
+#define HPP__MEM_STAT_PRINT_FNS(_name, _fn, _type) \
{ \
- .header = hpp__header_ ## _name, \
- .width = hpp__width_ ## _name, \
- .entry = hpp__entry_ ## _name \
+ .name = _name, \
+ .header = hpp__header_mem_stat_fn, \
+ .width = hpp__width_fn, \
+ .color = hpp__color_mem_stat_ ## _fn, \
+ .entry = hpp__entry_mem_stat_ ## _fn, \
+ .cmp = hpp__nop_cmp, \
+ .collapse = hpp__nop_cmp, \
+ .sort = hpp__sort_mem_stat, \
+ .idx = PERF_HPP__MEM_STAT_ ## _type, \
+ .equal = hpp__equal_mem_stat, \
}
struct perf_hpp_fmt perf_hpp__format[] = {
- HPP__COLOR_PRINT_FNS(baseline),
- HPP__COLOR_PRINT_FNS(overhead),
- HPP__COLOR_PRINT_FNS(overhead_sys),
- HPP__COLOR_PRINT_FNS(overhead_us),
- HPP__COLOR_PRINT_FNS(overhead_guest_sys),
- HPP__COLOR_PRINT_FNS(overhead_guest_us),
- HPP__PRINT_FNS(samples),
- HPP__PRINT_FNS(period),
- HPP__PRINT_FNS(period_baseline),
- HPP__PRINT_FNS(delta),
- HPP__PRINT_FNS(ratio),
- HPP__PRINT_FNS(wdiff),
- HPP__PRINT_FNS(formula)
+ HPP__COLOR_PRINT_FNS("Overhead", overhead, OVERHEAD),
+ HPP__COLOR_PRINT_FNS("Latency", latency, LATENCY),
+ HPP__COLOR_PRINT_FNS("sys", overhead_sys, OVERHEAD_SYS),
+ HPP__COLOR_PRINT_FNS("usr", overhead_us, OVERHEAD_US),
+ HPP__COLOR_PRINT_FNS("guest sys", overhead_guest_sys, OVERHEAD_GUEST_SYS),
+ HPP__COLOR_PRINT_FNS("guest usr", overhead_guest_us, OVERHEAD_GUEST_US),
+ HPP__COLOR_ACC_PRINT_FNS("Children", overhead_acc, OVERHEAD_ACC),
+ HPP__COLOR_ACC_PRINT_FNS("Latency", latency_acc, LATENCY_ACC),
+ HPP__PRINT_FNS("Samples", samples, SAMPLES),
+ HPP__PRINT_FNS("Period", period, PERIOD),
+ HPP__PRINT_FNS("Weight1", weight1, WEIGHT1),
+ HPP__PRINT_FNS("Weight2", weight2, WEIGHT2),
+ HPP__PRINT_FNS("Weight3", weight3, WEIGHT3),
+ HPP__MEM_STAT_PRINT_FNS("Mem Op", op, OP),
+ HPP__MEM_STAT_PRINT_FNS("Cache", cache, CACHE),
+ HPP__MEM_STAT_PRINT_FNS("Memory", memory, MEMORY),
+ HPP__MEM_STAT_PRINT_FNS("Snoop", snoop, SNOOP),
+ HPP__MEM_STAT_PRINT_FNS("D-TLB", dtlb, DTLB),
};
-LIST_HEAD(perf_hpp__list);
-
+struct perf_hpp_list perf_hpp_list = {
+ .fields = LIST_HEAD_INIT(perf_hpp_list.fields),
+ .sorts = LIST_HEAD_INIT(perf_hpp_list.sorts),
+ .nr_header_lines = 1,
+};
#undef HPP__COLOR_PRINT_FNS
+#undef HPP__COLOR_ACC_PRINT_FNS
#undef HPP__PRINT_FNS
+#undef HPP__MEM_STAT_PRINT_FNS
#undef HPP_PERCENT_FNS
+#undef HPP_PERCENT_ACC_FNS
#undef HPP_RAW_FNS
+#undef HPP_AVERAGE_FNS
+#undef HPP_MEM_STAT_FNS
#undef __HPP_HEADER_FN
#undef __HPP_WIDTH_FN
#undef __HPP_COLOR_PERCENT_FN
#undef __HPP_ENTRY_PERCENT_FN
+#undef __HPP_COLOR_ACC_PERCENT_FN
+#undef __HPP_ENTRY_ACC_PERCENT_FN
#undef __HPP_ENTRY_RAW_FN
+#undef __HPP_ENTRY_AVERAGE_FN
+#undef __HPP_COLOR_MEM_STAT_FN
+#undef __HPP_ENTRY_MEM_STAT_FN
+
+#undef __HPP_SORT_FN
+#undef __HPP_SORT_ACC_FN
+#undef __HPP_SORT_RAW_FN
+#undef __HPP_SORT_AVERAGE_FN
+
+static void fmt_free(struct perf_hpp_fmt *fmt)
+{
+ /*
+ * At this point fmt should be completely
+ * unhooked, if not it's a bug.
+ */
+ BUG_ON(!list_empty(&fmt->list));
+ BUG_ON(!list_empty(&fmt->sort_list));
+
+ if (fmt->free)
+ fmt->free(fmt);
+}
+static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
+{
+ return a->equal && a->equal(a, b);
+}
void perf_hpp__init(void)
{
+ int i;
+
+ for (i = 0; i < PERF_HPP__MAX_INDEX; i++) {
+ struct perf_hpp_fmt *fmt = &perf_hpp__format[i];
+
+ INIT_LIST_HEAD(&fmt->list);
+
+ /* sort_list may be linked by setup_sorting() */
+ if (fmt->sort_list.next == NULL)
+ INIT_LIST_HEAD(&fmt->sort_list);
+ }
+
+ /*
+ * If user specified field order, no need to setup default fields.
+ */
+ if (is_strict_order(field_order))
+ return;
+
+ /*
+ * Overhead and latency columns are added in setup_overhead(),
+ * so they are added implicitly here only if they were added
+ * by setup_overhead() before (have was_taken flag set).
+ * This is required because setup_overhead() has more complex
+ * logic, in particular it does not add "overhead" if user
+ * specified "latency" in sort order, and vise versa.
+ */
+ if (symbol_conf.cumulate_callchain) {
+ /*
+ * Addition of fields is idempotent, so we add latency
+ * column twice to get desired order with simpler logic.
+ */
+ if (symbol_conf.prefer_latency)
+ hpp_dimension__add_output(PERF_HPP__LATENCY_ACC, true);
+ hpp_dimension__add_output(PERF_HPP__OVERHEAD_ACC, true);
+ if (symbol_conf.enable_latency)
+ hpp_dimension__add_output(PERF_HPP__LATENCY_ACC, true);
+ perf_hpp__format[PERF_HPP__OVERHEAD].name = "Self";
+ }
+
+ if (symbol_conf.prefer_latency)
+ hpp_dimension__add_output(PERF_HPP__LATENCY, true);
+ hpp_dimension__add_output(PERF_HPP__OVERHEAD, true);
+ if (symbol_conf.enable_latency)
+ hpp_dimension__add_output(PERF_HPP__LATENCY, true);
+
if (symbol_conf.show_cpu_utilization) {
- perf_hpp__column_enable(PERF_HPP__OVERHEAD_SYS);
- perf_hpp__column_enable(PERF_HPP__OVERHEAD_US);
+ hpp_dimension__add_output(PERF_HPP__OVERHEAD_SYS, false);
+ hpp_dimension__add_output(PERF_HPP__OVERHEAD_US, false);
if (perf_guest) {
- perf_hpp__column_enable(PERF_HPP__OVERHEAD_GUEST_SYS);
- perf_hpp__column_enable(PERF_HPP__OVERHEAD_GUEST_US);
+ hpp_dimension__add_output(PERF_HPP__OVERHEAD_GUEST_SYS, false);
+ hpp_dimension__add_output(PERF_HPP__OVERHEAD_GUEST_US, false);
}
}
if (symbol_conf.show_nr_samples)
- perf_hpp__column_enable(PERF_HPP__SAMPLES);
+ hpp_dimension__add_output(PERF_HPP__SAMPLES, false);
if (symbol_conf.show_total_period)
- perf_hpp__column_enable(PERF_HPP__PERIOD);
+ hpp_dimension__add_output(PERF_HPP__PERIOD, false);
}
-void perf_hpp__column_register(struct perf_hpp_fmt *format)
+void perf_hpp_list__column_register(struct perf_hpp_list *list,
+ struct perf_hpp_fmt *format)
{
- list_add_tail(&format->list, &perf_hpp__list);
+ list_add_tail(&format->list, &list->fields);
}
-void perf_hpp__column_enable(unsigned col)
+void perf_hpp_list__register_sort_field(struct perf_hpp_list *list,
+ struct perf_hpp_fmt *format)
{
- BUG_ON(col >= PERF_HPP__MAX_INDEX);
- perf_hpp__column_register(&perf_hpp__format[col]);
+ list_add_tail(&format->sort_list, &list->sorts);
}
-static inline void advance_hpp(struct perf_hpp *hpp, int inc)
+void perf_hpp_list__prepend_sort_field(struct perf_hpp_list *list,
+ struct perf_hpp_fmt *format)
{
- hpp->buf += inc;
- hpp->size -= inc;
+ list_add(&format->sort_list, &list->sorts);
}
-int hist_entry__period_snprintf(struct perf_hpp *hpp, struct hist_entry *he,
- bool color)
+static void perf_hpp__column_unregister(struct perf_hpp_fmt *format)
{
- const char *sep = symbol_conf.field_sep;
- struct perf_hpp_fmt *fmt;
- char *start = hpp->buf;
- int ret;
- bool first = true;
+ list_del_init(&format->list);
+ list_del_init(&format->sort_list);
+ fmt_free(format);
+}
- if (symbol_conf.exclude_other && !he->parent)
- return 0;
+void perf_hpp__cancel_cumulate(struct evlist *evlist)
+{
+ struct perf_hpp_fmt *fmt, *acc, *ovh, *acc_lat, *tmp;
+ struct evsel *evsel;
- perf_hpp__for_each_format(fmt) {
- /*
- * If there's no field_sep, we still need
- * to display initial ' '.
- */
- if (!sep || !first) {
- ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " ");
- advance_hpp(hpp, ret);
- } else
- first = false;
+ if (is_strict_order(field_order))
+ return;
- if (color && fmt->color)
- ret = fmt->color(hpp, he);
- else
- ret = fmt->entry(hpp, he);
+ ovh = &perf_hpp__format[PERF_HPP__OVERHEAD];
+ acc = &perf_hpp__format[PERF_HPP__OVERHEAD_ACC];
+ acc_lat = &perf_hpp__format[PERF_HPP__LATENCY_ACC];
+
+ perf_hpp_list__for_each_format_safe(&perf_hpp_list, fmt, tmp) {
+ if (fmt_equal(acc, fmt) || fmt_equal(acc_lat, fmt)) {
+ perf_hpp__column_unregister(fmt);
+ continue;
+ }
- advance_hpp(hpp, ret);
+ if (fmt_equal(ovh, fmt))
+ fmt->name = "Overhead";
}
- return hpp->buf - start;
+ evlist__for_each_entry(evlist, evsel) {
+ struct hists *hists = evsel__hists(evsel);
+ struct perf_hpp_list_node *node;
+
+ list_for_each_entry(node, &hists->hpp_formats, list) {
+ perf_hpp_list__for_each_format_safe(&node->hpp, fmt, tmp) {
+ if (fmt_equal(acc, fmt) || fmt_equal(acc_lat, fmt)) {
+ perf_hpp__column_unregister(fmt);
+ continue;
+ }
+
+ if (fmt_equal(ovh, fmt))
+ fmt->name = "Overhead";
+ }
+ }
+ }
}
-int hist_entry__sort_snprintf(struct hist_entry *he, char *s, size_t size,
- struct hists *hists)
+void perf_hpp__cancel_latency(struct evlist *evlist)
{
- const char *sep = symbol_conf.field_sep;
- struct sort_entry *se;
- int ret = 0;
+ struct perf_hpp_fmt *fmt, *lat, *acc, *tmp;
+ struct evsel *evsel;
+
+ if (is_strict_order(field_order))
+ return;
+ if (sort_order && strstr(sort_order, "latency"))
+ return;
+
+ lat = &perf_hpp__format[PERF_HPP__LATENCY];
+ acc = &perf_hpp__format[PERF_HPP__LATENCY_ACC];
+
+ perf_hpp_list__for_each_format_safe(&perf_hpp_list, fmt, tmp) {
+ if (fmt_equal(lat, fmt) || fmt_equal(acc, fmt))
+ perf_hpp__column_unregister(fmt);
+ }
+
+ evlist__for_each_entry(evlist, evsel) {
+ struct hists *hists = evsel__hists(evsel);
+ struct perf_hpp_list_node *node;
- list_for_each_entry(se, &hist_entry__sort_list, list) {
- if (se->elide)
+ list_for_each_entry(node, &hists->hpp_formats, list) {
+ perf_hpp_list__for_each_format_safe(&node->hpp, fmt, tmp) {
+ if (fmt_equal(lat, fmt) || fmt_equal(acc, fmt))
+ perf_hpp__column_unregister(fmt);
+ }
+ }
+ }
+}
+
+void perf_hpp__setup_output_field(struct perf_hpp_list *list)
+{
+ struct perf_hpp_fmt *fmt;
+
+ /* append sort keys to output field */
+ perf_hpp_list__for_each_sort_list(list, fmt) {
+ struct perf_hpp_fmt *pos;
+
+ /* skip sort-only fields ("sort_compute" in perf diff) */
+ if (!fmt->entry && !fmt->color)
continue;
- ret += scnprintf(s + ret, size - ret, "%s", sep ?: " ");
- ret += se->se_snprintf(he, s + ret, size - ret,
- hists__col_len(hists, se->se_width_idx));
+ perf_hpp_list__for_each_format(list, pos) {
+ if (fmt_equal(fmt, pos))
+ goto next;
+ }
+
+ perf_hpp__column_register(fmt);
+next:
+ continue;
}
+}
- return ret;
+void perf_hpp__append_sort_keys(struct perf_hpp_list *list)
+{
+ struct perf_hpp_fmt *fmt;
+
+ /* append output fields to sort keys */
+ perf_hpp_list__for_each_format(list, fmt) {
+ struct perf_hpp_fmt *pos;
+
+ perf_hpp_list__for_each_sort_list(list, pos) {
+ if (fmt_equal(fmt, pos))
+ goto next;
+ }
+
+ perf_hpp__register_sort_field(fmt);
+next:
+ continue;
+ }
+}
+
+
+void perf_hpp__reset_output_field(struct perf_hpp_list *list)
+{
+ struct perf_hpp_fmt *fmt, *tmp;
+
+ /* reset output fields */
+ perf_hpp_list__for_each_format_safe(list, fmt, tmp)
+ perf_hpp__column_unregister(fmt);
+
+ /* reset sort keys */
+ perf_hpp_list__for_each_sort_list_safe(list, fmt, tmp)
+ perf_hpp__column_unregister(fmt);
}
/*
@@ -489,25 +1040,234 @@ int hist_entry__sort_snprintf(struct hist_entry *he, char *s, size_t size,
unsigned int hists__sort_list_width(struct hists *hists)
{
struct perf_hpp_fmt *fmt;
- struct sort_entry *se;
- int i = 0, ret = 0;
- struct perf_hpp dummy_hpp = {
- .ptr = hists_to_evsel(hists),
- };
-
- perf_hpp__for_each_format(fmt) {
- if (i)
+ int ret = 0;
+ bool first = true;
+ struct perf_hpp dummy_hpp;
+
+ hists__for_each_format(hists, fmt) {
+ if (perf_hpp__should_skip(fmt, hists))
+ continue;
+
+ if (first)
+ first = false;
+ else
ret += 2;
- ret += fmt->width(&dummy_hpp);
+ ret += fmt->width(fmt, &dummy_hpp, hists);
}
- list_for_each_entry(se, &hist_entry__sort_list, list)
- if (!se->elide)
- ret += 2 + hists__col_len(hists, se->se_width_idx);
-
- if (verbose) /* Addr + origin */
+ if (verbose > 0 && hists__has(hists, sym)) /* Addr + origin */
ret += 3 + BITS_PER_LONG / 4;
return ret;
}
+
+unsigned int hists__overhead_width(struct hists *hists)
+{
+ struct perf_hpp_fmt *fmt;
+ int ret = 0;
+ bool first = true;
+ struct perf_hpp dummy_hpp;
+
+ hists__for_each_format(hists, fmt) {
+ if (perf_hpp__is_sort_entry(fmt) || perf_hpp__is_dynamic_entry(fmt))
+ break;
+
+ if (first)
+ first = false;
+ else
+ ret += 2;
+
+ ret += fmt->width(fmt, &dummy_hpp, hists);
+ }
+
+ return ret;
+}
+
+void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists)
+{
+ if (perf_hpp__is_sort_entry(fmt))
+ return perf_hpp__reset_sort_width(fmt, hists);
+
+ if (perf_hpp__is_dynamic_entry(fmt))
+ return;
+
+ BUG_ON(fmt->idx >= PERF_HPP__MAX_INDEX);
+
+ switch (fmt->idx) {
+ case PERF_HPP__OVERHEAD:
+ case PERF_HPP__LATENCY:
+ case PERF_HPP__OVERHEAD_SYS:
+ case PERF_HPP__OVERHEAD_US:
+ case PERF_HPP__OVERHEAD_ACC:
+ fmt->len = 8;
+ break;
+
+ case PERF_HPP__OVERHEAD_GUEST_SYS:
+ case PERF_HPP__OVERHEAD_GUEST_US:
+ fmt->len = 9;
+ break;
+
+ case PERF_HPP__SAMPLES:
+ case PERF_HPP__PERIOD:
+ fmt->len = 12;
+ break;
+
+ case PERF_HPP__WEIGHT1:
+ case PERF_HPP__WEIGHT2:
+ case PERF_HPP__WEIGHT3:
+ fmt->len = 8;
+ break;
+
+ case PERF_HPP__MEM_STAT_OP:
+ case PERF_HPP__MEM_STAT_CACHE:
+ case PERF_HPP__MEM_STAT_MEMORY:
+ case PERF_HPP__MEM_STAT_SNOOP:
+ case PERF_HPP__MEM_STAT_DTLB:
+ fmt->len = MEM_STAT_LEN * MEM_STAT_PRINT_LEN;
+ break;
+
+ default:
+ break;
+ }
+}
+
+void hists__reset_column_width(struct hists *hists)
+{
+ struct perf_hpp_fmt *fmt;
+ struct perf_hpp_list_node *node;
+
+ hists__for_each_format(hists, fmt)
+ perf_hpp__reset_width(fmt, hists);
+
+ /* hierarchy entries have their own hpp list */
+ list_for_each_entry(node, &hists->hpp_formats, list) {
+ perf_hpp_list__for_each_format(&node->hpp, fmt)
+ perf_hpp__reset_width(fmt, hists);
+ }
+}
+
+void perf_hpp__set_user_width(const char *width_list_str)
+{
+ struct perf_hpp_fmt *fmt;
+ const char *ptr = width_list_str;
+
+ perf_hpp_list__for_each_format(&perf_hpp_list, fmt) {
+ char *p;
+
+ int len = strtol(ptr, &p, 10);
+ fmt->user_len = len;
+
+ if (*p == ',')
+ ptr = p + 1;
+ else
+ break;
+ }
+}
+
+static int add_hierarchy_fmt(struct hists *hists, struct perf_hpp_fmt *fmt)
+{
+ struct perf_hpp_list_node *node = NULL;
+ struct perf_hpp_fmt *fmt_copy;
+ bool found = false;
+ bool skip = perf_hpp__should_skip(fmt, hists);
+
+ list_for_each_entry(node, &hists->hpp_formats, list) {
+ if (node->level == fmt->level) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ node = malloc(sizeof(*node));
+ if (node == NULL)
+ return -1;
+
+ node->skip = skip;
+ node->level = fmt->level;
+ perf_hpp_list__init(&node->hpp);
+
+ hists->nr_hpp_node++;
+ list_add_tail(&node->list, &hists->hpp_formats);
+ }
+
+ fmt_copy = perf_hpp_fmt__dup(fmt);
+ if (fmt_copy == NULL)
+ return -1;
+
+ if (!skip)
+ node->skip = false;
+
+ list_add_tail(&fmt_copy->list, &node->hpp.fields);
+ list_add_tail(&fmt_copy->sort_list, &node->hpp.sorts);
+
+ return 0;
+}
+
+int perf_hpp__setup_hists_formats(struct perf_hpp_list *list,
+ struct evlist *evlist)
+{
+ struct evsel *evsel;
+ struct perf_hpp_fmt *fmt;
+ struct hists *hists;
+ int ret;
+
+ if (!symbol_conf.report_hierarchy)
+ return 0;
+
+ evlist__for_each_entry(evlist, evsel) {
+ hists = evsel__hists(evsel);
+
+ perf_hpp_list__for_each_sort_list(list, fmt) {
+ if (perf_hpp__is_dynamic_entry(fmt) &&
+ !perf_hpp__defined_dynamic_entry(fmt, hists))
+ continue;
+
+ ret = add_hierarchy_fmt(hists, fmt);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int perf_hpp__alloc_mem_stats(struct perf_hpp_list *list, struct evlist *evlist)
+{
+ struct perf_hpp_fmt *fmt;
+ struct evsel *evsel;
+ enum mem_stat_type mst[16];
+ unsigned nr_mem_stats = 0;
+
+ perf_hpp_list__for_each_format(list, fmt) {
+ if (!perf_hpp__is_mem_stat_entry(fmt))
+ continue;
+
+ assert(nr_mem_stats < ARRAY_SIZE(mst));
+ mst[nr_mem_stats++] = hpp__mem_stat_type(fmt);
+ }
+
+ if (nr_mem_stats == 0)
+ return 0;
+
+ list->nr_header_lines = 2;
+
+ evlist__for_each_entry(evlist, evsel) {
+ struct hists *hists = evsel__hists(evsel);
+
+ hists->mem_stat_types = calloc(nr_mem_stats,
+ sizeof(*hists->mem_stat_types));
+ if (hists->mem_stat_types == NULL)
+ return -ENOMEM;
+
+ hists->mem_stat_total = calloc(nr_mem_stats,
+ sizeof(*hists->mem_stat_total));
+ if (hists->mem_stat_total == NULL)
+ return -ENOMEM;
+
+ memcpy(hists->mem_stat_types, mst, nr_mem_stats * sizeof(*mst));
+ hists->nr_mem_stats = nr_mem_stats;
+ }
+ return 0;
+}
diff --git a/tools/perf/ui/keysyms.c b/tools/perf/ui/keysyms.c
new file mode 100644
index 000000000000..b64564b07f2f
--- /dev/null
+++ b/tools/perf/ui/keysyms.c
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include "keysyms.h"
+#include <linux/ctype.h>
+#include <linux/kernel.h>
+
+const char *key_name(int key, char *bf, size_t size)
+{
+ if (isprint(key)) {
+ scnprintf(bf, size, "%c", key);
+ } else if (key < 32) {
+ scnprintf(bf, size, "Ctrl+%c", key + '@');
+ } else {
+ const char *name = NULL;
+
+ switch (key) {
+ case K_DOWN: name = "Down"; break;
+ case K_END: name = "End"; break;
+ case K_ENTER: name = "Enter"; break;
+ case K_ESC: name = "ESC"; break;
+ case K_F1: name = "F1"; break;
+ case K_HOME: name = "Home"; break;
+ case K_LEFT: name = "Left"; break;
+ case K_PGDN: name = "PgDown"; break;
+ case K_PGUP: name = "PgUp"; break;
+ case K_RIGHT: name = "Right"; break;
+ case K_TAB: name = "Tab"; break;
+ case K_UNTAB: name = "Untab"; break;
+ case K_UP: name = "Up"; break;
+ case K_BKSPC: name = "Backspace"; break;
+ case K_DEL: name = "Del"; break;
+ default:
+ if (key >= SL_KEY_F(1) && key <= SL_KEY_F(63))
+ scnprintf(bf, size, "F%d", key - SL_KEY_F(0));
+ else
+ scnprintf(bf, size, "Unknown (%d)", key);
+ }
+
+ if (name)
+ scnprintf(bf, size, "%s", name);
+ }
+
+ return bf;
+}
diff --git a/tools/perf/ui/keysyms.h b/tools/perf/ui/keysyms.h
index 65092d576b4e..969060edc362 100644
--- a/tools/perf/ui/keysyms.h
+++ b/tools/perf/ui/keysyms.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_KEYSYMS_H_
#define _PERF_KEYSYMS_H_ 1
@@ -24,5 +25,8 @@
#define K_ERROR -2
#define K_RESIZE -3
#define K_SWITCH_INPUT_DATA -4
+#define K_RELOAD -5
+
+const char *key_name(int key, char *bf, size_t size);
#endif /* _PERF_KEYSYMS_H_ */
diff --git a/tools/perf/ui/libslang.h b/tools/perf/ui/libslang.h
index 4d54b6450f5b..6722561e0458 100644
--- a/tools/perf/ui/libslang.h
+++ b/tools/perf/ui/libslang.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_SLANG_H_
#define _PERF_UI_SLANG_H_ 1
/*
@@ -9,20 +10,12 @@
#ifndef HAVE_LONG_LONG
#define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG
#endif
-#include <slang.h>
-#if SLANG_VERSION < 20104
-#define slsmg_printf(msg, args...) \
- SLsmg_printf((char *)(msg), ##args)
-#define slsmg_write_nstring(msg, len) \
- SLsmg_write_nstring((char *)(msg), len)
-#define sltt_set_color(obj, name, fg, bg) \
- SLtt_set_color(obj,(char *)(name), (char *)(fg), (char *)(bg))
-#else
-#define slsmg_printf SLsmg_printf
-#define slsmg_write_nstring SLsmg_write_nstring
-#define sltt_set_color SLtt_set_color
-#endif
+/* Enable future slang's corrected function prototypes. */
+#define ENABLE_SLFUTURE_CONST 1
+#define ENABLE_SLFUTURE_VOID 1
+
+#include <slang.h>
#define SL_KEY_UNTAB 0x1000
diff --git a/tools/perf/ui/progress.c b/tools/perf/ui/progress.c
index 3ec695607a4d..99d60223c74b 100644
--- a/tools/perf/ui/progress.c
+++ b/tools/perf/ui/progress.c
@@ -1,26 +1,47 @@
-#include "../cache.h"
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
#include "progress.h"
-static void nop_progress_update(u64 curr __maybe_unused,
- u64 total __maybe_unused,
- const char *title __maybe_unused)
+static void null_progress__update(struct ui_progress *p __maybe_unused)
{
}
-static struct ui_progress default_progress_fns =
+static struct ui_progress_ops null_progress__ops =
{
- .update = nop_progress_update,
+ .update = null_progress__update,
};
-struct ui_progress *progress_fns = &default_progress_fns;
+struct ui_progress_ops *ui_progress__ops = &null_progress__ops;
-void ui_progress__update(u64 curr, u64 total, const char *title)
+void ui_progress__update(struct ui_progress *p, u64 adv)
{
- return progress_fns->update(curr, total, title);
+ u64 last = p->curr;
+
+ p->curr += adv;
+
+ if (p->curr >= p->next) {
+ u64 nr = DIV_ROUND_UP(p->curr - last, p->step);
+
+ p->next += nr * p->step;
+ ui_progress__ops->update(p);
+ }
+}
+
+void __ui_progress__init(struct ui_progress *p, u64 total,
+ const char *title, bool size)
+{
+ p->curr = 0;
+ p->next = p->step = total / 16 ?: 1;
+ p->total = total;
+ p->title = title;
+ p->size = size;
+
+ if (ui_progress__ops->init)
+ ui_progress__ops->init(p);
}
void ui_progress__finish(void)
{
- if (progress_fns->finish)
- progress_fns->finish();
+ if (ui_progress__ops->finish)
+ ui_progress__ops->finish();
}
diff --git a/tools/perf/ui/progress.h b/tools/perf/ui/progress.h
index 257cc224f9cf..4f52c37b2f09 100644
--- a/tools/perf/ui/progress.h
+++ b/tools/perf/ui/progress.h
@@ -1,18 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_PROGRESS_H_
#define _PERF_UI_PROGRESS_H_ 1
-#include <../types.h>
+#include <linux/types.h>
+
+void ui_progress__finish(void);
struct ui_progress {
- void (*update)(u64, u64, const char *);
- void (*finish)(void);
+ const char *title;
+ u64 curr, next, step, total;
+ bool size;
};
-extern struct ui_progress *progress_fns;
+void __ui_progress__init(struct ui_progress *p, u64 total,
+ const char *title, bool size);
-void ui_progress__init(void);
+#define ui_progress__init(p, total, title) \
+ __ui_progress__init(p, total, title, false)
-void ui_progress__update(u64 curr, u64 total, const char *title);
-void ui_progress__finish(void);
+#define ui_progress__init_size(p, total, title) \
+ __ui_progress__init(p, total, title, true)
+
+void ui_progress__update(struct ui_progress *p, u64 adv);
+
+struct ui_progress_ops {
+ void (*init)(struct ui_progress *p);
+ void (*update)(struct ui_progress *p);
+ void (*finish)(void);
+};
+
+extern struct ui_progress_ops *ui_progress__ops;
#endif
diff --git a/tools/perf/ui/setup.c b/tools/perf/ui/setup.c
index ae6a789cb0f6..ff800047e697 100644
--- a/tools/perf/ui/setup.c
+++ b/tools/perf/ui/setup.c
@@ -1,13 +1,82 @@
-#include <pthread.h>
+// SPDX-License-Identifier: GPL-2.0
+#include <dlfcn.h>
+#include <signal.h>
+#include <unistd.h>
-#include "../util/cache.h"
+#include <subcmd/pager.h>
#include "../util/debug.h"
#include "../util/hist.h"
+#include "ui.h"
-pthread_mutex_t ui__lock = PTHREAD_MUTEX_INITIALIZER;
+struct mutex ui__lock;
+void *perf_gtk_handle;
+int use_browser = -1;
+
+#define PERF_GTK_DSO "libperf-gtk.so"
+
+#ifdef HAVE_GTK2_SUPPORT
+
+static int setup_gtk_browser(void)
+{
+ int (*perf_ui_init)(void);
+
+ if (perf_gtk_handle)
+ return 0;
+
+ perf_gtk_handle = dlopen(PERF_GTK_DSO, RTLD_LAZY);
+ if (perf_gtk_handle == NULL) {
+ char buf[PATH_MAX];
+ scnprintf(buf, sizeof(buf), "%s/%s", LIBDIR, PERF_GTK_DSO);
+ perf_gtk_handle = dlopen(buf, RTLD_LAZY);
+ }
+ if (perf_gtk_handle == NULL)
+ return -1;
+
+ perf_ui_init = dlsym(perf_gtk_handle, "perf_gtk__init");
+ if (perf_ui_init == NULL)
+ goto out_close;
+
+ if (perf_ui_init() == 0)
+ return 0;
+
+out_close:
+ dlclose(perf_gtk_handle);
+ return -1;
+}
+
+static void exit_gtk_browser(bool wait_for_ok)
+{
+ void (*perf_ui_exit)(bool);
+
+ if (perf_gtk_handle == NULL)
+ return;
+
+ perf_ui_exit = dlsym(perf_gtk_handle, "perf_gtk__exit");
+ if (perf_ui_exit == NULL)
+ goto out_close;
+
+ perf_ui_exit(wait_for_ok);
+
+out_close:
+ dlclose(perf_gtk_handle);
+
+ perf_gtk_handle = NULL;
+}
+#else
+static inline int setup_gtk_browser(void) { return -1; }
+static inline void exit_gtk_browser(bool wait_for_ok __maybe_unused) {}
+#endif
+
+int stdio__config_color(const struct option *opt __maybe_unused,
+ const char *mode, int unset __maybe_unused)
+{
+ perf_use_color_default = perf_config_colorbool("color.ui", mode, -1);
+ return 0;
+}
void setup_browser(bool fallback_to_pager)
{
+ mutex_init(&ui__lock);
if (use_browser < 2 && (!isatty(1) || dump_trace))
use_browser = 0;
@@ -17,11 +86,14 @@ void setup_browser(bool fallback_to_pager)
switch (use_browser) {
case 2:
- if (perf_gtk__init() == 0)
+ if (setup_gtk_browser() == 0)
break;
+ printf("GTK browser requested but could not find %s\n",
+ PERF_GTK_DSO);
+ sleep(1);
+ use_browser = 1;
/* fall through */
case 1:
- use_browser = 1;
if (ui__init() == 0)
break;
/* fall through */
@@ -29,9 +101,6 @@ void setup_browser(bool fallback_to_pager)
use_browser = 0;
if (fallback_to_pager)
setup_pager();
-
- perf_hpp__column_enable(PERF_HPP__OVERHEAD);
- perf_hpp__init();
break;
}
}
@@ -40,7 +109,7 @@ void exit_browser(bool wait_for_ok)
{
switch (use_browser) {
case 2:
- perf_gtk__exit(wait_for_ok);
+ exit_gtk_browser(wait_for_ok);
break;
case 1:
@@ -50,4 +119,23 @@ void exit_browser(bool wait_for_ok)
default:
break;
}
+ mutex_destroy(&ui__lock);
+}
+
+void pthread__block_sigwinch(void)
+{
+ sigset_t set;
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGWINCH);
+ pthread_sigmask(SIG_BLOCK, &set, NULL);
+}
+
+void pthread__unblock_sigwinch(void)
+{
+ sigset_t set;
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGWINCH);
+ pthread_sigmask(SIG_UNBLOCK, &set, NULL);
}
diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c
index ae7a75432249..8c4c8925df2c 100644
--- a/tools/perf/ui/stdio/hist.c
+++ b/tools/perf/ui/stdio/hist.c
@@ -1,16 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <limits.h>
#include <stdio.h>
+#include <stdlib.h>
+#include <linux/string.h>
-#include "../../util/util.h"
+#include "../../util/callchain.h"
+#include "../../util/debug.h"
+#include "../../util/event.h"
#include "../../util/hist.h"
+#include "../../util/map.h"
+#include "../../util/maps.h"
+#include "../../util/symbol.h"
#include "../../util/sort.h"
#include "../../util/evsel.h"
-
+#include "../../util/srcline.h"
+#include "../../util/string2.h"
+#include "../../util/thread.h"
+#include "../../util/block-info.h"
+#include <linux/ctype.h>
+#include <linux/zalloc.h>
static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin)
{
int i;
int ret = fprintf(fp, " ");
+ if (left_margin > USHRT_MAX)
+ left_margin = USHRT_MAX;
+
for (i = 0; i < left_margin; i++)
ret += fprintf(fp, " ");
@@ -34,13 +51,16 @@ static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask,
return ret;
}
-static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain,
+static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_node *node,
+ struct callchain_list *chain,
int depth, int depth_mask, int period,
- u64 total_samples, u64 hits,
- int left_margin)
+ u64 total_samples, int left_margin)
{
int i;
size_t ret = 0;
+ char bf[1024], *alloc_str = NULL;
+ char buf[64];
+ const char *str;
ret += callchain__fprintf_left_margin(fp, left_margin);
for (i = 0; i < depth; i++) {
@@ -49,17 +69,28 @@ static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain,
else
ret += fprintf(fp, " ");
if (!period && i == depth - 1) {
- double percent;
-
- percent = hits * 100.0 / total_samples;
- ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent);
+ ret += fprintf(fp, "--");
+ ret += callchain_node__fprintf_value(node, fp, total_samples);
+ ret += fprintf(fp, "--");
} else
ret += fprintf(fp, "%s", " ");
}
- if (chain->ms.sym)
- ret += fprintf(fp, "%s\n", chain->ms.sym->name);
- else
- ret += fprintf(fp, "0x%0" PRIx64 "\n", chain->ip);
+
+ str = callchain_list__sym_name(chain, bf, sizeof(bf), false);
+
+ if (symbol_conf.show_branchflag_count) {
+ callchain_list_counts__printf_value(chain, NULL,
+ buf, sizeof(buf));
+
+ if (asprintf(&alloc_str, "%s%s", str, buf) < 0)
+ str = "Not enough memory!";
+ else
+ str = alloc_str;
+ }
+
+ fputs(str, fp);
+ fputc('\n', fp);
+ free(alloc_str);
return ret;
}
@@ -84,13 +115,14 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root,
int depth_mask, int left_margin)
{
struct rb_node *node, *next;
- struct callchain_node *child;
+ struct callchain_node *child = NULL;
struct callchain_list *chain;
int new_depth_mask = depth_mask;
u64 remaining;
size_t ret = 0;
int i;
uint entries_printed = 0;
+ int cumul_count = 0;
remaining = total_samples;
@@ -102,6 +134,7 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root,
child = rb_entry(node, struct callchain_node, rb_node);
cumul = callchain_cumul_hits(child);
remaining -= cumul;
+ cumul_count += callchain_cumul_counts(child);
/*
* The depth mask manages the output of pipes that show
@@ -122,10 +155,9 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root,
left_margin);
i = 0;
list_for_each_entry(chain, &child->val, list) {
- ret += ipchain__fprintf_graph(fp, chain, depth,
+ ret += ipchain__fprintf_graph(fp, child, chain, depth,
new_depth_mask, i++,
total_samples,
- cumul,
left_margin);
}
@@ -145,21 +177,50 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root,
if (callchain_param.mode == CHAIN_GRAPH_REL &&
remaining && remaining != total_samples) {
+ struct callchain_node rem_node = {
+ .hit = remaining,
+ };
if (!rem_sq_bracket)
return ret;
+ if (callchain_param.value == CCVAL_COUNT && child && child->parent) {
+ rem_node.count = child->parent->children_count - cumul_count;
+ if (rem_node.count <= 0)
+ return ret;
+ }
+
new_depth_mask &= ~(1 << (depth - 1));
- ret += ipchain__fprintf_graph(fp, &rem_hits, depth,
+ ret += ipchain__fprintf_graph(fp, &rem_node, &rem_hits, depth,
new_depth_mask, 0, total_samples,
- remaining, left_margin);
+ left_margin);
}
return ret;
}
+/*
+ * If have one single callchain root, don't bother printing
+ * its percentage (100 % in fractal mode and the same percentage
+ * than the hist in graph mode). This also avoid one level of column.
+ *
+ * However when percent-limit applied, it's possible that single callchain
+ * node have different (non-100% in fractal mode) percentage.
+ */
+static bool need_percent_display(struct rb_node *node, u64 parent_samples)
+{
+ struct callchain_node *cnode;
+
+ if (rb_next(node))
+ return true;
+
+ cnode = rb_entry(node, struct callchain_node, rb_node);
+ return callchain_cumul_hits(cnode) != parent_samples;
+}
+
static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root,
- u64 total_samples, int left_margin)
+ u64 total_samples, u64 parent_samples,
+ int left_margin)
{
struct callchain_node *cnode;
struct callchain_list *chain;
@@ -168,14 +229,10 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root,
struct rb_node *node;
int i = 0;
int ret = 0;
+ char bf[1024];
- /*
- * If have one single callchain root, don't bother printing
- * its percentage (100 % in fractal mode and the same percentage
- * than the hist in graph mode). This also avoid one level of column.
- */
node = rb_first(root);
- if (node && !rb_next(node)) {
+ if (node && !need_percent_display(node, parent_samples)) {
cnode = rb_entry(node, struct callchain_node, rb_node);
list_for_each_entry(chain, &cnode->val, list) {
/*
@@ -183,8 +240,10 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root,
* the symbol. No need to print it otherwise it appears as
* displayed twice.
*/
- if (!i++ && sort__first_dimension == SORT_SYM)
+ if (!i++ && field_order == NULL &&
+ sort_order && strstarts(sort_order, "sym"))
continue;
+
if (!printed) {
ret += callchain__fprintf_left_margin(fp, left_margin);
ret += fprintf(fp, "|\n");
@@ -195,10 +254,15 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root,
} else
ret += callchain__fprintf_left_margin(fp, left_margin);
- if (chain->ms.sym)
- ret += fprintf(fp, " %s\n", chain->ms.sym->name);
- else
- ret += fprintf(fp, " %p\n", (void *)(long)chain->ip);
+ ret += fprintf(fp, "%s",
+ callchain_list__sym_name(chain, bf,
+ sizeof(bf),
+ false));
+
+ if (symbol_conf.show_branchflag_count)
+ ret += callchain_list_counts__printf_value(
+ chain, fp, NULL, 0);
+ ret += fprintf(fp, "\n");
if (++entries_printed == callchain_param.print_limit)
break;
@@ -206,55 +270,56 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root,
root = &cnode->rb_root;
}
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ total_samples = parent_samples;
+
ret += __callchain__fprintf_graph(fp, root, total_samples,
1, 1, left_margin);
- ret += fprintf(fp, "\n");
+ if (ret) {
+ /* do not add a blank line if it printed nothing */
+ ret += fprintf(fp, "\n");
+ }
return ret;
}
-static size_t __callchain__fprintf_flat(FILE *fp,
- struct callchain_node *self,
+static size_t __callchain__fprintf_flat(FILE *fp, struct callchain_node *node,
u64 total_samples)
{
struct callchain_list *chain;
size_t ret = 0;
+ char bf[1024];
- if (!self)
+ if (!node)
return 0;
- ret += __callchain__fprintf_flat(fp, self->parent, total_samples);
+ ret += __callchain__fprintf_flat(fp, node->parent, total_samples);
- list_for_each_entry(chain, &self->val, list) {
+ list_for_each_entry(chain, &node->val, list) {
if (chain->ip >= PERF_CONTEXT_MAX)
continue;
- if (chain->ms.sym)
- ret += fprintf(fp, " %s\n", chain->ms.sym->name);
- else
- ret += fprintf(fp, " %p\n",
- (void *)(long)chain->ip);
+ ret += fprintf(fp, " %s\n", callchain_list__sym_name(chain,
+ bf, sizeof(bf), false));
}
return ret;
}
-static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *self,
+static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *tree,
u64 total_samples)
{
size_t ret = 0;
u32 entries_printed = 0;
- struct rb_node *rb_node;
struct callchain_node *chain;
+ struct rb_node *rb_node = rb_first(tree);
- rb_node = rb_first(self);
while (rb_node) {
- double percent;
-
chain = rb_entry(rb_node, struct callchain_node, rb_node);
- percent = chain->hit * 100.0 / total_samples;
- ret = percent_color_fprintf(fp, " %6.2f%%\n", percent);
+ ret += fprintf(fp, " ");
+ ret += callchain_node__fprintf_value(chain, fp, total_samples);
+ ret += fprintf(fp, "\n");
ret += __callchain__fprintf_flat(fp, chain, total_samples);
ret += fprintf(fp, "\n");
if (++entries_printed == callchain_param.print_limit)
@@ -266,22 +331,81 @@ static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *self,
return ret;
}
+static size_t __callchain__fprintf_folded(FILE *fp, struct callchain_node *node)
+{
+ const char *sep = symbol_conf.field_sep ?: ";";
+ struct callchain_list *chain;
+ size_t ret = 0;
+ char bf[1024];
+ bool first;
+
+ if (!node)
+ return 0;
+
+ ret += __callchain__fprintf_folded(fp, node->parent);
+
+ first = (ret == 0);
+ list_for_each_entry(chain, &node->val, list) {
+ if (chain->ip >= PERF_CONTEXT_MAX)
+ continue;
+ ret += fprintf(fp, "%s%s", first ? "" : sep,
+ callchain_list__sym_name(chain,
+ bf, sizeof(bf), false));
+ first = false;
+ }
+
+ return ret;
+}
+
+static size_t callchain__fprintf_folded(FILE *fp, struct rb_root *tree,
+ u64 total_samples)
+{
+ size_t ret = 0;
+ u32 entries_printed = 0;
+ struct callchain_node *chain;
+ struct rb_node *rb_node = rb_first(tree);
+
+ while (rb_node) {
+
+ chain = rb_entry(rb_node, struct callchain_node, rb_node);
+
+ ret += callchain_node__fprintf_value(chain, fp, total_samples);
+ ret += fprintf(fp, " ");
+ ret += __callchain__fprintf_folded(fp, chain);
+ ret += fprintf(fp, "\n");
+ if (++entries_printed == callchain_param.print_limit)
+ break;
+
+ rb_node = rb_next(rb_node);
+ }
+
+ return ret;
+}
+
static size_t hist_entry_callchain__fprintf(struct hist_entry *he,
u64 total_samples, int left_margin,
FILE *fp)
{
+ u64 parent_samples = he->stat.period;
+
+ if (symbol_conf.cumulate_callchain)
+ parent_samples = he->stat_acc->period;
+
switch (callchain_param.mode) {
case CHAIN_GRAPH_REL:
- return callchain__fprintf_graph(fp, &he->sorted_chain, he->stat.period,
- left_margin);
+ return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples,
+ parent_samples, left_margin);
break;
case CHAIN_GRAPH_ABS:
return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples,
- left_margin);
+ parent_samples, left_margin);
break;
case CHAIN_FLAT:
return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples);
break;
+ case CHAIN_FOLDED:
+ return callchain__fprintf_folded(fp, &he->sorted_chain, total_samples);
+ break;
case CHAIN_NONE:
break;
default:
@@ -291,177 +415,501 @@ static size_t hist_entry_callchain__fprintf(struct hist_entry *he,
return 0;
}
-static size_t hist_entry__callchain_fprintf(struct hist_entry *he,
- struct hists *hists,
- FILE *fp)
+int __hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp,
+ struct perf_hpp_list *hpp_list)
{
- int left_margin = 0;
- u64 total_period = hists->stats.total_period;
+ const char *sep = symbol_conf.field_sep;
+ struct perf_hpp_fmt *fmt;
+ char *start = hpp->buf;
+ int ret;
+ bool first = true;
+
+ if (symbol_conf.exclude_other && !he->parent)
+ return 0;
+
+ perf_hpp_list__for_each_format(hpp_list, fmt) {
+ if (perf_hpp__should_skip(fmt, he->hists))
+ continue;
+
+ /*
+ * If there's no field_sep, we still need
+ * to display initial ' '.
+ */
+ if (!sep || !first) {
+ ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " ");
+ advance_hpp(hpp, ret);
+ } else
+ first = false;
+
+ if (perf_hpp__use_color() && fmt->color)
+ ret = fmt->color(fmt, hpp, he);
+ else
+ ret = fmt->entry(fmt, hpp, he);
+
+ ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret);
+ advance_hpp(hpp, ret);
+ }
+
+ return hpp->buf - start;
+}
+
+static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp)
+{
+ return __hist_entry__snprintf(he, hpp, he->hists->hpp_list);
+}
+
+static int hist_entry__hierarchy_fprintf(struct hist_entry *he,
+ struct perf_hpp *hpp,
+ struct hists *hists,
+ FILE *fp)
+{
+ const char *sep = symbol_conf.field_sep;
+ struct perf_hpp_fmt *fmt;
+ struct perf_hpp_list_node *fmt_node;
+ char *buf = hpp->buf;
+ size_t size = hpp->size;
+ int ret, printed = 0;
+ bool first = true;
+
+ if (symbol_conf.exclude_other && !he->parent)
+ return 0;
+
+ ret = scnprintf(hpp->buf, hpp->size, "%*s", he->depth * HIERARCHY_INDENT, "");
+ advance_hpp(hpp, ret);
+
+ /* 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 there's no field_sep, we still need
+ * to display initial ' '.
+ */
+ if (!sep || !first) {
+ ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " ");
+ advance_hpp(hpp, ret);
+ } else
+ first = false;
+
+ if (perf_hpp__use_color() && fmt->color)
+ ret = fmt->color(fmt, hpp, he);
+ else
+ ret = fmt->entry(fmt, hpp, he);
+
+ ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret);
+ advance_hpp(hpp, ret);
+ }
+
+ if (!sep)
+ ret = scnprintf(hpp->buf, hpp->size, "%*s",
+ (hists->nr_hpp_node - 2) * HIERARCHY_INDENT, "");
+ advance_hpp(hpp, ret);
+
+ printed += fprintf(fp, "%s", buf);
+
+ perf_hpp_list__for_each_format(he->hpp_list, fmt) {
+ hpp->buf = buf;
+ hpp->size = size;
+
+ /*
+ * No need to call hist_entry__snprintf_alignment() since this
+ * fmt is always the last column in the hierarchy mode.
+ */
+ if (perf_hpp__use_color() && fmt->color)
+ fmt->color(fmt, hpp, he);
+ else
+ fmt->entry(fmt, hpp, he);
+
+ /*
+ * dynamic entries are right-aligned but we want left-aligned
+ * in the hierarchy mode
+ */
+ printed += fprintf(fp, "%s%s", sep ?: " ", skip_spaces(buf));
+ }
+ printed += putc('\n', fp);
+
+ if (he->leaf && hist_entry__has_callchains(he) && symbol_conf.use_callchain) {
+ u64 total = hists__total_period(hists);
- if (sort__first_dimension == SORT_COMM) {
- struct sort_entry *se = list_first_entry(&hist_entry__sort_list,
- typeof(*se), list);
- left_margin = hists__col_len(hists, se->se_width_idx);
- left_margin -= thread__comm_len(he->thread);
+ printed += hist_entry_callchain__fprintf(he, total, 0, fp);
+ goto out;
}
- return hist_entry_callchain__fprintf(he, total_period, left_margin, fp);
+out:
+ return printed;
+}
+
+static int hist_entry__block_fprintf(struct hist_entry *he,
+ char *bf, size_t size,
+ FILE *fp)
+{
+ struct block_hist *bh = container_of(he, struct block_hist, he);
+ int ret = 0;
+
+ for (unsigned int i = 0; i < bh->block_hists.nr_entries; i++) {
+ struct perf_hpp hpp = {
+ .buf = bf,
+ .size = size,
+ .skip = false,
+ };
+
+ bh->block_idx = i;
+ hist_entry__snprintf(he, &hpp);
+
+ if (!hpp.skip)
+ ret += fprintf(fp, "%s\n", bf);
+ }
+
+ return ret;
+}
+
+static int hist_entry__individual_block_fprintf(struct hist_entry *he,
+ char *bf, size_t size,
+ FILE *fp)
+{
+ int ret = 0;
+
+ struct perf_hpp hpp = {
+ .buf = bf,
+ .size = size,
+ .skip = false,
+ };
+
+ hist_entry__snprintf(he, &hpp);
+ if (!hpp.skip)
+ ret += fprintf(fp, "%s\n", bf);
+
+ return ret;
}
static int hist_entry__fprintf(struct hist_entry *he, size_t size,
- struct hists *hists, FILE *fp)
+ char *bf, size_t bfsz, FILE *fp,
+ bool ignore_callchains)
{
- char bf[512];
int ret;
+ int callchain_ret = 0;
struct perf_hpp hpp = {
.buf = bf,
.size = size,
};
- bool color = !symbol_conf.field_sep;
+ struct hists *hists = he->hists;
+ u64 total_period = hists->stats.total_period;
+
+ if (size == 0 || size > bfsz)
+ size = hpp.size = bfsz;
- if (size == 0 || size > sizeof(bf))
- size = hpp.size = sizeof(bf);
+ if (symbol_conf.report_hierarchy)
+ return hist_entry__hierarchy_fprintf(he, &hpp, hists, fp);
- ret = hist_entry__period_snprintf(&hpp, he, color);
- hist_entry__sort_snprintf(he, bf + ret, size - ret, hists);
+ if (symbol_conf.report_block)
+ return hist_entry__block_fprintf(he, bf, size, fp);
+
+ if (symbol_conf.report_individual_block)
+ return hist_entry__individual_block_fprintf(he, bf, size, fp);
+
+ hist_entry__snprintf(he, &hpp);
ret = fprintf(fp, "%s\n", bf);
- if (symbol_conf.use_callchain)
- ret += hist_entry__callchain_fprintf(he, hists, fp);
+ if (hist_entry__has_callchains(he) && !ignore_callchains)
+ callchain_ret = hist_entry_callchain__fprintf(he, total_period,
+ 0, fp);
+
+ ret += callchain_ret;
return ret;
}
-size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
- int max_cols, float min_pcnt, FILE *fp)
+static int print_hierarchy_indent(const char *sep, int indent,
+ const char *line, FILE *fp)
+{
+ int width;
+
+ if (sep != NULL || indent < 2)
+ return 0;
+
+ width = (indent - 2) * HIERARCHY_INDENT;
+
+ return fprintf(fp, "%-*.*s", width, width, line);
+}
+
+static int hists__fprintf_hierarchy_headers(struct hists *hists,
+ struct perf_hpp *hpp, FILE *fp)
{
+ bool first_node, first_col;
+ int indent;
+ int depth;
+ unsigned width = 0;
+ unsigned header_width = 0;
struct perf_hpp_fmt *fmt;
- struct sort_entry *se;
- struct rb_node *nd;
- size_t ret = 0;
- unsigned int width;
+ struct perf_hpp_list_node *fmt_node;
+ struct perf_hpp_list *hpp_list = hists->hpp_list;
const char *sep = symbol_conf.field_sep;
- const char *col_width = symbol_conf.col_width_list_str;
- int nr_rows = 0;
- char bf[96];
- struct perf_hpp dummy_hpp = {
- .buf = bf,
- .size = sizeof(bf),
- .ptr = hists_to_evsel(hists),
- };
- bool first = true;
- init_rem_hits();
+ indent = hists->nr_hpp_node;
+
+ /* the first hpp_list_node is for overhead columns */
+ fmt_node = list_first_entry(&hists->hpp_formats,
+ struct perf_hpp_list_node, list);
+
+ for (int line = 0; line < hpp_list->nr_header_lines; line++) {
+ /* first # is displayed one level up */
+ if (line)
+ fprintf(fp, "# ");
+
+ /* preserve max indent depth for column headers */
+ print_hierarchy_indent(sep, indent, " ", fp);
+
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
+ fmt->header(fmt, hpp, hists, line, NULL);
+ fprintf(fp, "%s%s", hpp->buf, sep ?: " ");
+ }
+
+ if (line < hpp_list->nr_header_lines - 1)
+ goto next_line;
+
+ /* combine sort headers with ' / ' */
+ first_node = true;
+ list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
+ if (!first_node)
+ header_width += fprintf(fp, " / ");
+ first_node = false;
+
+ first_col = true;
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
+ if (perf_hpp__should_skip(fmt, hists))
+ continue;
+
+ if (!first_col)
+ header_width += fprintf(fp, "+");
+ first_col = false;
- if (!show_header)
- goto print_entries;
+ fmt->header(fmt, hpp, hists, line, NULL);
+
+ header_width += fprintf(fp, "%s", strim(hpp->buf));
+ }
+ }
+
+next_line:
+ fprintf(fp, "\n");
+ }
fprintf(fp, "# ");
- perf_hpp__for_each_format(fmt) {
- if (!first)
+ /* preserve max indent depth for initial dots */
+ print_hierarchy_indent(sep, indent, dots, fp);
+
+ /* the first hpp_list_node is for overhead columns */
+ fmt_node = list_first_entry(&hists->hpp_formats,
+ struct perf_hpp_list_node, list);
+
+ first_col = true;
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
+ if (!first_col)
+ fprintf(fp, "%s", sep ?: "..");
+ first_col = false;
+
+ width = fmt->width(fmt, hpp, hists);
+ fprintf(fp, "%.*s", width, dots);
+ }
+
+ depth = 0;
+ list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
+ first_col = true;
+ width = depth * HIERARCHY_INDENT;
+
+ perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
+ if (perf_hpp__should_skip(fmt, hists))
+ continue;
+
+ if (!first_col)
+ width++; /* for '+' sign between column header */
+ first_col = false;
+
+ width += fmt->width(fmt, hpp, hists);
+ }
+
+ if (width > header_width)
+ header_width = width;
+
+ depth++;
+ }
+
+ fprintf(fp, "%s%-.*s", sep ?: " ", header_width, dots);
+
+ fprintf(fp, "\n#\n");
+
+ return 2;
+}
+
+static void fprintf_line(struct hists *hists, struct perf_hpp *hpp,
+ int line, FILE *fp)
+{
+ struct perf_hpp_fmt *fmt;
+ const char *sep = symbol_conf.field_sep;
+ bool first = true;
+ int span = 0;
+
+ hists__for_each_format(hists, fmt) {
+ if (perf_hpp__should_skip(fmt, hists))
+ continue;
+
+ if (!first && !span)
fprintf(fp, "%s", sep ?: " ");
else
first = false;
- fmt->header(&dummy_hpp);
- fprintf(fp, "%s", bf);
- }
+ fmt->header(fmt, hpp, hists, line, &span);
- list_for_each_entry(se, &hist_entry__sort_list, list) {
- if (se->elide)
- continue;
- if (sep) {
- fprintf(fp, "%c%s", *sep, se->se_header);
- continue;
- }
- width = strlen(se->se_header);
- if (symbol_conf.col_width_list_str) {
- if (col_width) {
- hists__set_col_len(hists, se->se_width_idx,
- atoi(col_width));
- col_width = strchr(col_width, ',');
- if (col_width)
- ++col_width;
- }
- }
- if (!hists__new_col_len(hists, se->se_width_idx, width))
- width = hists__col_len(hists, se->se_width_idx);
- fprintf(fp, " %*s", width, se->se_header);
+ if (!span)
+ fprintf(fp, "%s", hpp->buf);
}
+}
- fprintf(fp, "\n");
- if (max_rows && ++nr_rows >= max_rows)
- goto out;
+static int
+hists__fprintf_standard_headers(struct hists *hists,
+ struct perf_hpp *hpp,
+ FILE *fp)
+{
+ struct perf_hpp_list *hpp_list = hists->hpp_list;
+ struct perf_hpp_fmt *fmt;
+ unsigned int width;
+ const char *sep = symbol_conf.field_sep;
+ bool first = true;
+ int line;
+
+ for (line = 0; line < hpp_list->nr_header_lines; line++) {
+ /* first # is displayed one level up */
+ if (line)
+ fprintf(fp, "# ");
+ fprintf_line(hists, hpp, line, fp);
+ fprintf(fp, "\n");
+ }
if (sep)
- goto print_entries;
+ return hpp_list->nr_header_lines;
first = true;
fprintf(fp, "# ");
- perf_hpp__for_each_format(fmt) {
+ hists__for_each_format(hists, fmt) {
unsigned int i;
+ if (perf_hpp__should_skip(fmt, hists))
+ continue;
+
if (!first)
fprintf(fp, "%s", sep ?: " ");
else
first = false;
- width = fmt->width(&dummy_hpp);
+ width = fmt->width(fmt, hpp, hists);
for (i = 0; i < width; i++)
fprintf(fp, ".");
}
- list_for_each_entry(se, &hist_entry__sort_list, list) {
- unsigned int i;
+ fprintf(fp, "\n");
+ fprintf(fp, "#\n");
+ return hpp_list->nr_header_lines + 2;
+}
- if (se->elide)
- continue;
+int hists__fprintf_headers(struct hists *hists, FILE *fp)
+{
+ char bf[1024];
+ struct perf_hpp dummy_hpp = {
+ .buf = bf,
+ .size = sizeof(bf),
+ };
- fprintf(fp, " ");
- width = hists__col_len(hists, se->se_width_idx);
- if (width == 0)
- width = strlen(se->se_header);
- for (i = 0; i < width; i++)
- fprintf(fp, ".");
- }
+ fprintf(fp, "# ");
- fprintf(fp, "\n");
- if (max_rows && ++nr_rows >= max_rows)
+ if (symbol_conf.report_hierarchy)
+ return hists__fprintf_hierarchy_headers(hists, &dummy_hpp, fp);
+ else
+ return hists__fprintf_standard_headers(hists, &dummy_hpp, fp);
+
+}
+
+size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
+ int max_cols, float min_pcnt, FILE *fp,
+ bool ignore_callchains)
+{
+ struct rb_node *nd;
+ size_t ret = 0;
+ const char *sep = symbol_conf.field_sep;
+ int nr_rows = 0;
+ size_t linesz;
+ char *line = NULL;
+ unsigned indent;
+
+ init_rem_hits();
+
+ hists__reset_column_width(hists);
+
+ if (symbol_conf.col_width_list_str)
+ perf_hpp__set_user_width(symbol_conf.col_width_list_str);
+
+ if (show_header)
+ nr_rows += hists__fprintf_headers(hists, fp);
+
+ if (max_rows && nr_rows >= max_rows)
goto out;
- fprintf(fp, "#\n");
- if (max_rows && ++nr_rows >= max_rows)
+ linesz = hists__sort_list_width(hists) + 3 + 1;
+ linesz += perf_hpp__color_overhead();
+ line = malloc(linesz);
+ if (line == NULL) {
+ ret = -1;
goto out;
+ }
+
+ indent = hists__overhead_width(hists) + 4;
-print_entries:
- for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ for (nd = rb_first_cached(&hists->entries); nd;
+ nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD)) {
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;
if (h->filtered)
continue;
+ if (symbol_conf.report_individual_block)
+ percent = block_info__total_cycles_percent(h);
+ else
+ percent = hist_entry__get_percent_limit(h);
+
if (percent < min_pcnt)
continue;
- ret += hist_entry__fprintf(h, max_cols, hists, fp);
+ ret += hist_entry__fprintf(h, max_cols, line, linesz, fp, ignore_callchains);
if (max_rows && ++nr_rows >= max_rows)
- goto out;
+ break;
+
+ /*
+ * If all children are filtered out or percent-limited,
+ * display "no entry >= x.xx%" message.
+ */
+ if (!h->leaf && !hist_entry__has_hierarchy_children(h, min_pcnt)) {
+ int depth = hists->nr_hpp_node + h->depth + 1;
+
+ print_hierarchy_indent(sep, depth, " ", fp);
+ fprintf(fp, "%*sno entry >= %.2f%%\n", indent, "", min_pcnt);
+
+ if (max_rows && ++nr_rows >= max_rows)
+ break;
+ }
if (h->ms.map == NULL && verbose > 1) {
- __map_groups__fprintf_maps(&h->thread->mg,
- MAP__FUNCTION, verbose, fp);
+ maps__fprintf(thread__maps(h->thread), fp);
fprintf(fp, "%.10s end\n", graph_dotted_line);
}
}
+
+ free(line);
out:
- free(rem_sq_bracket);
+ zfree(&rem_sq_bracket);
return ret;
}
@@ -470,19 +918,25 @@ size_t events_stats__fprintf(struct events_stats *stats, FILE *fp)
{
int i;
size_t ret = 0;
+ u32 total = stats->nr_events[0];
for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) {
const char *name;
- if (stats->nr_events[i] == 0)
- continue;
-
name = perf_event__name(i);
if (!strcmp(name, "UNKNOWN"))
continue;
+ if (symbol_conf.skip_empty && !stats->nr_events[i])
+ continue;
- ret += fprintf(fp, "%16s events: %10d\n", name,
- stats->nr_events[i]);
+ if (i && total) {
+ ret += fprintf(fp, "%20s events: %10d (%4.1f%%)\n",
+ name, stats->nr_events[i],
+ 100.0 * stats->nr_events[i] / total);
+ } else {
+ ret += fprintf(fp, "%20s events: %10d\n",
+ name, stats->nr_events[i]);
+ }
}
return ret;
diff --git a/tools/perf/ui/tui/Build b/tools/perf/ui/tui/Build
new file mode 100644
index 000000000000..2ac058ad1a61
--- /dev/null
+++ b/tools/perf/ui/tui/Build
@@ -0,0 +1,4 @@
+perf-ui-y += setup.o
+perf-ui-y += util.o
+perf-ui-y += helpline.o
+perf-ui-y += progress.o
diff --git a/tools/perf/ui/tui/helpline.c b/tools/perf/ui/tui/helpline.c
index 1c8b9afd5d6e..b39451314f43 100644
--- a/tools/perf/ui/tui/helpline.c
+++ b/tools/perf/ui/tui/helpline.c
@@ -1,14 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <pthread.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
-#include "../../util/debug.h"
#include "../helpline.h"
#include "../ui.h"
#include "../libslang.h"
char ui_helpline__last_msg[1024];
+bool tui_helpline__set;
static void tui_helpline__pop(void)
{
@@ -20,9 +22,9 @@ static void tui_helpline__push(const char *msg)
SLsmg_gotorc(SLtt_Screen_Rows - 1, 0);
SLsmg_set_color(0);
- SLsmg_write_nstring((char *)msg, SLtt_Screen_Cols);
+ SLsmg_write_nstring(msg, SLtt_Screen_Cols);
SLsmg_refresh();
- strncpy(ui_helpline__current, msg, sz)[sz - 1] = '\0';
+ strlcpy(ui_helpline__current, msg, sz);
}
static int tui_helpline__show(const char *format, va_list ap)
@@ -30,17 +32,19 @@ static int tui_helpline__show(const char *format, va_list ap)
int ret;
static int backlog;
- pthread_mutex_lock(&ui__lock);
+ mutex_lock(&ui__lock);
ret = vscnprintf(ui_helpline__last_msg + backlog,
sizeof(ui_helpline__last_msg) - backlog, format, ap);
backlog += ret;
+ tui_helpline__set = true;
+
if (ui_helpline__last_msg[backlog - 1] == '\n') {
ui_helpline__puts(ui_helpline__last_msg);
SLsmg_refresh();
backlog = 0;
}
- pthread_mutex_unlock(&ui__lock);
+ mutex_unlock(&ui__lock);
return ret;
}
diff --git a/tools/perf/ui/tui/progress.c b/tools/perf/ui/tui/progress.c
index 6c2184d53cbf..71b6c8d9474f 100644
--- a/tools/perf/ui/tui/progress.c
+++ b/tools/perf/ui/tui/progress.c
@@ -1,11 +1,33 @@
-#include "../cache.h"
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
#include "../progress.h"
#include "../libslang.h"
#include "../ui.h"
+#include "tui.h"
+#include "units.h"
#include "../browser.h"
-static void tui_progress__update(u64 curr, u64 total, const char *title)
+static void __tui_progress__init(struct ui_progress *p)
{
+ p->next = p->step = p->total / (SLtt_Screen_Cols - 2) ?: 1;
+}
+
+static int get_title(struct ui_progress *p, char *buf, size_t size)
+{
+ char buf_cur[20];
+ char buf_tot[20];
+ int ret;
+
+ ret = unit_number__scnprintf(buf_cur, sizeof(buf_cur), p->curr);
+ ret += unit_number__scnprintf(buf_tot, sizeof(buf_tot), p->total);
+
+ return ret + scnprintf(buf, size, "%s [%s/%s]",
+ p->title, buf_cur, buf_tot);
+}
+
+static void tui_progress__update(struct ui_progress *p)
+{
+ char buf[100], *title = (char *) p->title;
int bar, y;
/*
* FIXME: We should have a per UI backend way of showing progress,
@@ -14,29 +36,52 @@ static void tui_progress__update(u64 curr, u64 total, const char *title)
if (use_browser <= 0)
return;
- if (total == 0)
+ if (p->total == 0)
return;
- ui__refresh_dimensions(true);
- pthread_mutex_lock(&ui__lock);
+ if (p->size) {
+ get_title(p, buf, sizeof(buf));
+ title = buf;
+ }
+
+ ui__refresh_dimensions(false);
+ mutex_lock(&ui__lock);
y = SLtt_Screen_Rows / 2 - 2;
SLsmg_set_color(0);
SLsmg_draw_box(y, 0, 3, SLtt_Screen_Cols);
SLsmg_gotorc(y++, 1);
- SLsmg_write_string((char *)title);
+ SLsmg_write_string(title);
+ SLsmg_fill_region(y, 1, 1, SLtt_Screen_Cols - 2, ' ');
SLsmg_set_color(HE_COLORSET_SELECTED);
- bar = ((SLtt_Screen_Cols - 2) * curr) / total;
+ bar = ((SLtt_Screen_Cols - 2) * p->curr) / p->total;
SLsmg_fill_region(y, 1, 1, bar, ' ');
SLsmg_refresh();
- pthread_mutex_unlock(&ui__lock);
+ mutex_unlock(&ui__lock);
}
-static struct ui_progress tui_progress_fns =
+static void tui_progress__finish(void)
{
- .update = tui_progress__update,
+ int y;
+
+ if (use_browser <= 0)
+ return;
+
+ ui__refresh_dimensions(false);
+ mutex_lock(&ui__lock);
+ y = SLtt_Screen_Rows / 2 - 2;
+ SLsmg_set_color(0);
+ SLsmg_fill_region(y, 0, 3, SLtt_Screen_Cols, ' ');
+ SLsmg_refresh();
+ mutex_unlock(&ui__lock);
+}
+
+static struct ui_progress_ops tui_progress__ops = {
+ .init = __tui_progress__init,
+ .update = tui_progress__update,
+ .finish = tui_progress__finish,
};
-void ui_progress__init(void)
+void tui_progress__init(void)
{
- progress_fns = &tui_progress_fns;
+ ui_progress__ops = &tui_progress__ops;
}
diff --git a/tools/perf/ui/tui/setup.c b/tools/perf/ui/tui/setup.c
index b9401482d110..022534eed68c 100644
--- a/tools/perf/ui/tui/setup.c
+++ b/tools/perf/ui/tui/setup.c
@@ -1,7 +1,15 @@
+#include <errno.h>
#include <signal.h>
#include <stdbool.h>
-
-#include "../../util/cache.h"
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+#include <linux/kernel.h>
+#ifdef HAVE_BACKTRACE_SUPPORT
+#include <execinfo.h>
+#endif
+
+#include "../../util/color.h"
#include "../../util/debug.h"
#include "../browser.h"
#include "../helpline.h"
@@ -9,10 +17,12 @@
#include "../util.h"
#include "../libslang.h"
#include "../keysyms.h"
+#include "tui.h"
static volatile int ui__need_resize;
extern struct perf_error_ops perf_tui_eops;
+extern bool tui_helpline__set;
extern void hist_browser__init_hpp(void);
@@ -20,10 +30,10 @@ void ui__refresh_dimensions(bool force)
{
if (force || ui__need_resize) {
ui__need_resize = 0;
- pthread_mutex_lock(&ui__lock);
+ mutex_lock(&ui__lock);
SLtt_get_screen_size();
SLsmg_reinit_smg();
- pthread_mutex_unlock(&ui__lock);
+ mutex_unlock(&ui__lock);
}
}
@@ -87,6 +97,25 @@ int ui__getch(int delay_secs)
return SLkp_getkey();
}
+#ifdef HAVE_BACKTRACE_SUPPORT
+static void ui__signal_backtrace(int sig)
+{
+ void *stackdump[32];
+ size_t size;
+
+ ui__exit(false);
+ psignal(sig, "perf");
+
+ printf("-------- backtrace --------\n");
+ size = backtrace(stackdump, ARRAY_SIZE(stackdump));
+ __dump_stack(stdout, stackdump, size);
+
+ exit(0);
+}
+#else
+# define ui__signal_backtrace ui__signal
+#endif
+
static void ui__signal(int sig)
{
ui__exit(false);
@@ -94,6 +123,23 @@ static void ui__signal(int sig)
exit(0);
}
+static void ui__sigcont(int sig)
+{
+ static struct termios tty;
+
+ if (sig == SIGTSTP) {
+ while (tcgetattr(SLang_TT_Read_FD, &tty) == -1 && errno == EINTR)
+ ;
+ while (write(SLang_TT_Read_FD, PERF_COLOR_RESET, sizeof(PERF_COLOR_RESET) - 1) == -1 && errno == EINTR)
+ ;
+ raise(SIGSTOP);
+ } else {
+ while (tcsetattr(SLang_TT_Read_FD, TCSADRAIN, &tty) == -1 && errno == EINTR)
+ ;
+ raise(SIGWINCH);
+ }
+}
+
int ui__init(void)
{
int err;
@@ -105,9 +151,10 @@ int ui__init(void)
err = SLsmg_init_smg();
if (err < 0)
goto out;
- err = SLang_init_tty(0, 0, 0);
+ err = SLang_init_tty(-1, 0, 0);
if (err < 0)
goto out;
+ SLtty_set_suspend_state(true);
err = SLkp_init();
if (err < 0) {
@@ -115,20 +162,22 @@ int ui__init(void)
goto out;
}
- SLkp_define_keysym((char *)"^(kB)", SL_KEY_UNTAB);
-
- ui_helpline__init();
- ui_browser__init();
- ui_progress__init();
+ SLkp_define_keysym("^(kB)", SL_KEY_UNTAB);
- signal(SIGSEGV, ui__signal);
- signal(SIGFPE, ui__signal);
+ signal(SIGSEGV, ui__signal_backtrace);
+ signal(SIGFPE, ui__signal_backtrace);
signal(SIGINT, ui__signal);
signal(SIGQUIT, ui__signal);
signal(SIGTERM, ui__signal);
+ signal(SIGTSTP, ui__sigcont);
+ signal(SIGCONT, ui__sigcont);
perf_error__register(&perf_tui_eops);
+ ui_helpline__init();
+ ui_browser__init();
+ tui_progress__init();
+
hist_browser__init_hpp();
out:
return err;
@@ -136,15 +185,17 @@ out:
void ui__exit(bool wait_for_ok)
{
- if (wait_for_ok)
+ if (wait_for_ok && tui_helpline__set)
ui__question_window("Fatal Error",
ui_helpline__last_msg,
"Press any key...", 0);
SLtt_set_cursor_visibility(1);
- SLsmg_refresh();
- SLsmg_reset_smg();
+ if (mutex_trylock(&ui__lock)) {
+ SLsmg_refresh();
+ SLsmg_reset_smg();
+ mutex_unlock(&ui__lock);
+ }
SLang_reset_tty();
-
perf_error__unregister(&perf_tui_eops);
}
diff --git a/tools/perf/ui/tui/tui.h b/tools/perf/ui/tui/tui.h
new file mode 100644
index 000000000000..8de06f634c56
--- /dev/null
+++ b/tools/perf/ui/tui/tui.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _PERF_TUI_H_
+#define _PERF_TUI_H_ 1
+
+void tui_progress__init(void);
+
+#endif /* _PERF_TUI_H_ */
diff --git a/tools/perf/ui/tui/util.c b/tools/perf/ui/tui/util.c
index 092902e30cee..e4d322ce0b54 100644
--- a/tools/perf/ui/tui/util.c
+++ b/tools/perf/ui/tui/util.c
@@ -1,11 +1,10 @@
-#include "../../util/util.h"
+// SPDX-License-Identifier: GPL-2.0
#include <signal.h>
#include <stdbool.h>
#include <string.h>
+#include <stdlib.h>
#include <sys/ttydefaults.h>
-#include "../../util/cache.h"
-#include "../../util/debug.h"
#include "../browser.h"
#include "../keysyms.h"
#include "../helpline.h"
@@ -21,10 +20,10 @@ static void ui_browser__argv_write(struct ui_browser *browser,
ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
HE_COLORSET_NORMAL);
- slsmg_write_nstring(*arg, browser->width);
+ ui_browser__write_nstring(browser, *arg, browser->width);
}
-static int popup_menu__run(struct ui_browser *menu)
+static int popup_menu__run(struct ui_browser *menu, int *keyp)
{
int key;
@@ -46,6 +45,11 @@ static int popup_menu__run(struct ui_browser *menu)
key = -1;
break;
default:
+ if (keyp) {
+ *keyp = key;
+ key = menu->nr_entries;
+ break;
+ }
continue;
}
@@ -56,7 +60,7 @@ static int popup_menu__run(struct ui_browser *menu)
return key;
}
-int ui__popup_menu(int argc, char * const argv[])
+int ui__popup_menu(int argc, char * const argv[], int *keyp)
{
struct ui_browser menu = {
.entries = (void *)argv,
@@ -65,8 +69,7 @@ int ui__popup_menu(int argc, char * const argv[])
.write = ui_browser__argv_write,
.nr_entries = argc,
};
-
- return popup_menu__run(&menu);
+ return popup_menu__run(&menu, keyp);
}
int ui_browser__input_window(const char *title, const char *text, char *input,
@@ -92,6 +95,8 @@ int ui_browser__input_window(const char *title, const char *text, char *input,
t = sep + 1;
}
+ mutex_lock(&ui__lock);
+
max_len += 2;
nr_lines += 8;
y = SLtt_Screen_Rows / 2 - nr_lines / 2;
@@ -101,7 +106,7 @@ int ui_browser__input_window(const char *title, const char *text, char *input,
SLsmg_draw_box(y, x++, nr_lines, max_len);
if (title) {
SLsmg_gotorc(y, x + 1);
- SLsmg_write_string((char *)title);
+ SLsmg_write_string(title);
}
SLsmg_gotorc(++y, x);
nr_lines -= 7;
@@ -112,21 +117,27 @@ int ui_browser__input_window(const char *title, const char *text, char *input,
len = 5;
while (len--) {
SLsmg_gotorc(y + len - 1, x);
- SLsmg_write_nstring((char *)" ", max_len);
+ SLsmg_write_nstring(" ", max_len);
}
SLsmg_draw_box(y++, x + 1, 3, max_len - 2);
SLsmg_gotorc(y + 3, x);
- SLsmg_write_nstring((char *)exit_msg, max_len);
+ SLsmg_write_nstring(exit_msg, max_len);
SLsmg_refresh();
+ mutex_unlock(&ui__lock);
+
x += 2;
len = 0;
key = ui__getch(delay_secs);
while (key != K_TIMER && key != K_ENTER && key != K_ESC) {
+ mutex_lock(&ui__lock);
+
if (key == K_BKSPC) {
- if (len == 0)
+ if (len == 0) {
+ mutex_unlock(&ui__lock);
goto next_key;
+ }
SLsmg_gotorc(y, x + --len);
SLsmg_write_char(' ');
} else {
@@ -136,6 +147,8 @@ int ui_browser__input_window(const char *title, const char *text, char *input,
}
SLsmg_refresh();
+ mutex_unlock(&ui__lock);
+
/* XXX more graceful overflow handling needed */
if (len == sizeof(buf) - 1) {
ui_helpline__push("maximum size of symbol name reached!");
@@ -151,8 +164,7 @@ next_key:
return key;
}
-int ui__question_window(const char *title, const char *text,
- const char *exit_msg, int delay_secs)
+void __ui__info_window(const char *title, const char *text, const char *exit_msg)
{
int x, y;
int max_len = 0, nr_lines = 0;
@@ -175,7 +187,9 @@ int ui__question_window(const char *title, const char *text,
}
max_len += 2;
- nr_lines += 4;
+ nr_lines += 2;
+ if (exit_msg)
+ nr_lines += 2;
y = SLtt_Screen_Rows / 2 - nr_lines / 2,
x = SLtt_Screen_Cols / 2 - max_len / 2;
@@ -183,18 +197,37 @@ int ui__question_window(const char *title, const char *text,
SLsmg_draw_box(y, x++, nr_lines, max_len);
if (title) {
SLsmg_gotorc(y, x + 1);
- SLsmg_write_string((char *)title);
+ SLsmg_write_string(title);
}
SLsmg_gotorc(++y, x);
- nr_lines -= 2;
+ if (exit_msg)
+ nr_lines -= 2;
max_len -= 2;
SLsmg_write_wrapped_string((unsigned char *)text, y, x,
nr_lines, max_len, 1);
- SLsmg_gotorc(y + nr_lines - 2, x);
- SLsmg_write_nstring((char *)" ", max_len);
- SLsmg_gotorc(y + nr_lines - 1, x);
- SLsmg_write_nstring((char *)exit_msg, max_len);
+ if (exit_msg) {
+ SLsmg_gotorc(y + nr_lines - 2, x);
+ SLsmg_write_nstring(" ", max_len);
+ SLsmg_gotorc(y + nr_lines - 1, x);
+ SLsmg_write_nstring(exit_msg, max_len);
+ }
+}
+
+void ui__info_window(const char *title, const char *text)
+{
+ mutex_lock(&ui__lock);
+ __ui__info_window(title, text, NULL);
+ SLsmg_refresh();
+ mutex_unlock(&ui__lock);
+}
+
+int ui__question_window(const char *title, const char *text,
+ const char *exit_msg, int delay_secs)
+{
+ mutex_lock(&ui__lock);
+ __ui__info_window(title, text, exit_msg);
SLsmg_refresh();
+ mutex_unlock(&ui__lock);
return ui__getch(delay_secs);
}
@@ -215,9 +248,7 @@ static int __ui__warning(const char *title, const char *format, va_list args)
if (vasprintf(&s, format, args) > 0) {
int key;
- pthread_mutex_lock(&ui__lock);
key = ui__question_window(title, s, "Press any key...", 0);
- pthread_mutex_unlock(&ui__lock);
free(s);
return key;
}
diff --git a/tools/perf/ui/ui.h b/tools/perf/ui/ui.h
index 70cb0d4eb8aa..d8e911205582 100644
--- a/tools/perf/ui/ui.h
+++ b/tools/perf/ui/ui.h
@@ -1,18 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_H_
#define _PERF_UI_H_ 1
-#include <pthread.h>
+#include "../util/mutex.h"
#include <stdbool.h>
#include <linux/compiler.h>
-extern pthread_mutex_t ui__lock;
+extern struct mutex ui__lock;
+extern void *perf_gtk_handle;
extern int use_browser;
void setup_browser(bool fallback_to_pager);
void exit_browser(bool wait_for_ok);
-#ifdef SLANG_SUPPORT
+#ifdef HAVE_SLANG_SUPPORT
int ui__init(void);
void ui__exit(bool wait_for_ok);
#else
@@ -23,17 +25,13 @@ static inline int ui__init(void)
static inline void ui__exit(bool wait_for_ok __maybe_unused) {}
#endif
-#ifdef GTK2_SUPPORT
-int perf_gtk__init(void);
-void perf_gtk__exit(bool wait_for_ok);
-#else
-static inline int perf_gtk__init(void)
-{
- return -1;
-}
-static inline void perf_gtk__exit(bool wait_for_ok __maybe_unused) {}
-#endif
-
void ui__refresh_dimensions(bool force);
+struct option;
+
+int stdio__config_color(const struct option *opt, const char *mode, int unset);
+
+void pthread__block_sigwinch(void);
+void pthread__unblock_sigwinch(void);
+
#endif /* _PERF_UI_H_ */
diff --git a/tools/perf/ui/util.c b/tools/perf/ui/util.c
index e3e0a963d03a..1d38ddf01b60 100644
--- a/tools/perf/ui/util.c
+++ b/tools/perf/ui/util.c
@@ -1,6 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
#include "util.h"
-#include "../debug.h"
-
+#include "../util/debug.h"
+#include <stdio.h>
/*
* Default error logging functions
@@ -14,6 +15,9 @@ static int perf_stdio__error(const char *format, va_list args)
static int perf_stdio__warning(const char *format, va_list args)
{
+ if (quiet)
+ return 0;
+
fprintf(stderr, "Warning:\n");
vfprintf(stderr, format, args);
return 0;
@@ -44,6 +48,8 @@ int ui__warning(const char *format, ...)
{
int ret;
va_list args;
+ if (quiet)
+ return 0;
va_start(args, format);
ret = perf_eops->warning(format, args);
diff --git a/tools/perf/ui/util.h b/tools/perf/ui/util.h
index 361f08c52d37..e30cea807564 100644
--- a/tools/perf/ui/util.h
+++ b/tools/perf/ui/util.h
@@ -1,12 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_UTIL_H_
#define _PERF_UI_UTIL_H_ 1
#include <stdarg.h>
int ui__getch(int delay_secs);
-int ui__popup_menu(int argc, char * const argv[]);
+int ui__popup_menu(int argc, char * const argv[], int *keyp);
int ui__help_window(const char *text);
int ui__dialog_yesno(const char *msg);
+void __ui__info_window(const char *title, const char *text, const char *exit_msg);
+void ui__info_window(const char *title, const char *text);
int ui__question_window(const char *title, const char *text,
const char *exit_msg, int delay_secs);