diff options
Diffstat (limited to 'tools/perf/ui')
33 files changed, 7409 insertions, 0 deletions
diff --git a/tools/perf/ui/browser.c b/tools/perf/ui/browser.c new file mode 100644 index 00000000000..3ccf6e14f89 --- /dev/null +++ b/tools/perf/ui/browser.c @@ -0,0 +1,719 @@ +#include "../util.h" +#include "../cache.h" +#include "../../perf.h" +#include "libslang.h" +#include "ui.h" +#include "util.h" +#include <linux/compiler.h> +#include <linux/list.h> +#include <linux/rbtree.h> +#include <stdlib.h> +#include <sys/ttydefaults.h> +#include "browser.h" +#include "helpline.h" +#include "keysyms.h" +#include "../color.h" + +static int ui_browser__percent_color(struct ui_browser *browser, +				     double percent, bool current) +{ +	if (current && (!browser->use_navkeypressed || browser->navkeypressed)) +		return HE_COLORSET_SELECTED; +	if (percent >= MIN_RED) +		return HE_COLORSET_TOP; +	if (percent >= MIN_GREEN) +		return HE_COLORSET_MEDIUM; +	return HE_COLORSET_NORMAL; +} + +int ui_browser__set_color(struct ui_browser *browser, int color) +{ +	int ret = browser->current_color; +	browser->current_color = color; +	SLsmg_set_color(color); +	return ret; +} + +void ui_browser__set_percent_color(struct ui_browser *browser, +				   double percent, bool current) +{ +	 int color = ui_browser__percent_color(browser, percent, current); +	 ui_browser__set_color(browser, color); +} + +void ui_browser__gotorc(struct ui_browser *browser, int y, int x) +{ +	SLsmg_gotorc(browser->y + y, browser->x + x); +} + +static struct list_head * +ui_browser__list_head_filter_entries(struct ui_browser *browser, +				     struct list_head *pos) +{ +	do { +		if (!browser->filter || !browser->filter(browser, pos)) +			return pos; +		pos = pos->next; +	} while (pos != browser->entries); + +	return NULL; +} + +static struct list_head * +ui_browser__list_head_filter_prev_entries(struct ui_browser *browser, +					  struct list_head *pos) +{ +	do { +		if (!browser->filter || !browser->filter(browser, pos)) +			return pos; +		pos = pos->prev; +	} while (pos != browser->entries); + +	return NULL; +} + +void ui_browser__list_head_seek(struct ui_browser *browser, off_t offset, int whence) +{ +	struct list_head *head = browser->entries; +	struct list_head *pos; + +	if (browser->nr_entries == 0) +		return; + +	switch (whence) { +	case SEEK_SET: +		pos = ui_browser__list_head_filter_entries(browser, head->next); +		break; +	case SEEK_CUR: +		pos = browser->top; +		break; +	case SEEK_END: +		pos = ui_browser__list_head_filter_prev_entries(browser, head->prev); +		break; +	default: +		return; +	} + +	assert(pos != NULL); + +	if (offset > 0) { +		while (offset-- != 0) +			pos = ui_browser__list_head_filter_entries(browser, pos->next); +	} else { +		while (offset++ != 0) +			pos = ui_browser__list_head_filter_prev_entries(browser, pos->prev); +	} + +	browser->top = pos; +} + +void ui_browser__rb_tree_seek(struct ui_browser *browser, off_t offset, int whence) +{ +	struct rb_root *root = browser->entries; +	struct rb_node *nd; + +	switch (whence) { +	case SEEK_SET: +		nd = rb_first(root); +		break; +	case SEEK_CUR: +		nd = browser->top; +		break; +	case SEEK_END: +		nd = rb_last(root); +		break; +	default: +		return; +	} + +	if (offset > 0) { +		while (offset-- != 0) +			nd = rb_next(nd); +	} else { +		while (offset++ != 0) +			nd = rb_prev(nd); +	} + +	browser->top = nd; +} + +unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser) +{ +	struct rb_node *nd; +	int row = 0; + +	if (browser->top == NULL) +                browser->top = rb_first(browser->entries); + +	nd = browser->top; + +	while (nd != NULL) { +		ui_browser__gotorc(browser, row, 0); +		browser->write(browser, nd, row); +		if (++row == browser->height) +			break; +		nd = rb_next(nd); +	} + +	return row; +} + +bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row) +{ +	return browser->top_idx + row == browser->index; +} + +void ui_browser__refresh_dimensions(struct ui_browser *browser) +{ +	browser->width = SLtt_Screen_Cols - 1; +	browser->height = SLtt_Screen_Rows - 2; +	browser->y = 1; +	browser->x = 0; +} + +void ui_browser__handle_resize(struct ui_browser *browser) +{ +	ui__refresh_dimensions(false); +	ui_browser__show(browser, browser->title, ui_helpline__current); +	ui_browser__refresh(browser); +} + +int ui_browser__warning(struct ui_browser *browser, int timeout, +			const char *format, ...) +{ +	va_list args; +	char *text; +	int key = 0, err; + +	va_start(args, format); +	err = vasprintf(&text, format, args); +	va_end(args); + +	if (err < 0) { +		va_start(args, format); +		ui_helpline__vpush(format, args); +		va_end(args); +	} else { +		while ((key = ui__question_window("Warning!", text, +						   "Press any key...", +						   timeout)) == K_RESIZE) +			ui_browser__handle_resize(browser); +		free(text); +	} + +	return key; +} + +int ui_browser__help_window(struct ui_browser *browser, const char *text) +{ +	int key; + +	while ((key = ui__help_window(text)) == K_RESIZE) +		ui_browser__handle_resize(browser); + +	return key; +} + +bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text) +{ +	int key; + +	while ((key = ui__dialog_yesno(text)) == K_RESIZE) +		ui_browser__handle_resize(browser); + +	return key == K_ENTER || toupper(key) == 'Y'; +} + +void ui_browser__reset_index(struct ui_browser *browser) +{ +	browser->index = browser->top_idx = 0; +	browser->seek(browser, 0, SEEK_SET); +} + +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); +} + +void ui_browser__show_title(struct ui_browser *browser, const char *title) +{ +	pthread_mutex_lock(&ui__lock); +	__ui_browser__show_title(browser, title); +	pthread_mutex_unlock(&ui__lock); +} + +int ui_browser__show(struct ui_browser *browser, const char *title, +		     const char *helpline, ...) +{ +	int err; +	va_list ap; + +	ui_browser__refresh_dimensions(browser); + +	pthread_mutex_lock(&ui__lock); +	__ui_browser__show_title(browser, title); + +	browser->title = 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); +	return err ? 0 : -1; +} + +void ui_browser__hide(struct ui_browser *browser) +{ +	pthread_mutex_lock(&ui__lock); +	ui_helpline__pop(); +	zfree(&browser->helpline); +	pthread_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; + +	if (browser->nr_entries > 1) { +		pct = ((browser->index * (browser->height - 1)) / +		       (browser->nr_entries - 1)); +	} + +	SLsmg_set_char_set(1); + +	while (h < height) { +	        ui_browser__gotorc(browser, row++, col); +		SLsmg_write_char(h == pct ? SLSMG_DIAMOND_CHAR : SLSMG_CKBRD_CHAR); +		++h; +	} + +	SLsmg_set_char_set(0); +} + +static int __ui_browser__refresh(struct ui_browser *browser) +{ +	int row; +	int width = browser->width; + +	row = browser->refresh(browser); +	ui_browser__set_color(browser, HE_COLORSET_NORMAL); + +	if (!browser->use_navkeypressed || browser->navkeypressed) +		ui_browser__scrollbar_set(browser); +	else +		width += 1; + +	SLsmg_fill_region(browser->y + row, browser->x, +			  browser->height - row, width, ' '); + +	return 0; +} + +int ui_browser__refresh(struct ui_browser *browser) +{ +	pthread_mutex_lock(&ui__lock); +	__ui_browser__refresh(browser); +	pthread_mutex_unlock(&ui__lock); + +	return 0; +} + +/* + * Here we're updating nr_entries _after_ we started browsing, i.e.  we have to + * forget about any reference to any entry in the underlying data structure, + * that is why we do a SEEK_SET. Think about 'perf top' in the hists browser + * after an output_resort and hist decay. + */ +void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries) +{ +	off_t offset = nr_entries - browser->nr_entries; + +	browser->nr_entries = nr_entries; + +	if (offset < 0) { +		if (browser->top_idx < (u64)-offset) +			offset = -browser->top_idx; + +		browser->index += offset; +		browser->top_idx += offset; +	} + +	browser->top = NULL; +	browser->seek(browser, browser->top_idx, SEEK_SET); +} + +int ui_browser__run(struct ui_browser *browser, int delay_secs) +{ +	int err, key; + +	while (1) { +		off_t offset; + +		pthread_mutex_lock(&ui__lock); +		err = __ui_browser__refresh(browser); +		SLsmg_refresh(); +		pthread_mutex_unlock(&ui__lock); +		if (err < 0) +			break; + +		key = ui__getch(delay_secs); + +		if (key == K_RESIZE) { +			ui__refresh_dimensions(false); +			ui_browser__refresh_dimensions(browser); +			__ui_browser__show_title(browser, browser->title); +			ui_helpline__puts(browser->helpline); +			continue; +		} + +		if (browser->use_navkeypressed && !browser->navkeypressed) { +			if (key == K_DOWN || key == K_UP || +			    key == K_PGDN || key == K_PGUP || +			    key == K_HOME || key == K_END || +			    key == ' ') { +				browser->navkeypressed = true; +				continue; +			} else +				return key; +		} + +		switch (key) { +		case K_DOWN: +			if (browser->index == browser->nr_entries - 1) +				break; +			++browser->index; +			if (browser->index == browser->top_idx + browser->height) { +				++browser->top_idx; +				browser->seek(browser, +1, SEEK_CUR); +			} +			break; +		case K_UP: +			if (browser->index == 0) +				break; +			--browser->index; +			if (browser->index < browser->top_idx) { +				--browser->top_idx; +				browser->seek(browser, -1, SEEK_CUR); +			} +			break; +		case K_PGDN: +		case ' ': +			if (browser->top_idx + browser->height > browser->nr_entries - 1) +				break; + +			offset = browser->height; +			if (browser->index + offset > browser->nr_entries - 1) +				offset = browser->nr_entries - 1 - browser->index; +			browser->index += offset; +			browser->top_idx += offset; +			browser->seek(browser, +offset, SEEK_CUR); +			break; +		case K_PGUP: +			if (browser->top_idx == 0) +				break; + +			if (browser->top_idx < browser->height) +				offset = browser->top_idx; +			else +				offset = browser->height; + +			browser->index -= offset; +			browser->top_idx -= offset; +			browser->seek(browser, -offset, SEEK_CUR); +			break; +		case K_HOME: +			ui_browser__reset_index(browser); +			break; +		case K_END: +			offset = browser->height - 1; +			if (offset >= browser->nr_entries) +				offset = browser->nr_entries - 1; + +			browser->index = browser->nr_entries - 1; +			browser->top_idx = browser->index - offset; +			browser->seek(browser, -offset, SEEK_END); +			break; +		default: +			return key; +		} +	} +	return -1; +} + +unsigned int ui_browser__list_head_refresh(struct ui_browser *browser) +{ +	struct list_head *pos; +	struct list_head *head = browser->entries; +	int row = 0; + +	if (browser->top == NULL || browser->top == browser->entries) +                browser->top = ui_browser__list_head_filter_entries(browser, head->next); + +	pos = browser->top; + +	list_for_each_from(pos, head) { +		if (!browser->filter || !browser->filter(browser, pos)) { +			ui_browser__gotorc(browser, row, 0); +			browser->write(browser, pos, row); +			if (++row == browser->height) +				break; +		} +	} + +	return row; +} + +static struct ui_browser_colorset { +	const char *name, *fg, *bg; +	int colorset; +} ui_browser__colorsets[] = { +	{ +		.colorset = HE_COLORSET_TOP, +		.name	  = "top", +		.fg	  = "red", +		.bg	  = "default", +	}, +	{ +		.colorset = HE_COLORSET_MEDIUM, +		.name	  = "medium", +		.fg	  = "green", +		.bg	  = "default", +	}, +	{ +		.colorset = HE_COLORSET_NORMAL, +		.name	  = "normal", +		.fg	  = "default", +		.bg	  = "default", +	}, +	{ +		.colorset = HE_COLORSET_SELECTED, +		.name	  = "selected", +		.fg	  = "black", +		.bg	  = "lightgray", +	}, +	{ +		.colorset = HE_COLORSET_CODE, +		.name	  = "code", +		.fg	  = "blue", +		.bg	  = "default", +	}, +	{ +		.colorset = HE_COLORSET_ADDR, +		.name	  = "addr", +		.fg	  = "magenta", +		.bg	  = "default", +	}, +	{ +		.colorset = HE_COLORSET_ROOT, +		.name	  = "root", +		.fg	  = "white", +		.bg	  = "blue", +	}, +	{ +		.name = NULL, +	} +}; + + +static int ui_browser__color_config(const char *var, const char *value, +				    void *data __maybe_unused) +{ +	char *fg = NULL, *bg; +	int i; + +	/* same dir for all commands */ +	if (prefixcmp(var, "colors.") != 0) +		return 0; + +	for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) { +		const char *name = var + 7; + +		if (strcmp(ui_browser__colorsets[i].name, name) != 0) +			continue; + +		fg = strdup(value); +		if (fg == NULL) +			break; + +		bg = strchr(fg, ','); +		if (bg == NULL) +			break; + +		*bg = '\0'; +		while (isspace(*++bg)); +		ui_browser__colorsets[i].bg = bg; +		ui_browser__colorsets[i].fg = fg; +		return 0; +	} + +	free(fg); +	return -1; +} + +void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence) +{ +	switch (whence) { +	case SEEK_SET: +		browser->top = browser->entries; +		break; +	case SEEK_CUR: +		browser->top = browser->top + browser->top_idx + offset; +		break; +	case SEEK_END: +		browser->top = browser->top + browser->nr_entries - 1 + offset; +		break; +	default: +		return; +	} +} + +unsigned int ui_browser__argv_refresh(struct ui_browser *browser) +{ +	unsigned int row = 0, idx = browser->top_idx; +	char **pos; + +	if (browser->top == NULL) +		browser->top = browser->entries; + +	pos = (char **)browser->top; +	while (idx < 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) +				break; +		} + +		++idx; +		++pos; +	} + +	return row; +} + +void __ui_browser__vline(struct ui_browser *browser, unsigned int column, +			 u16 start, u16 end) +{ +	SLsmg_set_char_set(1); +	ui_browser__gotorc(browser, start, column); +	SLsmg_draw_vline(end - start + 1); +	SLsmg_set_char_set(0); +} + +void ui_browser__write_graph(struct ui_browser *browser __maybe_unused, +			     int graph) +{ +	SLsmg_set_char_set(1); +	SLsmg_write_char(graph); +	SLsmg_set_char_set(0); +} + +static void __ui_browser__line_arrow_up(struct ui_browser *browser, +					unsigned int column, +					u64 start, u64 end) +{ +	unsigned int row, end_row; + +	SLsmg_set_char_set(1); + +	if (start < browser->top_idx + browser->height) { +		row = start - browser->top_idx; +		ui_browser__gotorc(browser, row, column); +		SLsmg_write_char(SLSMG_LLCORN_CHAR); +		ui_browser__gotorc(browser, row, column + 1); +		SLsmg_draw_hline(2); + +		if (row-- == 0) +			goto out; +	} else +		row = browser->height - 1; + +	if (end > browser->top_idx) +		end_row = end - browser->top_idx; +	else +		end_row = 0; + +	ui_browser__gotorc(browser, end_row, column); +	SLsmg_draw_vline(row - end_row + 1); + +	ui_browser__gotorc(browser, end_row, column); +	if (end >= browser->top_idx) { +		SLsmg_write_char(SLSMG_ULCORN_CHAR); +		ui_browser__gotorc(browser, end_row, column + 1); +		SLsmg_write_char(SLSMG_HLINE_CHAR); +		ui_browser__gotorc(browser, end_row, column + 2); +		SLsmg_write_char(SLSMG_RARROW_CHAR); +	} +out: +	SLsmg_set_char_set(0); +} + +static void __ui_browser__line_arrow_down(struct ui_browser *browser, +					  unsigned int column, +					  u64 start, u64 end) +{ +	unsigned int row, end_row; + +	SLsmg_set_char_set(1); + +	if (start >= browser->top_idx) { +		row = start - browser->top_idx; +		ui_browser__gotorc(browser, row, column); +		SLsmg_write_char(SLSMG_ULCORN_CHAR); +		ui_browser__gotorc(browser, row, column + 1); +		SLsmg_draw_hline(2); + +		if (row++ == 0) +			goto out; +	} else +		row = 0; + +	if (end >= browser->top_idx + browser->height) +		end_row = browser->height - 1; +	else +		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) { +		SLsmg_write_char(SLSMG_LLCORN_CHAR); +		ui_browser__gotorc(browser, end_row, column + 1); +		SLsmg_write_char(SLSMG_HLINE_CHAR); +		ui_browser__gotorc(browser, end_row, column + 2); +		SLsmg_write_char(SLSMG_RARROW_CHAR); +	} +out: +	SLsmg_set_char_set(0); +} + +void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column, +			      u64 start, u64 end) +{ +	if (start > end) +		__ui_browser__line_arrow_up(browser, column, start, end); +	else +		__ui_browser__line_arrow_down(browser, column, start, end); +} + +void ui_browser__init(void) +{ +	int i = 0; + +	perf_config(ui_browser__color_config, NULL); + +	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); +	} + +	annotate_browser__init(); +} diff --git a/tools/perf/ui/browser.h b/tools/perf/ui/browser.h new file mode 100644 index 00000000000..03d4d6295f1 --- /dev/null +++ b/tools/perf/ui/browser.h @@ -0,0 +1,74 @@ +#ifndef _PERF_UI_BROWSER_H_ +#define _PERF_UI_BROWSER_H_ 1 + +#include <linux/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_ADDR	55 +#define HE_COLORSET_ROOT	56 + +struct ui_browser { +	u64	      index, top_idx; +	void	      *top, *entries; +	u16	      y, x, width, height; +	int	      current_color; +	void	      *priv; +	const char    *title; +	char	      *helpline; +	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 *browser, +				   double percent, bool current); +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 *browser, int y, int x); +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__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 *browser, const char *title, +		     const char *helpline, ...); +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); +void __ui_browser__vline(struct ui_browser *browser, unsigned int column, +			 u16 start, u16 end); + +int ui_browser__warning(struct ui_browser *browser, int timeout, +			const char *format, ...); +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_env; +int tui__header_window(struct perf_session_env *env); + +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 *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 *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/annotate.c b/tools/perf/ui/browsers/annotate.c new file mode 100644 index 00000000000..f0697a3aede --- /dev/null +++ b/tools/perf/ui/browsers/annotate.c @@ -0,0 +1,1023 @@ +#include "../../util/util.h" +#include "../browser.h" +#include "../helpline.h" +#include "../libslang.h" +#include "../ui.h" +#include "../util.h" +#include "../../util/annotate.h" +#include "../../util/hist.h" +#include "../../util/sort.h" +#include "../../util/symbol.h" +#include "../../util/evsel.h" +#include <pthread.h> + +struct browser_disasm_line { +	struct rb_node	rb_node; +	u32		idx; +	int		idx_asm; +	int		jump_sources; +	/* +	 * actual length of this array is saved on the nr_events field +	 * of the struct annotate_browser +	 */ +	double		percent[1]; +}; + +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, +}; + +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 inline struct browser_disasm_line *disasm_line__browser(struct disasm_line *dl) +{ +	return (struct browser_disasm_line *)(dl + 1); +} + +static bool disasm_line__filter(struct ui_browser *browser __maybe_unused, +				void *entry) +{ +	if (annotate_browser__opts.hide_src_code) { +		struct disasm_line *dl = list_entry(entry, struct disasm_line, node); +		return dl->offset == -1; +	} + +	return false; +} + +static int annotate_browser__jumps_percent_color(struct annotate_browser *browser, +						 int nr, bool current) +{ +	if (current && (!browser->b.use_navkeypressed || browser->b.navkeypressed)) +		return HE_COLORSET_SELECTED; +	if (nr == browser->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) +{ +	 int color = annotate_browser__jumps_percent_color(browser, nr, current); +	 return ui_browser__set_color(&browser->b, color); +} + +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(' '); + +	/* 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, " "); +			} +		} + +		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(' '); +			} +		} + +		disasm_line__scnprintf(dl, bf, sizeof(bf), !annotate_browser__opts.use_offset); +		slsmg_write_nstring(bf, width - pcnt_width - 3 - printed); +	} + +	if (current_entry) +		ab->selection = dl; +} + +static bool disasm_line__is_valid_jump(struct disasm_line *dl, struct symbol *sym) +{ +	if (!dl || !dl->ins || !ins__is_jump(dl->ins) +	    || !disasm_line__has_offset(dl) +	    || dl->ops.target.offset >= symbol__size(sym)) +		return false; + +	return true; +} + +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; +	unsigned int from, to; +	struct map_symbol *ms = ab->b.priv; +	struct symbol *sym = ms->sym; +	u8 pcnt_width = 7; + +	/* PLT symbols contain external offsets */ +	if (strstr(sym->name, "@plt")) +		return; + +	if (!disasm_line__is_valid_jump(cursor, sym)) +		return; + +	target = ab->offsets[cursor->ops.target.offset]; +	if (!target) +		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; +	} else { +		from = (u64)bcursor->idx; +		to = (u64)btarget->idx; +	} + +	pcnt_width *= ab->nr_events; + +	ui_browser__set_color(browser, HE_COLORSET_CODE); +	__ui_browser__line_arrow(browser, pcnt_width + 2 + ab->addr_width, +				 from, to); +} + +static unsigned int annotate_browser__refresh(struct ui_browser *browser) +{ +	struct annotate_browser *ab = container_of(browser, struct annotate_browser, b); +	int ret = ui_browser__list_head_refresh(browser); +	int pcnt_width; + +	pcnt_width = 7 * ab->nr_events; + +	if (annotate_browser__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); +	return ret; +} + +static int disasm__cmp(struct browser_disasm_line *a, +		       struct browser_disasm_line *b, int nr_pcnt) +{ +	int i; + +	for (i = 0; i < nr_pcnt; i++) { +		if (a->percent[i] == b->percent[i]) +			continue; +		return a->percent[i] < b->percent[i]; +	} +	return 0; +} + +static void disasm_rb_tree__insert(struct rb_root *root, struct browser_disasm_line *bdl, +				   int nr_events) +{ +	struct rb_node **p = &root->rb_node; +	struct rb_node *parent = NULL; +	struct browser_disasm_line *l; + +	while (*p != NULL) { +		parent = *p; +		l = rb_entry(parent, struct browser_disasm_line, rb_node); + +		if (disasm__cmp(bdl, l, nr_events)) +			p = &(*p)->rb_left; +		else +			p = &(*p)->rb_right; +	} +	rb_link_node(&bdl->rb_node, parent, p); +	rb_insert_color(&bdl->rb_node, root); +} + +static void annotate_browser__set_top(struct annotate_browser *browser, +				      struct disasm_line *pos, u32 idx) +{ +	unsigned back; + +	ui_browser__refresh_dimensions(&browser->b); +	back = browser->b.height / 2; +	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); + +		if (disasm_line__filter(&browser->b, &pos->node)) +			continue; + +		--browser->b.top_idx; +		--back; +	} + +	browser->b.top = pos; +	browser->b.navkeypressed = true; +} + +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; + +	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; +	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 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); + +	browser->entries = RB_ROOT; + +	pthread_mutex_lock(¬es->lock); + +	list_for_each_entry(pos, ¬es->src->source, node) { +		struct browser_disasm_line *bpos = disasm_line__browser(pos); +		const char *path = NULL; +		double max_percent = 0.0; +		int i; + +		if (pos->offset == -1) { +			RB_CLEAR_NODE(&bpos->rb_node); +			continue; +		} + +		next = disasm__get_next_ip_line(¬es->src->source, pos); + +		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); + +			if (max_percent < bpos->percent[i]) +				max_percent = bpos->percent[i]; +		} + +		if (max_percent < 0.01) { +			RB_CLEAR_NODE(&bpos->rb_node); +			continue; +		} +		disasm_rb_tree__insert(&browser->entries, bpos, +				       browser->nr_events); +	} +	pthread_mutex_unlock(¬es->lock); + +	browser->curr_hot = rb_last(&browser->entries); +} + +static bool annotate_browser__toggle_source(struct annotate_browser *browser) +{ +	struct disasm_line *dl; +	struct browser_disasm_line *bdl; +	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); + +	if (annotate_browser__opts.hide_src_code) { +		if (bdl->idx_asm < offset) +			offset = bdl->idx; + +		browser->b.nr_entries = browser->nr_entries; +		annotate_browser__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; +	} 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 (bdl->idx_asm < offset) +			offset = bdl->idx_asm; + +		browser->b.nr_entries = browser->nr_asm_entries; +		annotate_browser__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; +	} + +	return true; +} + +static void annotate_browser__init_asm_mode(struct annotate_browser *browser) +{ +	ui_browser__reset_index(&browser->b); +	browser->b.nr_entries = browser->nr_asm_entries; +} + +#define SYM_TITLE_MAX_SIZE (PATH_MAX + 64) + +static int sym_title(struct symbol *sym, struct map *map, char *title, +		     size_t sz) +{ +	return snprintf(title, sz, "%s  %s", sym->name, map->dso->long_name); +} + +static bool annotate_browser__callq(struct annotate_browser *browser, +				    struct perf_evsel *evsel, +				    struct hist_browser_timer *hbt) +{ +	struct map_symbol *ms = browser->b.priv; +	struct disasm_line *dl = browser->selection; +	struct annotation *notes; +	struct addr_map_symbol target = { +		.map = ms->map, +		.addr = map__objdump_2mem(ms->map, dl->ops.target.addr), +	}; +	char title[SYM_TITLE_MAX_SIZE]; + +	if (!ins__is_call(dl->ins)) +		return false; + +	if (map_groups__find_ams(&target, NULL) || +	    map__rip_2objdump(target.map, target.map->map_ip(target.map, +							     target.addr)) != +	    dl->ops.target.addr) { +		ui_helpline__puts("The called function was not found."); +		return true; +	} + +	notes = symbol__annotation(target.sym); +	pthread_mutex_lock(¬es->lock); + +	if (notes->src == NULL && symbol__alloc_hist(target.sym) < 0) { +		pthread_mutex_unlock(¬es->lock); +		ui__warning("Not enough memory for annotating '%s' symbol!\n", +			    target.sym->name); +		return true; +	} + +	pthread_mutex_unlock(¬es->lock); +	symbol__tui_annotate(target.sym, target.map, evsel, hbt); +	sym_title(ms->sym, ms->map, title, sizeof(title)); +	ui_browser__show_title(&browser->b, title); +	return true; +} + +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 disasm_line *pos; + +	*idx = 0; +	list_for_each_entry(pos, ¬es->src->source, node) { +		if (pos->offset == offset) +			return pos; +		if (!disasm_line__filter(&browser->b, &pos->node)) +			++*idx; +	} + +	return NULL; +} + +static bool annotate_browser__jump(struct annotate_browser *browser) +{ +	struct disasm_line *dl = browser->selection; +	s64 idx; + +	if (!ins__is_jump(dl->ins)) +		return false; + +	dl = annotate_browser__find_offset(browser, dl->ops.target.offset, &idx); +	if (dl == NULL) { +		ui_helpline__puts("Invalid jump offset"); +		return true; +	} + +	annotate_browser__set_top(browser, dl, idx); +	 +	return true; +} + +static +struct disasm_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; + +	*idx = browser->b.index; +	list_for_each_entry_continue(pos, ¬es->src->source, node) { +		if (disasm_line__filter(&browser->b, &pos->node)) +			continue; + +		++*idx; + +		if (pos->line && strstr(pos->line, s) != NULL) +			return pos; +	} + +	return NULL; +} + +static bool __annotate_browser__search(struct annotate_browser *browser) +{ +	struct disasm_line *dl; +	s64 idx; + +	dl = annotate_browser__find_string(browser, browser->search_bf, &idx); +	if (dl == NULL) { +		ui_helpline__puts("String not found!"); +		return false; +	} + +	annotate_browser__set_top(browser, dl, idx); +	browser->searching_backwards = false; +	return true; +} + +static +struct disasm_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; + +	*idx = browser->b.index; +	list_for_each_entry_continue_reverse(pos, ¬es->src->source, node) { +		if (disasm_line__filter(&browser->b, &pos->node)) +			continue; + +		--*idx; + +		if (pos->line && strstr(pos->line, s) != NULL) +			return pos; +	} + +	return NULL; +} + +static bool __annotate_browser__search_reverse(struct annotate_browser *browser) +{ +	struct disasm_line *dl; +	s64 idx; + +	dl = annotate_browser__find_string_reverse(browser, browser->search_bf, &idx); +	if (dl == NULL) { +		ui_helpline__puts("String not found!"); +		return false; +	} + +	annotate_browser__set_top(browser, dl, idx); +	browser->searching_backwards = true; +	return true; +} + +static bool annotate_browser__search_window(struct annotate_browser *browser, +					    int delay_secs) +{ +	if (ui_browser__input_window("Search", "String: ", browser->search_bf, +				     "ENTER: OK, ESC: Cancel", +				     delay_secs * 2) != K_ENTER || +	    !*browser->search_bf) +		return false; + +	return true; +} + +static bool annotate_browser__search(struct annotate_browser *browser, int delay_secs) +{ +	if (annotate_browser__search_window(browser, delay_secs)) +		return __annotate_browser__search(browser); + +	return false; +} + +static bool annotate_browser__continue_search(struct annotate_browser *browser, +					      int delay_secs) +{ +	if (!*browser->search_bf) +		return annotate_browser__search(browser, delay_secs); + +	return __annotate_browser__search(browser); +} + +static bool annotate_browser__search_reverse(struct annotate_browser *browser, +					   int delay_secs) +{ +	if (annotate_browser__search_window(browser, delay_secs)) +		return __annotate_browser__search_reverse(browser); + +	return false; +} + +static +bool annotate_browser__continue_search_reverse(struct annotate_browser *browser, +					       int delay_secs) +{ +	if (!*browser->search_bf) +		return annotate_browser__search_reverse(browser, delay_secs); + +	return __annotate_browser__search_reverse(browser); +} + +static void annotate_browser__update_addr_width(struct annotate_browser *browser) +{ +	if (annotate_browser__opts.use_offset) +		browser->target_width = browser->min_addr_width; +	else +		browser->target_width = browser->max_addr_width; + +	browser->addr_width = browser->target_width; + +	if (annotate_browser__opts.show_nr_jumps) +		browser->addr_width += browser->jumps_width + 1; +} + +static int annotate_browser__run(struct annotate_browser *browser, +				 struct perf_evsel *evsel, +				 struct hist_browser_timer *hbt) +{ +	struct rb_node *nd = NULL; +	struct map_symbol *ms = browser->b.priv; +	struct symbol *sym = ms->sym; +	const char *help = "Press 'h' for help on key bindings"; +	int delay_secs = hbt ? hbt->refresh : 0; +	int key; +	char title[SYM_TITLE_MAX_SIZE]; + +	sym_title(sym, ms->map, title, sizeof(title)); +	if (ui_browser__show(&browser->b, title, help) < 0) +		return -1; + +	annotate_browser__calc_percent(browser, evsel); + +	if (browser->curr_hot) { +		annotate_browser__set_rb_top(browser, browser->curr_hot); +		browser->b.navkeypressed = false; +	} + +	nd = browser->curr_hot; + +	while (1) { +		key = ui_browser__run(&browser->b, delay_secs); + +		if (delay_secs != 0) { +			annotate_browser__calc_percent(browser, evsel); +			/* +			 * Current line focus got out of the list of most active +			 * lines, NULL it so that if TAB|UNTAB is pressed, we +			 * move to curr_hot (current hottest line). +			 */ +			if (nd != NULL && RB_EMPTY_NODE(nd)) +				nd = NULL; +		} + +		switch (key) { +		case K_TIMER: +			if (hbt) +				hbt->timer(hbt->arg); + +			if (delay_secs != 0) +				symbol__annotate_decay_histogram(sym, evsel->idx); +			continue; +		case K_TAB: +			if (nd != NULL) { +				nd = rb_prev(nd); +				if (nd == NULL) +					nd = rb_last(&browser->entries); +			} else +				nd = browser->curr_hot; +			break; +		case K_UNTAB: +			if (nd != NULL) +				nd = rb_next(nd); +				if (nd == NULL) +					nd = rb_first(&browser->entries); +			else +				nd = browser->curr_hot; +			break; +		case K_F1: +		case 'h': +			ui_browser__help_window(&browser->b, +		"UP/DOWN/PGUP\n" +		"PGDN/SPACE    Navigate\n" +		"q/ESC/CTRL+C  Exit\n\n" +		"->            Go to target\n" +		"<-            Exit\n" +		"H             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" +		"s             Toggle source code view\n" +		"/             Search string\n" +		"r             Run available scripts\n" +		"?             Search string backwards\n"); +			continue; +		case 'r': +			{ +				script_browse(NULL); +				continue; +			} +		case 'H': +			nd = browser->curr_hot; +			break; +		case 's': +			if (annotate_browser__toggle_source(browser)) +				ui_helpline__puts(help); +			continue; +		case 'o': +			annotate_browser__opts.use_offset = !annotate_browser__opts.use_offset; +			annotate_browser__update_addr_width(browser); +			continue; +		case 'j': +			annotate_browser__opts.jump_arrows = !annotate_browser__opts.jump_arrows; +			continue; +		case 'J': +			annotate_browser__opts.show_nr_jumps = !annotate_browser__opts.show_nr_jumps; +			annotate_browser__update_addr_width(browser); +			continue; +		case '/': +			if (annotate_browser__search(browser, delay_secs)) { +show_help: +				ui_helpline__puts(help); +			} +			continue; +		case 'n': +			if (browser->searching_backwards ? +			    annotate_browser__continue_search_reverse(browser, delay_secs) : +			    annotate_browser__continue_search(browser, delay_secs)) +				goto show_help; +			continue; +		case '?': +			if (annotate_browser__search_reverse(browser, delay_secs)) +				goto show_help; +			continue; +		case 'D': { +			static int seq; +			ui_helpline__pop(); +			ui_helpline__fpush("%d: nr_ent=%d, height=%d, idx=%d, top_idx=%d, nr_asm_entries=%d", +					   seq++, browser->b.nr_entries, +					   browser->b.height, +					   browser->b.index, +					   browser->b.top_idx, +					   browser->nr_asm_entries); +		} +			continue; +		case K_ENTER: +		case K_RIGHT: +			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; +				goto out; +			} else if (!(annotate_browser__jump(browser) || +				     annotate_browser__callq(browser, evsel, hbt))) { +show_sup_ins: +				ui_helpline__puts("Actions are only available for 'callq', 'retq' & jump instructions."); +			} +			continue; +		case K_LEFT: +		case K_ESC: +		case 'q': +		case CTRL('c'): +			goto out; +		default: +			continue; +		} + +		if (nd != NULL) +			annotate_browser__set_rb_top(browser, nd); +	} +out: +	ui_browser__hide(&browser->b); +	return key; +} + +int hist_entry__tui_annotate(struct hist_entry *he, struct perf_evsel *evsel, +			     struct hist_browser_timer *hbt) +{ +	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; + +		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; +	} +		 +} + +static inline int width_jumps(int n) +{ +	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 annotate_browser browser = { +		.b = { +			.refresh = annotate_browser__refresh, +			.seek	 = ui_browser__list_head_seek, +			.write	 = annotate_browser__write, +			.filter  = disasm_line__filter, +			.priv	 = &ms, +			.use_navkeypressed = true, +		}, +	}; +	int ret = -1; +	int nr_pcnt = 1; +	size_t sizeof_bdl = sizeof(struct browser_disasm_line); + +	if (sym == NULL) +		return -1; + +	size = symbol__size(sym); + +	if (map->dso->annotate_warned) +		return -1; + +	browser.offsets = zalloc(size * sizeof(struct disasm_line *)); +	if (browser.offsets == NULL) { +		ui__error("Not enough memory!"); +		return -1; +	} + +	if (perf_evsel__is_group_event(evsel)) { +		nr_pcnt = evsel->nr_members; +		sizeof_bdl += sizeof(double) * (nr_pcnt - 1); +	} + +	if (symbol__annotate(sym, map, sizeof_bdl) < 0) { +		ui__error("%s", ui_helpline__last_msg); +		goto out_free_offsets; +	} + +	ui_helpline__push("Press <- or ESC to exit"); + +	notes = symbol__annotation(sym); +	browser.start = map__rip_2objdump(map, sym->start); + +	list_for_each_entry(pos, ¬es->src->source, node) { +		struct browser_disasm_line *bpos; +		size_t line_len = strlen(pos->line); + +		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 = ¬es->src->source, +	browser.b.width += 18; /* Percentage */ + +	if (annotate_browser__opts.hide_src_code) +		annotate_browser__init_asm_mode(&browser); + +	annotate_browser__update_addr_width(&browser); + +	ret = annotate_browser__run(&browser, evsel, hbt); +	list_for_each_entry_safe(pos, n, ¬es->src->source, node) { +		list_del(&pos->node); +		disasm_line__free(pos); +	} + +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); +} + +static int annotate__config(const char *var, const char *value, +			    void *data __maybe_unused) +{ +	struct annotate_config *cfg; +	const char *name; + +	if (prefixcmp(var, "annotate.") != 0) +		return 0; + +	name = var + 9; +	cfg = bsearch(name, annotate__configs, ARRAY_SIZE(annotate__configs), +		      sizeof(struct annotate_config), annotate_config__cmp); + +	if (cfg == NULL) +		return -1; + +	*cfg->value = perf_config_bool(name, value); +	return 0; +} + +void annotate_browser__init(void) +{ +	perf_config(annotate__config, NULL); +} diff --git a/tools/perf/ui/browsers/header.c b/tools/perf/ui/browsers/header.c new file mode 100644 index 00000000000..89c16b98861 --- /dev/null +++ b/tools/perf/ui/browsers/header.c @@ -0,0 +1,127 @@ +#include "util/cache.h" +#include "util/debug.h" +#include "ui/browser.h" +#include "ui/ui.h" +#include "ui/util.h" +#include "ui/libslang.h" +#include "util/header.h" +#include "util/session.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); + +	slsmg_write_nstring(str, browser->width); +} + +static int list_menu__run(struct ui_browser *menu) +{ +	int key; +	unsigned long offset; +	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: +			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_env *env) +{ +	int i, argc = 0; +	char **argv; +	struct perf_session *session; +	char *ptr, *pos; +	size_t size; +	FILE *fp = open_memstream(&ptr, &size); + +	session = container_of(env, struct perf_session, header.env); +	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 new file mode 100644 index 00000000000..04a229aa5c0 --- /dev/null +++ b/tools/perf/ui/browsers/hists.c @@ -0,0 +1,2007 @@ +#include <stdio.h> +#include "../libslang.h" +#include <stdlib.h> +#include <string.h> +#include <linux/rbtree.h> + +#include "../../util/evsel.h" +#include "../../util/evlist.h" +#include "../../util/hist.h" +#include "../../util/pstack.h" +#include "../../util/sort.h" +#include "../../util/util.h" +#include "../../arch/common.h" + +#include "../browser.h" +#include "../helpline.h" +#include "../util.h" +#include "../ui.h" +#include "map.h" +#include "annotate.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_non_filtered_entries; +	u64		     nr_callchain_rows; +}; + +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 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; +} + +static u32 hist_browser__nr_entries(struct hist_browser *hb) +{ +	u32 nr_entries; + +	if (hist_browser__has_filter(hb)) +		nr_entries = hb->nr_non_filtered_entries; +	else +		nr_entries = hb->hists->nr_entries; + +	return nr_entries + hb->nr_callchain_rows; +} + +static void hist_browser__refresh_dimensions(struct hist_browser *browser) +{ +	/* 3 == +/- toggle symbol before actual hist_entry rendering */ +	browser->b.width = 3 + (hists__sort_list_width(browser->hists) + +			     sizeof("[k]")); +} + +static void hist_browser__reset(struct hist_browser *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); +	ui_browser__reset_index(&browser->b); +} + +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); +} + +static char callchain_list__folded(const struct callchain_list *cl) +{ +	return map_symbol__folded(&cl->ms); +} + +static void map_symbol__set_folding(struct map_symbol *ms, bool unfold) +{ +	ms->unfolded = unfold ? ms->has_children : false; +} + +static int callchain_node__count_rows_rb_tree(struct callchain_node *node) +{ +	int n = 0; +	struct rb_node *nd; + +	for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { +		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); +		struct callchain_list *chain; +		char folded_sign = ' '; /* No children */ + +		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 == '+') +				break; +		} + +		if (folded_sign == '-') /* Have children and they're unfolded */ +			n += callchain_node__count_rows_rb_tree(child); +	} + +	return n; +} + +static int callchain_node__count_rows(struct callchain_node *node) +{ +	struct callchain_list *chain; +	bool unfolded = false; +	int n = 0; + +	list_for_each_entry(chain, &node->val, list) { +		++n; +		unfolded = chain->ms.unfolded; +	} + +	if (unfolded) +		n += callchain_node__count_rows_rb_tree(node); + +	return n; +} + +static int callchain__count_rows(struct rb_root *chain) +{ +	struct rb_node *nd; +	int n = 0; + +	for (nd = rb_first(chain); nd; nd = rb_next(nd)) { +		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); +		n += callchain_node__count_rows(node); +	} + +	return n; +} + +static bool map_symbol__toggle_fold(struct map_symbol *ms) +{ +	if (!ms) +		return false; + +	if (!ms->has_children) +		return false; + +	ms->unfolded = !ms->unfolded; +	return true; +} + +static void callchain_node__init_have_children_rb_tree(struct callchain_node *node) +{ +	struct rb_node *nd = rb_first(&node->rb_root); + +	for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { +		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); +		struct callchain_list *chain; +		bool first = true; + +		list_for_each_entry(chain, &child->val, list) { +			if (first) { +				first = false; +				chain->ms.has_children = chain->list.next != &child->val || +							 !RB_EMPTY_ROOT(&child->rb_root); +			} else +				chain->ms.has_children = chain->list.next == &child->val && +							 !RB_EMPTY_ROOT(&child->rb_root); +		} + +		callchain_node__init_have_children_rb_tree(child); +	} +} + +static void callchain_node__init_have_children(struct callchain_node *node) +{ +	struct callchain_list *chain; + +	list_for_each_entry(chain, &node->val, list) +		chain->ms.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; + +	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); +	} +} + +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); +		callchain__init_have_children(&he->sorted_chain); +		he->init_have_children = true; +	} +} + +static bool hist_browser__toggle_fold(struct hist_browser *browser) +{ +	if (map_symbol__toggle_fold(browser->selection)) { +		struct hist_entry *he = browser->he_selection; + +		hist_entry__init_have_children(he); +		browser->b.nr_entries -= he->nr_rows; +		browser->nr_callchain_rows -= he->nr_rows; + +		if (he->ms.unfolded) +			he->nr_rows = callchain__count_rows(&he->sorted_chain); +		else +			he->nr_rows = 0; + +		browser->b.nr_entries += he->nr_rows; +		browser->nr_callchain_rows += he->nr_rows; + +		return true; +	} + +	/* If it doesn't have children, no toggling performed */ +	return false; +} + +static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold) +{ +	int n = 0; +	struct rb_node *nd; + +	for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { +		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); +		struct callchain_list *chain; +		bool has_children = false; + +		list_for_each_entry(chain, &child->val, list) { +			++n; +			map_symbol__set_folding(&chain->ms, unfold); +			has_children = chain->ms.has_children; +		} + +		if (has_children) +			n += callchain_node__set_folding_rb_tree(child, unfold); +	} + +	return n; +} + +static int callchain_node__set_folding(struct callchain_node *node, bool unfold) +{ +	struct callchain_list *chain; +	bool has_children = false; +	int n = 0; + +	list_for_each_entry(chain, &node->val, list) { +		++n; +		map_symbol__set_folding(&chain->ms, unfold); +		has_children = chain->ms.has_children; +	} + +	if (has_children) +		n += callchain_node__set_folding_rb_tree(node, unfold); + +	return n; +} + +static int callchain__set_folding(struct rb_root *chain, bool unfold) +{ +	struct rb_node *nd; +	int n = 0; + +	for (nd = rb_first(chain); nd; nd = rb_next(nd)) { +		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); +		n += callchain_node__set_folding(node, unfold); +	} + +	return n; +} + +static void hist_entry__set_folding(struct hist_entry *he, bool unfold) +{ +	hist_entry__init_have_children(he); +	map_symbol__set_folding(&he->ms, 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 +__hist_browser__set_folding(struct hist_browser *browser, bool unfold) +{ +	struct rb_node *nd; +	struct hists *hists = browser->hists; + +	for (nd = rb_first(&hists->entries); +	     (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL; +	     nd = rb_next(nd)) { +		struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); +		hist_entry__set_folding(he, unfold); +		browser->nr_callchain_rows += he->nr_rows; +	} +} + +static void hist_browser__set_folding(struct hist_browser *browser, bool unfold) +{ +	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 ui_browser__warn_lost_events(struct ui_browser *browser) +{ +	ui_browser__warning(browser, 4, +		"Events are being lost, check IO/CPU overload!\n\n" +		"You may want to run 'perf' using a RT scheduler policy:\n\n" +		" perf top -r 80\n\n" +		"Or reduce the sampling frequency."); +} + +static int hist_browser__run(struct hist_browser *browser, const char *ev_name, +			     struct hist_browser_timer *hbt) +{ +	int key; +	char title[160]; +	int delay_secs = hbt ? hbt->refresh : 0; + +	browser->b.entries = &browser->hists->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); + +	if (ui_browser__show(&browser->b, title, +			     "Press '?' for help on key bindings") < 0) +		return -1; + +	while (1) { +		key = ui_browser__run(&browser->b, delay_secs); + +		switch (key) { +		case K_TIMER: { +			u64 nr_entries; +			hbt->timer(hbt->arg); + +			if (hist_browser__has_filter(browser)) +				hist_browser__update_nr_entries(browser); + +			nr_entries = hist_browser__nr_entries(browser); +			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); +		} +			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) +{ +	int printed; + +	if (cl->ms.sym) +		printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name); +	else +		printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip); + +	if (show_dso) +		scnprintf(bf + printed, bfsize - printed, " %s", +			  cl->ms.map ? cl->ms.map->dso->short_name : "unknown"); + +	return bf; +} + +#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) +{ +	struct rb_node *node; +	int first_row = row, width, offset = level * LEVEL_OFFSET_STEP; +	u64 new_total, remaining; + +	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; +			int color; +			bool was_first = first; + +			if (first) +				first = false; +			else +				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; + +				if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) +					str = "Not enough memory!"; +				else +					str = alloc_str; +			} + +			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; +			} + +			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); + +			if (++row == browser->b.height) +				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; +		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) +{ +	struct callchain_list *chain; +	int first_row = row, +	     offset = level * LEVEL_OFFSET_STEP, +	     width = browser->b.width - offset; +	char folded_sign = ' '; + +	list_for_each_entry(chain, &node->val, list) { +		char bf[1024], *s; +		int color; + +		folded_sign = callchain_list__folded(chain); + +		if (*row_offset != 0) { +			--*row_offset; +			continue; +		} + +		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; +		} + +		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); + +		if (++row == browser->b.height) +			goto out; +	} + +	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; +} + +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; + +	for (nd = rb_first(chain); nd; nd = rb_next(nd)) { +		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + +		row += hist_browser__show_callchain_node(browser, node, level, +							 row, row_offset, +							 is_current_entry); +		if (row == browser->b.height) +			break; +	} + +	return row - first_row; +} + +struct hpp_arg { +	struct ui_browser *b; +	char folded_sign; +	bool current_entry; +}; + +static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...) +{ +	struct hpp_arg *arg = hpp->ptr; +	int ret; +	va_list args; +	double percent; + +	va_start(args, fmt); +	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, percent); +	slsmg_printf("%s", hpp->buf); + +	advance_hpp(hpp, ret); +	return ret; +} + +#define __HPP_COLOR_PERCENT_FN(_type, _field)				\ +static u64 __hpp_get_##_field(struct hist_entry *he)			\ +{									\ +	return he->stat._field;						\ +}									\ +									\ +static int								\ +hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ +				struct perf_hpp *hpp,			\ +				struct hist_entry *he)			\ +{									\ +	return __hpp__fmt(hpp, he, __hpp_get_##_field, " %6.2f%%",	\ +			  __hpp__slsmg_color_printf, true);		\ +} + +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field)			\ +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 __maybe_unused,\ +				struct perf_hpp *hpp,			\ +				struct hist_entry *he)			\ +{									\ +	if (!symbol_conf.cumulate_callchain) {				\ +		int ret = scnprintf(hpp->buf, hpp->size, "%8s", "N/A");	\ +		slsmg_printf("%s", hpp->buf);				\ +									\ +		return ret;						\ +	}								\ +	return __hpp__fmt(hpp, he, __hpp_get_acc_##_field, " %6.2f%%",	\ +			  __hpp__slsmg_color_printf, true);		\ +} + +__HPP_COLOR_PERCENT_FN(overhead, period) +__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 +#undef __HPP_COLOR_ACC_PERCENT_FN + +void hist_browser__init_hpp(void) +{ +	perf_hpp__format[PERF_HPP__OVERHEAD].color = +				hist_browser__hpp_color_overhead; +	perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color = +				hist_browser__hpp_color_overhead_sys; +	perf_hpp__format[PERF_HPP__OVERHEAD_US].color = +				hist_browser__hpp_color_overhead_us; +	perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color = +				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; +} + +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); +	off_t row_offset = entry->row_offset; +	bool first = true; +	struct perf_hpp_fmt *fmt; + +	if (current_entry) { +		browser->he_selection = entry; +		browser->selection = &entry->ms; +	} + +	if (symbol_conf.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, +			.folded_sign	= folded_sign, +			.current_entry	= current_entry, +		}; +		struct perf_hpp hpp = { +			.buf		= s, +			.size		= sizeof(s), +			.ptr		= &arg, +		}; + +		ui_browser__gotorc(&browser->b, row, 0); + +		perf_hpp__for_each_format(fmt) { +			if (perf_hpp__should_skip(fmt)) +				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 (symbol_conf.use_callchain) { +					slsmg_printf("%c ", folded_sign); +					width -= 2; +				} +				first = false; +			} else { +				slsmg_printf("  "); +				width -= 2; +			} + +			if (fmt->color) { +				width -= fmt->color(fmt, &hpp, entry); +			} else { +				width -= fmt->entry(fmt, &hpp, entry); +				slsmg_printf("%s", s); +			} +		} + +		/* The scroll bar isn't being used */ +		if (!browser->b.navkeypressed) +			width += 1; + +		slsmg_write_nstring("", 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, +							¤t_entry); +		if (current_entry) +			browser->he_selection = entry; +	} + +	return printed; +} + +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); +	} +} + +static unsigned int hist_browser__refresh(struct ui_browser *browser) +{ +	unsigned row = 0; +	struct rb_node *nd; +	struct hist_browser *hb = container_of(browser, struct hist_browser, b); + +	ui_browser__hists_init_top(browser); + +	for (nd = browser->top; nd; nd = rb_next(nd)) { +		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); +		float percent; + +		if (h->filtered) +			continue; + +		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) +			break; +	} + +	return row; +} + +static struct rb_node *hists__filter_entries(struct rb_node *nd, +					     float min_pcnt) +{ +	while (nd != NULL) { +		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); +		float percent = hist_entry__get_percent_limit(h); + +		if (!h->filtered && percent >= min_pcnt) +			return nd; + +		nd = rb_next(nd); +	} + +	return NULL; +} + +static struct rb_node *hists__filter_prev_entries(struct rb_node *nd, +						  float min_pcnt) +{ +	while (nd != NULL) { +		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); +		float percent = hist_entry__get_percent_limit(h); + +		if (!h->filtered && percent >= min_pcnt) +			return nd; + +		nd = rb_prev(nd); +	} + +	return NULL; +} + +static void ui_browser__hists_seek(struct ui_browser *browser, +				   off_t offset, int whence) +{ +	struct hist_entry *h; +	struct rb_node *nd; +	bool first = true; +	struct hist_browser *hb; + +	hb = container_of(browser, struct hist_browser, b); + +	if (browser->nr_entries == 0) +		return; + +	ui_browser__hists_init_top(browser); + +	switch (whence) { +	case SEEK_SET: +		nd = hists__filter_entries(rb_first(browser->entries), +					   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->min_pcnt); +		first = false; +		break; +	default: +		return; +	} + +	/* +	 * Moves not relative to the first visible entry invalidates its +	 * row_offset: +	 */ +	h = rb_entry(browser->top, struct hist_entry, rb_node); +	h->row_offset = 0; + +	/* +	 * Here we have to check if nd is expanded (+), if it is we can't go +	 * the next top level hist_entry, instead we must compute an offset of +	 * what _not_ to show and not change the first visible entry. +	 * +	 * This offset increments when we are going from top to bottom and +	 * decreases when we're going from bottom to top. +	 * +	 * As we don't have backpointers to the top level in the callchains +	 * structure, we need to always print the whole hist_entry callchain, +	 * skipping the first ones that are before the first visible entry +	 * and stop when we printed enough lines to fill the screen. +	 */ +do_offset: +	if (offset > 0) { +		do { +			h = rb_entry(nd, struct hist_entry, rb_node); +			if (h->ms.unfolded) { +				u16 remaining = h->nr_rows - h->row_offset; +				if (offset > remaining) { +					offset -= remaining; +					h->row_offset = 0; +				} else { +					h->row_offset += offset; +					offset = 0; +					browser->top = nd; +					break; +				} +			} +			nd = hists__filter_entries(rb_next(nd), hb->min_pcnt); +			if (nd == NULL) +				break; +			--offset; +			browser->top = nd; +		} while (offset != 0); +	} else if (offset < 0) { +		while (1) { +			h = rb_entry(nd, struct hist_entry, rb_node); +			if (h->ms.unfolded) { +				if (first) { +					if (-offset > h->row_offset) { +						offset += h->row_offset; +						h->row_offset = 0; +					} else { +						h->row_offset += offset; +						offset = 0; +						browser->top = nd; +						break; +					} +				} else { +					if (-offset > h->nr_rows) { +						offset += h->nr_rows; +						h->row_offset = 0; +					} else { +						h->row_offset = h->nr_rows + offset; +						offset = 0; +						browser->top = nd; +						break; +					} +				} +			} + +			nd = hists__filter_prev_entries(rb_prev(nd), +							hb->min_pcnt); +			if (nd == NULL) +				break; +			++offset; +			browser->top = nd; +			if (offset == 0) { +				/* +				 * Last unfiltered hist_entry, check if it is +				 * unfolded, if it is then we should have +				 * row_offset at its last entry. +				 */ +				h = rb_entry(nd, struct hist_entry, rb_node); +				if (h->ms.unfolded) +					h->row_offset = h->nr_rows; +				break; +			} +			first = false; +		} +	} else { +		browser->top = nd; +		h = rb_entry(nd, struct hist_entry, rb_node); +		h->row_offset = 0; +	} +} + +static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser, +							struct callchain_node *chain_node, +							u64 total, int level, +							FILE *fp) +{ +	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; +	} + +	return printed; +} + +static int hist_browser__fprintf_callchain_node(struct hist_browser *browser, +						struct callchain_node *node, +						int level, FILE *fp) +{ +	struct callchain_list *chain; +	int offset = level * LEVEL_OFFSET_STEP; +	char folded_sign = ' '; +	int printed = 0; + +	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 (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; + +	for (nd = rb_first(chain); nd; nd = rb_next(nd)) { +		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + +		printed += hist_browser__fprintf_callchain_node(browser, node, level, fp); +	} + +	return printed; +} + +static int hist_browser__fprintf_entry(struct hist_browser *browser, +				       struct hist_entry *he, FILE *fp) +{ +	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; + +	if (symbol_conf.use_callchain) +		folded_sign = hist_entry__folded(he); + +	if (symbol_conf.use_callchain) +		printed += fprintf(fp, "%c ", folded_sign); + +	perf_hpp__for_each_format(fmt) { +		if (perf_hpp__should_skip(fmt)) +			continue; + +		if (!first) { +			ret = scnprintf(hpp.buf, hpp.size, "  "); +			advance_hpp(&hpp, ret); +		} else +			first = false; + +		ret = fmt->entry(fmt, &hpp, he); +		advance_hpp(&hpp, ret); +	} +	printed += fprintf(fp, "%s\n", rtrim(s)); + +	if (folded_sign == '-') +		printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp); + +	return printed; +} + +static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp) +{ +	struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries), +						   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->min_pcnt); +	} + +	return printed; +} + +static int hist_browser__dump(struct hist_browser *browser) +{ +	char filename[64]; +	FILE *fp; + +	while (1) { +		scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq); +		if (access(filename, F_OK)) +			break; +		/* + 		 * XXX: Just an arbitrary lazy upper limit + 		 */ +		if (++browser->print_seq == 8192) { +			ui_helpline__fpush("Too many perf.hist.N files, nothing written!"); +			return -1; +		} +	} + +	fp = fopen(filename, "w"); +	if (fp == NULL) { +		char bf[64]; +		const char *err = strerror_r(errno, bf, sizeof(bf)); +		ui_helpline__fpush("Couldn't write to %s: %s", filename, err); +		return -1; +	} + +	++browser->print_seq; +	hist_browser__fprintf(browser, fp); +	fclose(fp); +	ui_helpline__fpush("%s written!", filename); + +	return 0; +} + +static struct hist_browser *hist_browser__new(struct hists *hists) +{ +	struct hist_browser *browser = zalloc(sizeof(*browser)); + +	if (browser) { +		browser->hists = hists; +		browser->b.refresh = hist_browser__refresh; +		browser->b.seek = ui_browser__hists_seek; +		browser->b.use_navkeypressed = true; +	} + +	return browser; +} + +static void hist_browser__delete(struct hist_browser *browser) +{ +	free(browser); +} + +static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser) +{ +	return browser->he_selection; +} + +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) +{ +	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 (symbol_conf.filter_relative) { +		nr_samples = hists->stats.nr_non_filtered_samples; +		nr_events = hists->stats.total_non_filtered_period; +	} + +	if (perf_evsel__is_group_event(evsel)) { +		struct perf_evsel *pos; + +		perf_evsel__group_desc(evsel, buf, buflen); +		ev_name = buf; + +		for_each_group_member(pos, evsel) { +			if (symbol_conf.filter_relative) { +				nr_samples += pos->hists.stats.nr_non_filtered_samples; +				nr_events += pos->hists.stats.total_non_filtered_period; +			} else { +				nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; +				nr_events += pos->hists.stats.total_period; +			} +		} +	} + +	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); + + +	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_str(thread) : ""), +				    thread->tid); +	if (dso) +		printed += scnprintf(bf + printed, size - printed, +				    ", DSO: %s", dso->short_name); +	return printed; +} + +static inline void free_popup_options(char **options, int n) +{ +	int i; + +	for (i = 0; i < n; ++i) +		zfree(&options[i]); +} + +/* Check whether the browser is for 'top' or 'report' */ +static inline bool is_report_browser(void *timer) +{ +	return timer == NULL; +} + +/* + * Only runtime switching of perf data file will make "input_name" point + * to a malloced buffer. So add "is_input_name_malloced" flag to decide + * whether we need to call free() for current "input_name" during the switch. + */ +static bool is_input_name_malloced = false; + +static int switch_data_file(void) +{ +	char *pwd, *options[32], *abs_path[32], *tmp; +	DIR *pwd_dir; +	int nr_options = 0, choice = -1, ret = -1; +	struct dirent *dent; + +	pwd = getenv("PWD"); +	if (!pwd) +		return ret; + +	pwd_dir = opendir(pwd); +	if (!pwd_dir) +		return ret; + +	memset(options, 0, sizeof(options)); +	memset(options, 0, sizeof(abs_path)); + +	while ((dent = readdir(pwd_dir))) { +		char path[PATH_MAX]; +		u64 magic; +		char *name = dent->d_name; +		FILE *file; + +		if (!(dent->d_type == DT_REG)) +			continue; + +		snprintf(path, sizeof(path), "%s/%s", pwd, name); + +		file = fopen(path, "r"); +		if (!file) +			continue; + +		if (fread(&magic, 1, 8, file) < 8) +			goto close_file_and_continue; + +		if (is_perf_magic(magic)) { +			options[nr_options] = strdup(name); +			if (!options[nr_options]) +				goto close_file_and_continue; + +			abs_path[nr_options] = strdup(path); +			if (!abs_path[nr_options]) { +				zfree(&options[nr_options]); +				ui__warning("Can't search all data files due to memory shortage.\n"); +				fclose(file); +				break; +			} + +			nr_options++; +		} + +close_file_and_continue: +		fclose(file); +		if (nr_options >= 32) { +			ui__warning("Too many perf data files in PWD!\n" +				    "Only the first 32 files will be listed.\n"); +			break; +		} +	} +	closedir(pwd_dir); + +	if (nr_options) { +		choice = ui__popup_menu(nr_options, options); +		if (choice < nr_options && choice >= 0) { +			tmp = strdup(abs_path[choice]); +			if (tmp) { +				if (is_input_name_malloced) +					free((void *)input_name); +				input_name = tmp; +				is_input_name_malloced = true; +				ret = 0; +			} else +				ui__warning("Data switch failed due to memory shortage!\n"); +		} +	} + +	free_popup_options(options, nr_options); +	free_popup_options(abs_path, nr_options); +	return ret; +} + +static void hist_browser__update_nr_entries(struct hist_browser *hb) +{ +	u64 nr_entries = 0; +	struct rb_node *nd = rb_first(&hb->hists->entries); + +	if (hb->min_pcnt == 0) { +		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 = rb_next(nd); +	} + +	hb->nr_non_filtered_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) +{ +	struct hists *hists = &evsel->hists; +	struct hist_browser *browser = hist_browser__new(hists); +	struct branch_info *bi; +	struct pstack *fstack; +	char *options[16]; +	int nr_options = 0; +	int key = -1; +	char buf[64]; +	char script_opt[64]; +	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\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"			\ +	"d             Zoom into current DSO\n"				\ +	"E             Expand all callchains\n"				\ +	"F             Toggle percentage of filtered entries\n"		\ + +	/* help messages are sorted by lexical order of the hotkey */ +	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"; +	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" +	"/             Filter symbol by name"; + +	if (browser == NULL) +		return -1; + +	if (min_pcnt) { +		browser->min_pcnt = min_pcnt; +		hist_browser__update_nr_entries(browser); +	} + +	fstack = pstack__new(2); +	if (fstack == NULL) +		goto out; + +	ui_helpline__push(helpline); + +	memset(options, 0, sizeof(options)); + +	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; + +		nr_options = 0; + +		key = hist_browser__run(browser, ev_name, hbt); + +		if (browser->he_selection != NULL) { +			thread = hist_browser__selected_thread(browser); +			dso = browser->selection->map ? browser->selection->map->dso : NULL; +		} +		switch (key) { +		case K_TAB: +		case K_UNTAB: +			if (nr_events == 1) +				continue; +			/* +			 * Exit the browser, let hists__browser_tree +			 * go to the next or previous +			 */ +			goto out_free_stack; +		case 'a': +			if (!sort__has_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) +				continue; +			goto do_annotate; +		case 'P': +			hist_browser__dump(browser); +			continue; +		case 'd': +			goto zoom_dso; +		case 'V': +			browser->show_dso = !browser->show_dso; +			continue; +		case 't': +			goto zoom_thread; +		case '/': +			if (ui_browser__input_window("Symbol to show", +					"Please enter the name of symbol you want to see", +					buf, "ENTER: OK, ESC: Cancel", +					delay_secs * 2) == K_ENTER) { +				hists->symbol_filter_str = *buf ? buf : NULL; +				hists__filter_by_symbol(hists); +				hist_browser__reset(browser); +			} +			continue; +		case 'r': +			if (is_report_browser(hbt)) +				goto do_scripts; +			continue; +		case 's': +			if (is_report_browser(hbt)) +				goto do_data_switch; +			continue; +		case 'i': +			/* env->arch is NULL for live-mode (i.e. perf top) */ +			if (env->arch) +				tui__header_window(env); +			continue; +		case 'F': +			symbol_conf.filter_relative ^= 1; +			continue; +		case K_F1: +		case 'h': +		case '?': +			ui_browser__help_window(&browser->b, +				is_report_browser(hbt) ? report_help : top_help); +			continue; +		case K_ENTER: +		case K_RIGHT: +			/* menu */ +			break; +		case K_LEFT: { +			const void *top; + +			if (pstack__empty(fstack)) { +				/* +				 * Go back to the perf_evsel_menu__run or other user +				 */ +				if (left_exits) +					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; +			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; +		default: +			continue; +		} + +		if (!sort__has_sym) +			goto add_exit_option; + +		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) { +				struct annotation *notes; + +				notes = symbol__annotation(browser->selection->sym); + +				if (notes->src && +				    asprintf(&options[nr_options], "Annotate %s", +						 browser->selection->sym->name) > 0) +					annotate = nr_options++; +			} +		} + +		if (thread != NULL && +		    asprintf(&options[nr_options], "Zoom %s %s(%d) thread", +			     (browser->hists->thread_filter ? "out of" : "into"), +			     (thread->comm_set ? thread__comm_str(thread) : ""), +			     thread->tid) > 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++; + +		/* perf script support */ +		if (browser->he_selection) { +			struct symbol *sym; + +			if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]", +				     thread__comm_str(browser->he_selection->thread)) > 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 (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; +			struct annotation *notes; +			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; +			} + +			notes = symbol__annotation(he->ms.sym); +			if (!notes->src) +				continue; + +			/* +			 * 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 +			 */ +			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; +				perf_hpp__set_elide(HISTC_DSO, 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; +				perf_hpp__set_elide(HISTC_DSO, true); +				pstack__push(fstack, &browser->hists->dso_filter); +			} +			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; +				perf_hpp__set_elide(HISTC_THREAD, false); +			} else { +				ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", +						   thread->comm_set ? thread__comm_str(thread) : "", +						   thread->tid); +				browser->hists->thread_filter = thread; +				perf_hpp__set_elide(HISTC_THREAD, false); +				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 ", thread__comm_str(browser->he_selection->thread)); + +			if (choice == scripts_symbol) +				sprintf(script_opt, " -S %s ", browser->he_selection->ms.sym->name); + +			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; +				break; +			} else +				ui__warning("Won't switch the data files due to\n" +					"no valid data file get selected!\n"); +		} +	} +out_free_stack: +	pstack__delete(fstack); +out: +	hist_browser__delete(browser); +	free_popup_options(options, nr_options - 1); +	return key; +} + +struct perf_evsel_menu { +	struct ui_browser b; +	struct perf_evsel *selection; +	bool lost_events, lost_events_warned; +	float min_pcnt; +	struct perf_session_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); +	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); +	char bf[256], unit; +	const char *warn = " "; +	size_t printed; + +	ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : +						       HE_COLORSET_NORMAL); + +	if (perf_evsel__is_group_event(evsel)) { +		struct perf_evsel *pos; + +		ev_name = perf_evsel__group_name(evsel); + +		for_each_group_member(pos, evsel) { +			nr_events += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; +		} +	} + +	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); + +	nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST]; +	if (nr_events != 0) { +		menu->lost_events = true; +		if (!current_entry) +			ui_browser__set_color(browser, HE_COLORSET_TOP); +		nr_events = convert_unit(nr_events, &unit); +		printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!", +				     nr_events, unit, unit == ' ' ? "" : " "); +		warn = bf; +	} + +	slsmg_write_nstring(warn, browser->width - printed); + +	if (current_entry) +		menu->selection = evsel; +} + +static int perf_evsel_menu__run(struct perf_evsel_menu *menu, +				int nr_events, const char *help, +				struct hist_browser_timer *hbt) +{ +	struct perf_evlist *evlist = menu->b.priv; +	struct perf_evsel *pos; +	const char *ev_name, *title = "Available samples"; +	int delay_secs = hbt ? hbt->refresh : 0; +	int key; + +	if (ui_browser__show(&menu->b, title, +			     "ESC: exit, ENTER|->: Browse histograms") < 0) +		return -1; + +	while (1) { +		key = ui_browser__run(&menu->b, delay_secs); + +		switch (key) { +		case K_TIMER: +			hbt->timer(hbt->arg); + +			if (!menu->lost_events_warned && menu->lost_events) { +				ui_browser__warn_lost_events(&menu->b); +				menu->lost_events_warned = true; +			} +			continue; +		case K_RIGHT: +		case K_ENTER: +			if (!menu->selection) +				continue; +			pos = menu->selection; +browse_hists: +			perf_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); +			ui_browser__show_title(&menu->b, title); +			switch (key) { +			case K_TAB: +				if (pos->node.next == &evlist->entries) +					pos = perf_evlist__first(evlist); +				else +					pos = perf_evsel__next(pos); +				goto browse_hists; +			case K_UNTAB: +				if (pos->node.prev == &evlist->entries) +					pos = perf_evlist__last(evlist); +				else +					pos = perf_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 'q': +			case CTRL('c'): +				goto out; +			default: +				continue; +			} +		case K_LEFT: +			continue; +		case K_ESC: +			if (!ui_browser__dialog_yesno(&menu->b, +					       "Do you really want to exit?")) +				continue; +			/* Fall thru */ +		case 'q': +		case CTRL('c'): +			goto out; +		default: +			continue; +		} +	} + +out: +	ui_browser__hide(&menu->b); +	return key; +} + +static bool filter_group_entries(struct ui_browser *browser __maybe_unused, +				 void *entry) +{ +	struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node); + +	if (symbol_conf.event_group && !perf_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) +{ +	struct perf_evsel *pos; +	struct perf_evsel_menu menu = { +		.b = { +			.entries    = &evlist->entries, +			.refresh    = ui_browser__list_head_refresh, +			.seek	    = ui_browser__list_head_seek, +			.write	    = perf_evsel_menu__write, +			.filter	    = filter_group_entries, +			.nr_entries = nr_entries, +			.priv	    = evlist, +		}, +		.min_pcnt = min_pcnt, +		.env = env, +	}; + +	ui_helpline__push("Press ESC to exit"); + +	evlist__for_each(evlist, pos) { +		const char *ev_name = perf_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); +} + +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 nr_entries = evlist->nr_entries; + +single_entry: +	if (nr_entries == 1) { +		struct perf_evsel *first = perf_evlist__first(evlist); +		const char *ev_name = perf_evsel__name(first); + +		return perf_evsel__hists_browse(first, nr_entries, help, +						ev_name, false, hbt, min_pcnt, +						env); +	} + +	if (symbol_conf.event_group) { +		struct perf_evsel *pos; + +		nr_entries = 0; +		evlist__for_each(evlist, pos) { +			if (perf_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); +} diff --git a/tools/perf/ui/browsers/map.c b/tools/perf/ui/browsers/map.c new file mode 100644 index 00000000000..b11639f3368 --- /dev/null +++ b/tools/perf/ui/browsers/map.c @@ -0,0 +1,130 @@ +#include "../libslang.h" +#include <elf.h> +#include <inttypes.h> +#include <sys/ttydefaults.h> +#include <string.h> +#include <linux/bitops.h> +#include "../../util/util.h" +#include "../../util/debug.h" +#include "../../util/symbol.h" +#include "../browser.h" +#include "../helpline.h" +#include "../keysyms.h" +#include "map.h" + +struct map_browser { +	struct ui_browser b; +	struct map	  *map; +	u8		  addrlen; +}; + +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(browser, struct map_browser, b); +	bool current_entry = ui_browser__is_current_entry(browser, row); +	int width; + +	ui_browser__set_percent_color(browser, 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 = browser->width - ((mb->addrlen * 2) + 4); +	if (width > 0) +		slsmg_write_nstring(sym->name, width); +} + +/* FIXME uber-kludgy, see comment on cmd_report... */ +static u32 *symbol__browser_index(struct symbol *browser) +{ +	return ((void *)browser) - sizeof(struct rb_node) - sizeof(u32); +} + +static int map_browser__search(struct map_browser *browser) +{ +	char target[512]; +	struct symbol *sym; +	int err = ui_browser__input_window("Search by name/addr", +					   "Prefix with 0x to search by address", +					   target, "ENTER: OK, ESC: Cancel", 0); +	if (err != K_ENTER) +		return -1; + +	if (target[0] == '0' && tolower(target[1]) == 'x') { +		u64 addr = strtoull(target, NULL, 16); +		sym = map__find_symbol(browser->map, addr, NULL); +	} else +		sym = map__find_symbol_by_name(browser->map, target, NULL); + +	if (sym != NULL) { +		u32 *idx = symbol__browser_index(sym); + +		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 *browser) +{ +	int key; + +	if (ui_browser__show(&browser->b, browser->map->dso->long_name, +			     "Press <- or ESC to exit, %s / to search", +			     verbose ? "" : "restart with -v to use") < 0) +		return -1; + +	while (1) { +		key = ui_browser__run(&browser->b, 0); + +		switch (key) { +		case '/': +			if (verbose) +				map_browser__search(browser); +		default: +			break; +                case K_LEFT: +                case K_ESC: +                case 'q': +                case CTRL('c'): +                        goto out; +		} +	} +out: +	ui_browser__hide(&browser->b); +	return key; +} + +int map__browse(struct map *map) +{ +	struct map_browser mb = { +		.b = { +			.entries = &map->dso->symbols[map->type], +			.refresh = ui_browser__rb_tree_refresh, +			.seek	 = ui_browser__rb_tree_seek, +			.write	 = map_browser__write, +		}, +		.map = map, +	}; +	struct rb_node *nd; +	char tmp[BITS_PER_LONG / 4]; +	u64 maxaddr = 0; + +	for (nd = rb_first(mb.b.entries); nd; nd = rb_next(nd)) { +		struct symbol *pos = rb_entry(nd, struct symbol, rb_node); + +		if (maxaddr < pos->end) +			maxaddr = pos->end; +		if (verbose) { +			u32 *idx = symbol__browser_index(pos); +			*idx = mb.b.nr_entries; +		} +		++mb.b.nr_entries; +	} + +	mb.addrlen = snprintf(tmp, sizeof(tmp), "%" PRIx64, maxaddr); +	return map_browser__run(&mb); +} diff --git a/tools/perf/ui/browsers/map.h b/tools/perf/ui/browsers/map.h new file mode 100644 index 00000000000..2d58e4b3eb6 --- /dev/null +++ b/tools/perf/ui/browsers/map.h @@ -0,0 +1,6 @@ +#ifndef _PERF_UI_MAP_BROWSER_H_ +#define _PERF_UI_MAP_BROWSER_H_ 1 +struct map; + +int map__browse(struct map *map); +#endif /* _PERF_UI_MAP_BROWSER_H_ */ diff --git a/tools/perf/ui/browsers/scripts.c b/tools/perf/ui/browsers/scripts.c new file mode 100644 index 00000000000..402d2bd30b0 --- /dev/null +++ b/tools/perf/ui/browsers/scripts.c @@ -0,0 +1,187 @@ +#include <elf.h> +#include <inttypes.h> +#include <sys/ttydefaults.h> +#include <string.h> +#include "../../util/sort.h" +#include "../../util/util.h" +#include "../../util/hist.h" +#include "../../util/debug.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; +}; + +#define SCRIPT_NAMELEN	128 +#define SCRIPT_MAX_NO	64 +/* + * Usually the full path for a script is: + *	/home/username/libexec/perf-core/scripts/python/xxx.py + *	/home/username/libexec/perf-core/scripts/perl/xxx.pl + * So 256 should be long enough to contain the full path. + */ +#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; + +	/* Preset the script name to SCRIPT_NAMELEN */ +	buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN)); +	if (!buf) +		return ret; + +	for (i = 0; i < SCRIPT_MAX_NO; i++) { +		names[i] = buf + i * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN); +		paths[i] = names[i] + SCRIPT_NAMELEN; +	} + +	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; +		} +	} + +	free(buf); +	return ret; +} + +static void script_browser__write(struct ui_browser *browser, +				   void *entry, int row) +{ +	struct script_line *sline = list_entry(entry, struct script_line, node); +	bool current_entry = ui_browser__is_current_entry(browser, row); + +	ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : +						       HE_COLORSET_NORMAL); + +	slsmg_write_nstring(sline->line, browser->width); +} + +static int script_browser__run(struct perf_script_browser *browser) +{ +	int key; + +	if (ui_browser__show(&browser->b, browser->script_name, +			     "Press <- or ESC to exit") < 0) +		return -1; + +	while (1) { +		key = ui_browser__run(&browser->b, 0); + +		/* We can add some special key handling here if needed */ +		break; +	} + +	ui_browser__hide(&browser->b); +	return key; +} + + +int script_browse(const char *script_opt) +{ +	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, +	}; + +	INIT_LIST_HEAD(&script.entries); + +	/* Save each line of the output in one struct script_line object. */ +	buf = zalloc((sizeof(*sline)) * MAX_LINES); +	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); +	} + +	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'; +		} +		list_add_tail(&sline->node, &script.entries); + +		if (script.b.width < retlen) +			script.b.width = retlen; + +		if (nr_entries++ >= MAX_LINES - 1) +			break; +		sline++; +	} + +	if (script.b.width > AVERAGE_LINE_LEN) +		script.b.width = AVERAGE_LINE_LEN; + +	free(line); +	pclose(fp); + +	script.nr_lines = nr_entries; +	script.b.nr_entries = nr_entries; +	script.b.entries = &script.entries; + +	ret = script_browser__run(&script); +exit: +	free(buf); +	return ret; +} diff --git a/tools/perf/ui/gtk/annotate.c b/tools/perf/ui/gtk/annotate.c new file mode 100644 index 00000000000..9c7ff8d31b2 --- /dev/null +++ b/tools/perf/ui/gtk/annotate.c @@ -0,0 +1,252 @@ +#include "gtk.h" +#include "util/debug.h" +#include "util/annotate.h" +#include "util/evsel.h" +#include "ui/helpline.h" + + +enum { +	ANN_COL__PERCENT, +	ANN_COL__OFFSET, +	ANN_COL__LINE, + +	MAX_ANN_COLS +}; + +static const char *const col_names[] = { +	"Overhead", +	"Offset", +	"Line" +}; + +static int perf_gtk__get_percent(char *buf, size_t size, struct symbol *sym, +				 struct disasm_line *dl, int evidx) +{ +	struct sym_hist *symhist; +	double percent = 0.0; +	const char *markup; +	int ret = 0; + +	strcpy(buf, ""); + +	if (dl->offset == (s64) -1) +		return 0; + +	symhist = annotation__histogram(symbol__annotation(sym), evidx); +	if (!symbol_conf.event_group && !symhist->addr[dl->offset]) +		return 0; + +	percent = 100.0 * symhist->addr[dl->offset] / symhist->sum; + +	markup = perf_gtk__get_percent_color(percent); +	if (markup) +		ret += scnprintf(buf, size, "%s", markup); +	ret += scnprintf(buf + ret, size - ret, "%6.2f%%", percent); +	if (markup) +		ret += scnprintf(buf + ret, size - ret, "</span>"); + +	return ret; +} + +static int perf_gtk__get_offset(char *buf, size_t size, struct symbol *sym, +				struct map *map, struct disasm_line *dl) +{ +	u64 start = map__rip_2objdump(map, sym->start); + +	strcpy(buf, ""); + +	if (dl->offset == (s64) -1) +		return 0; + +	return scnprintf(buf, size, "%"PRIx64, start + dl->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); +	const char *markup = "<span fgcolor='gray'>"; + +	strcpy(buf, ""); + +	if (!line) +		return 0; + +	if (dl->offset != (s64) -1) +		markup = NULL; + +	if (markup) +		ret += scnprintf(buf, size, "%s", markup); +	ret += scnprintf(buf + ret, size - ret, "%s", line); +	if (markup) +		ret += scnprintf(buf + ret, size - ret, "</span>"); + +	g_free(line); +	return ret; +} + +static int perf_gtk__annotate_symbol(GtkWidget *window, struct symbol *sym, +				struct map *map, struct perf_evsel *evsel, +				struct hist_browser_timer *hbt __maybe_unused) +{ +	struct disasm_line *pos, *n; +	struct annotation *notes; +	GType col_types[MAX_ANN_COLS]; +	GtkCellRenderer *renderer; +	GtkListStore *store; +	GtkWidget *view; +	int i; +	char s[512]; + +	notes = symbol__annotation(sym); + +	for (i = 0; i < MAX_ANN_COLS; i++) { +		col_types[i] = G_TYPE_STRING; +	} +	store = gtk_list_store_newv(MAX_ANN_COLS, col_types); + +	view = gtk_tree_view_new(); +	renderer = gtk_cell_renderer_text_new(); + +	for (i = 0; i < MAX_ANN_COLS; i++) { +		gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), +					-1, col_names[i], renderer, "markup", +					i, NULL); +	} + +	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, ¬es->src->source, 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++) { +				ret += perf_gtk__get_percent(s + ret, +							     sizeof(s) - ret, +							     sym, pos, +							     evsel->idx + i); +				ret += scnprintf(s + ret, sizeof(s) - ret, " "); +			} +		} else { +			ret = perf_gtk__get_percent(s, sizeof(s), sym, pos, +						    evsel->idx); +		} + +		if (ret) +			gtk_list_store_set(store, &iter, ANN_COL__PERCENT, s, -1); +		if (perf_gtk__get_offset(s, sizeof(s), sym, map, 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); +	} + +	gtk_container_add(GTK_CONTAINER(window), view); + +	list_for_each_entry_safe(pos, n, ¬es->src->source, node) { +		list_del(&pos->node); +		disasm_line__free(pos); +	} + +	return 0; +} + +static int symbol__gtk_annotate(struct symbol *sym, struct map *map, +				struct perf_evsel *evsel, +				struct hist_browser_timer *hbt) +{ +	GtkWidget *window; +	GtkWidget *notebook; +	GtkWidget *scrolled_window; +	GtkWidget *tab_label; + +	if (map->dso->annotate_warned) +		return -1; + +	if (symbol__annotate(sym, map, 0) < 0) { +		ui__error("%s", ui_helpline__current); +		return -1; +	} + +	if (perf_gtk__is_active_context(pgctx)) { +		window = pgctx->main_window; +		notebook = pgctx->notebook; +	} else { +		GtkWidget *vbox; +		GtkWidget *infobar; +		GtkWidget *statbar; + +		signal(SIGSEGV, perf_gtk__signal); +		signal(SIGFPE,  perf_gtk__signal); +		signal(SIGINT,  perf_gtk__signal); +		signal(SIGQUIT, perf_gtk__signal); +		signal(SIGTERM, perf_gtk__signal); + +		window = gtk_window_new(GTK_WINDOW_TOPLEVEL); +		gtk_window_set_title(GTK_WINDOW(window), "perf annotate"); + +		g_signal_connect(window, "delete_event", gtk_main_quit, NULL); + +		pgctx = perf_gtk__activate_context(window); +		if (!pgctx) +			return -1; + +		vbox = gtk_vbox_new(FALSE, 0); +		notebook = gtk_notebook_new(); +		pgctx->notebook = notebook; + +		gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); + +		infobar = perf_gtk__setup_info_bar(); +		if (infobar) { +			gtk_box_pack_start(GTK_BOX(vbox), infobar, +					   FALSE, FALSE, 0); +		} + +		statbar = perf_gtk__setup_statusbar(); +		gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0); + +		gtk_container_add(GTK_CONTAINER(window), vbox); +	} + +	scrolled_window = gtk_scrolled_window_new(NULL, NULL); +	tab_label = gtk_label_new(sym->name); + +	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), +				       GTK_POLICY_AUTOMATIC, +				       GTK_POLICY_AUTOMATIC); + +	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window, +				 tab_label); + +	perf_gtk__annotate_symbol(scrolled_window, sym, map, evsel, hbt); +	return 0; +} + +int hist_entry__gtk_annotate(struct hist_entry *he, +			     struct perf_evsel *evsel, +			     struct hist_browser_timer *hbt) +{ +	return symbol__gtk_annotate(he->ms.sym, he->ms.map, evsel, hbt); +} + +void perf_gtk__show_annotations(void) +{ +	GtkWidget *window; + +	if (!perf_gtk__is_active_context(pgctx)) +		return; + +	window = pgctx->main_window; +	gtk_widget_show_all(window); + +	perf_gtk__resize_window(window); +	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + +	gtk_main(); + +	perf_gtk__deactivate_context(&pgctx); +} diff --git a/tools/perf/ui/gtk/browser.c b/tools/perf/ui/gtk/browser.c new file mode 100644 index 00000000000..c24d9122129 --- /dev/null +++ b/tools/perf/ui/gtk/browser.c @@ -0,0 +1,87 @@ +#include "../evlist.h" +#include "../cache.h" +#include "../evsel.h" +#include "../sort.h" +#include "../hist.h" +#include "../helpline.h" +#include "gtk.h" + +#include <signal.h> + +void perf_gtk__signal(int sig) +{ +	perf_gtk__exit(false); +	psignal(sig, "perf"); +} + +void perf_gtk__resize_window(GtkWidget *window) +{ +	GdkRectangle rect; +	GdkScreen *screen; +	int monitor; +	int height; +	int width; + +	screen = gtk_widget_get_screen(window); + +	monitor = gdk_screen_get_monitor_at_window(screen, window->window); + +	gdk_screen_get_monitor_geometry(screen, monitor, &rect); + +	width	= rect.width * 3 / 4; +	height	= rect.height * 3 / 4; + +	gtk_window_resize(GTK_WINDOW(window), width, height); +} + +const char *perf_gtk__get_percent_color(double percent) +{ +	if (percent >= MIN_RED) +		return "<span fgcolor='red'>"; +	if (percent >= MIN_GREEN) +		return "<span fgcolor='dark green'>"; +	return NULL; +} + +#ifdef HAVE_GTK_INFO_BAR_SUPPORT +GtkWidget *perf_gtk__setup_info_bar(void) +{ +	GtkWidget *info_bar; +	GtkWidget *label; +	GtkWidget *content_area; + +	info_bar = gtk_info_bar_new(); +	gtk_widget_set_no_show_all(info_bar, TRUE); + +	label = gtk_label_new(""); +	gtk_widget_show(label); + +	content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar)); +	gtk_container_add(GTK_CONTAINER(content_area), label); + +	gtk_info_bar_add_button(GTK_INFO_BAR(info_bar), GTK_STOCK_OK, +				GTK_RESPONSE_OK); +	g_signal_connect(info_bar, "response", +			 G_CALLBACK(gtk_widget_hide), NULL); + +	pgctx->info_bar = info_bar; +	pgctx->message_label = label; + +	return info_bar; +} +#endif + +GtkWidget *perf_gtk__setup_statusbar(void) +{ +	GtkWidget *stbar; +	unsigned ctxid; + +	stbar = gtk_statusbar_new(); + +	ctxid = gtk_statusbar_get_context_id(GTK_STATUSBAR(stbar), +					     "perf report"); +	pgctx->statbar = stbar; +	pgctx->statbar_ctx_id = ctxid; + +	return stbar; +} diff --git a/tools/perf/ui/gtk/gtk.h b/tools/perf/ui/gtk/gtk.h new file mode 100644 index 00000000000..0a9173ff9a6 --- /dev/null +++ b/tools/perf/ui/gtk/gtk.h @@ -0,0 +1,67 @@ +#ifndef _PERF_GTK_H_ +#define _PERF_GTK_H_ 1 + +#include <stdbool.h> + +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#include <gtk/gtk.h> +#pragma GCC diagnostic error "-Wstrict-prototypes" + + +struct perf_gtk_context { +	GtkWidget *main_window; +	GtkWidget *notebook; + +#ifdef HAVE_GTK_INFO_BAR_SUPPORT +	GtkWidget *info_bar; +	GtkWidget *message_label; +#endif +	GtkWidget *statbar; +	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) +{ +	return ctx && ctx->main_window; +} + +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 gtk_ui_progress__init(void); +void perf_gtk__init_hpp(void); + +void perf_gtk__signal(int sig); +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_SUPPORT +GtkWidget *perf_gtk__setup_info_bar(void); +#else +static inline GtkWidget *perf_gtk__setup_info_bar(void) +{ +	return NULL; +} +#endif + +struct perf_evsel; +struct perf_evlist; +struct hist_entry; +struct hist_browser_timer; + +int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, const char *help, +				  struct hist_browser_timer *hbt, +				  float min_pcnt); +int hist_entry__gtk_annotate(struct hist_entry *he, +			     struct perf_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 new file mode 100644 index 00000000000..3388cbd1218 --- /dev/null +++ b/tools/perf/ui/gtk/helpline.c @@ -0,0 +1,57 @@ +#include <stdio.h> +#include <string.h> + +#include "gtk.h" +#include "../ui.h" +#include "../helpline.h" +#include "../../util/debug.h" + +static void gtk_helpline_pop(void) +{ +	if (!perf_gtk__is_active_context(pgctx)) +		return; + +	gtk_statusbar_pop(GTK_STATUSBAR(pgctx->statbar), +			  pgctx->statbar_ctx_id); +} + +static void gtk_helpline_push(const char *msg) +{ +	if (!perf_gtk__is_active_context(pgctx)) +		return; + +	gtk_statusbar_push(GTK_STATUSBAR(pgctx->statbar), +			   pgctx->statbar_ctx_id, msg); +} + +static int gtk_helpline_show(const char *fmt, va_list ap) +{ +	int ret; +	char *ptr; +	static int backlog; + +	ret = vscnprintf(ui_helpline__current + backlog, +			 sizeof(ui_helpline__current) - backlog, fmt, ap); +	backlog += ret; + +	/* only first line can be displayed */ +	ptr = strchr(ui_helpline__current, '\n'); +	if (ptr && (ptr - ui_helpline__current) <= backlog) { +		*ptr = '\0'; +		ui_helpline__puts(ui_helpline__current); +		backlog = 0; +	} + +	return ret; +} + +static struct ui_helpline gtk_helpline_fns = { +	.pop	= gtk_helpline_pop, +	.push	= gtk_helpline_push, +	.show	= gtk_helpline_show, +}; + +void perf_gtk__init_helpline(void) +{ +	helpline_fns = >k_helpline_fns; +} diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c new file mode 100644 index 00000000000..6ca60e482cd --- /dev/null +++ b/tools/perf/ui/gtk/hists.c @@ -0,0 +1,365 @@ +#include "../evlist.h" +#include "../cache.h" +#include "../evsel.h" +#include "../sort.h" +#include "../hist.h" +#include "../helpline.h" +#include "gtk.h" + +#define MAX_COLUMNS			32 + +static int __percent_color_snprintf(struct perf_hpp *hpp, const char *fmt, ...) +{ +	int ret = 0; +	va_list args; +	double percent; +	const char *markup; +	char *buf = hpp->buf; +	size_t size = hpp->size; + +	va_start(args, fmt); +	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, fmt, percent); + +	if (markup) +		ret += scnprintf(buf + ret, size - ret, "</span>"); + +	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_fmt *fmt __maybe_unused,	\ +				       struct perf_hpp *hpp,			\ +				       struct hist_entry *he)			\ +{										\ +	return __hpp__fmt(hpp, he, he_get_##_field, " %6.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 __maybe_unused,	\ +				       struct perf_hpp *hpp,			\ +				       struct hist_entry *he)			\ +{										\ +	return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, " %6.2f%%",		\ +			      __percent_color_snprintf, true);			\ +} + +__HPP_COLOR_PERCENT_FN(overhead, period) +__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__format[PERF_HPP__OVERHEAD].color = +				perf_gtk__hpp_color_overhead; +	perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color = +				perf_gtk__hpp_color_overhead_sys; +	perf_hpp__format[PERF_HPP__OVERHEAD_US].color = +				perf_gtk__hpp_color_overhead_us; +	perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color = +				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 callchain_list__sym_name(struct callchain_list *cl, +				     char *bf, size_t bfsize) +{ +	if (cl->ms.sym) +		scnprintf(bf, bfsize, "%s", cl->ms.sym->name); +	else +		scnprintf(bf, bfsize, "%#" PRIx64, cl->ip); +} + +static void perf_gtk__add_callchain(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; +		double percent; +		u64 hits, child_total; + +		node = rb_entry(nd, struct callchain_node, rb_node); + +		hits = callchain_cumul_hits(node); +		percent = 100.0 * hits / total; + +		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); + +			scnprintf(buf, sizeof(buf), "%5.2f%%", percent); +			gtk_tree_store_set(store, &iter, 0, buf, -1); + +			callchain_list__sym_name(chain, buf, sizeof(buf)); +			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(&node->rb_root, store, &iter, col, +					child_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, +				 float min_pcnt) +{ +	struct perf_hpp_fmt *fmt; +	GType col_types[MAX_COLUMNS]; +	GtkCellRenderer *renderer; +	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), +	}; + +	nr_cols = 0; + +	perf_hpp__for_each_format(fmt) +		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; + +	perf_hpp__for_each_format(fmt) { +		if (perf_hpp__should_skip(fmt)) +			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; + +		fmt->header(fmt, &hpp, hists_to_evsel(hists)); + +		gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), +							    -1, ltrim(s), +							    renderer, "markup", +							    col_idx++, NULL); +	} + +	for (col_idx = 0; col_idx < nr_cols; col_idx++) { +		GtkTreeViewColumn *column; + +		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)) { +		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); +		GtkTreeIter iter; +		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; + +		perf_hpp__for_each_format(fmt) { +			if (perf_hpp__should_skip(fmt)) +				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 (symbol_conf.use_callchain && sort__has_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); +} + +int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, +				  const char *help, +				  struct hist_browser_timer *hbt __maybe_unused, +				  float min_pcnt) +{ +	struct perf_evsel *pos; +	GtkWidget *vbox; +	GtkWidget *notebook; +	GtkWidget *info_bar; +	GtkWidget *statbar; +	GtkWidget *window; + +	signal(SIGSEGV, perf_gtk__signal); +	signal(SIGFPE,  perf_gtk__signal); +	signal(SIGINT,  perf_gtk__signal); +	signal(SIGQUIT, perf_gtk__signal); +	signal(SIGTERM, perf_gtk__signal); + +	window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + +	gtk_window_set_title(GTK_WINDOW(window), "perf report"); + +	g_signal_connect(window, "delete_event", gtk_main_quit, NULL); + +	pgctx = perf_gtk__activate_context(window); +	if (!pgctx) +		return -1; + +	vbox = gtk_vbox_new(FALSE, 0); + +	notebook = gtk_notebook_new(); + +	gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); + +	info_bar = perf_gtk__setup_info_bar(); +	if (info_bar) +		gtk_box_pack_start(GTK_BOX(vbox), info_bar, FALSE, FALSE, 0); + +	statbar = perf_gtk__setup_statusbar(); +	gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0); + +	gtk_container_add(GTK_CONTAINER(window), vbox); + +	evlist__for_each(evlist, pos) { +		struct hists *hists = &pos->hists; +		const char *evname = perf_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)) +				continue; + +			if (pos->nr_members > 1) { +				perf_evsel__group_desc(pos, buf, size); +				evname = buf; +			} +		} + +		scrolled_window = gtk_scrolled_window_new(NULL, NULL); + +		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), +							GTK_POLICY_AUTOMATIC, +							GTK_POLICY_AUTOMATIC); + +		perf_gtk__show_hists(scrolled_window, hists, min_pcnt); + +		tab_label = gtk_label_new(evname); + +		gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window, tab_label); +	} + +	gtk_widget_show_all(window); + +	perf_gtk__resize_window(window); + +	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + +	ui_helpline__push(help); + +	gtk_main(); + +	perf_gtk__deactivate_context(&pgctx); + +	return 0; +} diff --git a/tools/perf/ui/gtk/progress.c b/tools/perf/ui/gtk/progress.c new file mode 100644 index 00000000000..b656655fbc3 --- /dev/null +++ b/tools/perf/ui/gtk/progress.c @@ -0,0 +1,59 @@ +#include <inttypes.h> + +#include "gtk.h" +#include "../progress.h" +#include "util.h" + +static GtkWidget *dialog; +static GtkWidget *progress; + +static void gtk_ui_progress__update(struct ui_progress *p) +{ +	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(p->title); + +		dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); +		progress = gtk_progress_bar_new(); + +		gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, FALSE, 3); +		gtk_box_pack_start(GTK_BOX(vbox), progress, TRUE, TRUE, 3); + +		gtk_container_add(GTK_CONTAINER(dialog), vbox); + +		gtk_window_set_title(GTK_WINDOW(dialog), "perf"); +		gtk_window_resize(GTK_WINDOW(dialog), 300, 80); +		gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); + +		gtk_widget_show_all(dialog); +	} + +	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction); +	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 */ +	while (gtk_events_pending()) +		gtk_main_iteration(); +} + +static void gtk_ui_progress__finish(void) +{ +	/* this will also destroy all of its children */ +	gtk_widget_destroy(dialog); + +	dialog = NULL; +} + +static struct ui_progress_ops gtk_ui_progress__ops = { +	.update		= gtk_ui_progress__update, +	.finish		= gtk_ui_progress__finish, +}; + +void gtk_ui_progress__init(void) +{ +	ui_progress__ops = >k_ui_progress__ops; +} diff --git a/tools/perf/ui/gtk/setup.c b/tools/perf/ui/gtk/setup.c new file mode 100644 index 00000000000..1d57676f821 --- /dev/null +++ b/tools/perf/ui/gtk/setup.c @@ -0,0 +1,23 @@ +#include "gtk.h" +#include "../../util/cache.h" +#include "../../util/debug.h" + +extern struct perf_error_ops perf_gtk_eops; + +int perf_gtk__init(void) +{ +	perf_error__register(&perf_gtk_eops); +	perf_gtk__init_helpline(); +	gtk_ui_progress__init(); +	perf_gtk__init_hpp(); + +	return gtk_init_check(NULL, NULL) ? 0 : -1; +} + +void perf_gtk__exit(bool wait_for_ok __maybe_unused) +{ +	if (!perf_gtk__is_active_context(pgctx)) +		return; +	perf_error__unregister(&perf_gtk_eops); +	gtk_main_quit(); +} diff --git a/tools/perf/ui/gtk/util.c b/tools/perf/ui/gtk/util.c new file mode 100644 index 00000000000..52e7fc48af9 --- /dev/null +++ b/tools/perf/ui/gtk/util.c @@ -0,0 +1,112 @@ +#include "../util.h" +#include "../../util/debug.h" +#include "gtk.h" + +#include <string.h> + + +struct perf_gtk_context *pgctx; + +struct perf_gtk_context *perf_gtk__activate_context(GtkWidget *window) +{ +	struct perf_gtk_context *ctx; + +	ctx = malloc(sizeof(*pgctx)); +	if (ctx) +		ctx->main_window = window; + +	return ctx; +} + +int perf_gtk__deactivate_context(struct perf_gtk_context **ctx) +{ +	if (!perf_gtk__is_active_context(*ctx)) +		return -1; + +	zfree(ctx); +	return 0; +} + +static int perf_gtk__error(const char *format, va_list args) +{ +	char *msg; +	GtkWidget *dialog; + +	if (!perf_gtk__is_active_context(pgctx) || +	    vasprintf(&msg, format, args) < 0) { +		fprintf(stderr, "Error:\n"); +		vfprintf(stderr, format, args); +		fprintf(stderr, "\n"); +		return -1; +	} + +	dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(pgctx->main_window), +					GTK_DIALOG_DESTROY_WITH_PARENT, +					GTK_MESSAGE_ERROR, +					GTK_BUTTONS_CLOSE, +					"<b>Error</b>\n\n%s", msg); +	gtk_dialog_run(GTK_DIALOG(dialog)); + +	gtk_widget_destroy(dialog); +	free(msg); +	return 0; +} + +#ifdef HAVE_GTK_INFO_BAR_SUPPORT +static int perf_gtk__warning_info_bar(const char *format, va_list args) +{ +	char *msg; + +	if (!perf_gtk__is_active_context(pgctx) || +	    vasprintf(&msg, format, args) < 0) { +		fprintf(stderr, "Warning:\n"); +		vfprintf(stderr, format, args); +		fprintf(stderr, "\n"); +		return -1; +	} + +	gtk_label_set_text(GTK_LABEL(pgctx->message_label), msg); +	gtk_info_bar_set_message_type(GTK_INFO_BAR(pgctx->info_bar), +				      GTK_MESSAGE_WARNING); +	gtk_widget_show(pgctx->info_bar); + +	free(msg); +	return 0; +} +#else +static int perf_gtk__warning_statusbar(const char *format, va_list args) +{ +	char *msg, *p; + +	if (!perf_gtk__is_active_context(pgctx) || +	    vasprintf(&msg, format, args) < 0) { +		fprintf(stderr, "Warning:\n"); +		vfprintf(stderr, format, args); +		fprintf(stderr, "\n"); +		return -1; +	} + +	gtk_statusbar_pop(GTK_STATUSBAR(pgctx->statbar), +			  pgctx->statbar_ctx_id); + +	/* Only first line can be displayed */ +	p = strchr(msg, '\n'); +	if (p) +		*p = '\0'; + +	gtk_statusbar_push(GTK_STATUSBAR(pgctx->statbar), +			   pgctx->statbar_ctx_id, msg); + +	free(msg); +	return 0; +} +#endif + +struct perf_error_ops perf_gtk_eops = { +	.error		= perf_gtk__error, +#ifdef HAVE_GTK_INFO_BAR_SUPPORT +	.warning	= perf_gtk__warning_info_bar, +#else +	.warning	= perf_gtk__warning_statusbar, +#endif +}; diff --git a/tools/perf/ui/helpline.c b/tools/perf/ui/helpline.c new file mode 100644 index 00000000000..700fb3cfa1c --- /dev/null +++ b/tools/perf/ui/helpline.c @@ -0,0 +1,73 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../debug.h" +#include "helpline.h" +#include "ui.h" + +char ui_helpline__current[512]; + +static void nop_helpline__pop(void) +{ +} + +static void nop_helpline__push(const char *msg __maybe_unused) +{ +} + +static int nop_helpline__show(const char *fmt __maybe_unused, +			       va_list ap __maybe_unused) +{ +	return 0; +} + +static struct ui_helpline default_helpline_fns = { +	.pop	= nop_helpline__pop, +	.push	= nop_helpline__push, +	.show	= nop_helpline__show, +}; + +struct ui_helpline *helpline_fns = &default_helpline_fns; + +void ui_helpline__pop(void) +{ +	helpline_fns->pop(); +} + +void ui_helpline__push(const char *msg) +{ +	helpline_fns->push(msg); +} + +void ui_helpline__vpush(const char *fmt, va_list ap) +{ +	char *s; + +	if (vasprintf(&s, fmt, ap) < 0) +		vfprintf(stderr, fmt, ap); +	else { +		ui_helpline__push(s); +		free(s); +	} +} + +void ui_helpline__fpush(const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	ui_helpline__vpush(fmt, ap); +	va_end(ap); +} + +void ui_helpline__puts(const char *msg) +{ +	ui_helpline__pop(); +	ui_helpline__push(msg); +} + +int ui_helpline__vshow(const char *fmt, va_list ap) +{ +	return helpline_fns->show(fmt, ap); +} diff --git a/tools/perf/ui/helpline.h b/tools/perf/ui/helpline.h new file mode 100644 index 00000000000..46181f4fc07 --- /dev/null +++ b/tools/perf/ui/helpline.h @@ -0,0 +1,29 @@ +#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); +	int  (*show)(const char *fmt, va_list ap); +}; + +extern struct ui_helpline *helpline_fns; + +void ui_helpline__init(void); + +void ui_helpline__pop(void); +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); +int  ui_helpline__vshow(const char *fmt, va_list ap); + +extern char ui_helpline__current[512]; +extern char ui_helpline__last_msg[]; + +#endif /* _PERF_UI_HELPLINE_H_ */ diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c new file mode 100644 index 00000000000..498adb23c02 --- /dev/null +++ b/tools/perf/ui/hist.c @@ -0,0 +1,624 @@ +#include <math.h> +#include <linux/compiler.h> + +#include "../util/hist.h" +#include "../util/util.h" +#include "../util/sort.h" +#include "../util/evsel.h" + +/* hist period print (hpp) functions */ + +#define hpp__call_print_fn(hpp, fn, fmt, ...)			\ +({								\ +	int __ret = fn(hpp, fmt, ##__VA_ARGS__);		\ +	advance_hpp(hpp, __ret);				\ +	__ret;							\ +}) + +int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, +	       hpp_field_fn get_field, const char *fmt, +	       hpp_snprint_fn print_fn, bool fmt_percent) +{ +	int ret; +	struct hists *hists = he->hists; +	struct perf_evsel *evsel = hists_to_evsel(hists); +	char *buf = hpp->buf; +	size_t size = hpp->size; + +	if (fmt_percent) { +		double percent = 0.0; +		u64 total = hists__total_period(hists); + +		if (total) +			percent = 100.0 * get_field(he) / total; + +		ret = hpp__call_print_fn(hpp, print_fn, fmt, percent); +	} else +		ret = hpp__call_print_fn(hpp, print_fn, fmt, get_field(he)); + +	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 = hists__total_period(pair->hists); + +			if (!total) +				continue; + +			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 +				 */ +				if (fmt_percent) { +					ret += hpp__call_print_fn(hpp, print_fn, +								  fmt, 0.0); +				} else { +					ret += hpp__call_print_fn(hpp, print_fn, +								  fmt, 0ULL); +				} +			} + +			if (fmt_percent) { +				ret += hpp__call_print_fn(hpp, print_fn, fmt, +							  100.0 * period / total); +			} else { +				ret += hpp__call_print_fn(hpp, print_fn, fmt, +							  period); +			} + +			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 +			 */ +			if (fmt_percent) { +				ret += hpp__call_print_fn(hpp, print_fn, +							  fmt, 0.0); +			} else { +				ret += hpp__call_print_fn(hpp, print_fn, +							  fmt, 0ULL); +			} +		} +	} + +	/* +	 * Restore original buf and size as it's where caller expects +	 * the result will be saved. +	 */ +	hpp->buf = buf; +	hpp->size = size; + +	return ret; +} + +int __hpp__fmt_acc(struct perf_hpp *hpp, struct hist_entry *he, +		   hpp_field_fn get_field, const char *fmt, +		   hpp_snprint_fn print_fn, bool fmt_percent) +{ +	if (!symbol_conf.cumulate_callchain) { +		return snprintf(hpp->buf, hpp->size, "%*s", +				fmt_percent ? 8 : 12, "N/A"); +	} + +	return __hpp__fmt(hpp, he, get_field, fmt, print_fn, fmt_percent); +} + +static int field_cmp(u64 field_a, u64 field_b) +{ +	if (field_a > field_b) +		return 1; +	if (field_a < field_b) +		return -1; +	return 0; +} + +static int __hpp__sort(struct hist_entry *a, struct hist_entry *b, +		       hpp_field_fn get_field) +{ +	s64 ret; +	int i, nr_members; +	struct perf_evsel *evsel; +	struct hist_entry *pair; +	u64 *fields_a, *fields_b; + +	ret = field_cmp(get_field(a), get_field(b)); +	if (ret || !symbol_conf.event_group) +		return ret; + +	evsel = hists_to_evsel(a->hists); +	if (!perf_evsel__is_group_event(evsel)) +		return ret; + +	nr_members = evsel->nr_members; +	fields_a = calloc(sizeof(*fields_a), nr_members); +	fields_b = calloc(sizeof(*fields_b), nr_members); + +	if (!fields_a || !fields_b) +		goto out; + +	list_for_each_entry(pair, &a->pairs.head, pairs.node) { +		evsel = hists_to_evsel(pair->hists); +		fields_a[perf_evsel__group_idx(evsel)] = get_field(pair); +	} + +	list_for_each_entry(pair, &b->pairs.head, pairs.node) { +		evsel = hists_to_evsel(pair->hists); +		fields_b[perf_evsel__group_idx(evsel)] = get_field(pair); +	} + +	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__sort_acc(struct hist_entry *a, struct hist_entry *b, +			   hpp_field_fn get_field) +{ +	s64 ret = 0; + +	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; + +		ret = b->callchain->max_depth - a->callchain->max_depth; +	} +	return ret; +} + +#define __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) 		\ +static int hpp__header_##_type(struct perf_hpp_fmt *fmt __maybe_unused,	\ +			       struct perf_hpp *hpp,			\ +			       struct perf_evsel *evsel)		\ +{									\ +	int len = _min_width;						\ +									\ +	if (symbol_conf.event_group)					\ +		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_fmt *fmt __maybe_unused,	\ +			      struct perf_hpp *hpp __maybe_unused,	\ +			      struct perf_evsel *evsel)			\ +{									\ +	int len = _min_width;						\ +									\ +	if (symbol_conf.event_group)					\ +		len = max(len, evsel->nr_members * _unit_width);	\ +									\ +	return len;							\ +} + +static int hpp_color_scnprintf(struct perf_hpp *hpp, const char *fmt, ...) +{ +	va_list args; +	ssize_t ssize = hpp->size; +	double percent; +	int ret; + +	va_start(args, fmt); +	percent = va_arg(args, double); +	ret = value_color_snprintf(hpp->buf, hpp->size, fmt, percent); +	va_end(args); + +	return (ret >= ssize) ? (ssize - 1) : ret; +} + +static int hpp_entry_scnprintf(struct perf_hpp *hpp, const char *fmt, ...) +{ +	va_list args; +	ssize_t ssize = hpp->size; +	int ret; + +	va_start(args, fmt); +	ret = vsnprintf(hpp->buf, hpp->size, fmt, args); +	va_end(args); + +	return (ret >= ssize) ? (ssize - 1) : ret; +} + +#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_fmt *fmt __maybe_unused,		\ +			      struct perf_hpp *hpp, struct hist_entry *he) 	\ +{										\ +	return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%",			\ +			  hpp_color_scnprintf, true);				\ +} + +#define __HPP_ENTRY_PERCENT_FN(_type, _field)					\ +static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused,		\ +			      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,			\ +			  hpp_entry_scnprintf, true);				\ +} + +#define __HPP_SORT_FN(_type, _field)						\ +static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b)	\ +{										\ +	return __hpp__sort(a, b, he_get_##_field);				\ +} + +#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 hpp__color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,		\ +			      struct perf_hpp *hpp, struct hist_entry *he) 	\ +{										\ +	return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, " %6.2f%%",		\ +			      hpp_color_scnprintf, true);			\ +} + +#define __HPP_ENTRY_ACC_PERCENT_FN(_type, _field)				\ +static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused,		\ +			      struct perf_hpp *hpp, struct hist_entry *he) 	\ +{										\ +	const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%";		\ +	return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, fmt,		\ +			      hpp_entry_scnprintf, true);			\ +} + +#define __HPP_SORT_ACC_FN(_type, _field)					\ +static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b)	\ +{										\ +	return __hpp__sort_acc(a, b, he_get_acc_##_field);			\ +} + +#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 __maybe_unused,		\ +			      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,			\ +			  hpp_entry_scnprintf, false);				\ +} + +#define __HPP_SORT_RAW_FN(_type, _field)					\ +static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b)	\ +{										\ +	return __hpp__sort(a, b, he_get_raw_##_field);				\ +} + + +#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)					\ +__HPP_SORT_FN(_type, _field) + +#define HPP_PERCENT_ACC_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_ACC_PERCENT_FN(_type, _field)				\ +__HPP_ENTRY_ACC_PERCENT_FN(_type, _field)				\ +__HPP_SORT_ACC_FN(_type, _field) + +#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)					\ +__HPP_SORT_RAW_FN(_type, _field) + +__HPP_HEADER_FN(overhead_self, "Self", 8, 8) + +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) +HPP_PERCENT_ACC_FNS(overhead_acc, "Children", period, 8, 8) + +HPP_RAW_FNS(samples, "Samples", nr_events, 12, 12) +HPP_RAW_FNS(period, "Period", period, 12, 12) + +static int64_t hpp__nop_cmp(struct hist_entry *a __maybe_unused, +			    struct hist_entry *b __maybe_unused) +{ +	return 0; +} + +#define HPP__COLOR_PRINT_FNS(_name)			\ +	{						\ +		.header	= hpp__header_ ## _name,	\ +		.width	= hpp__width_ ## _name,		\ +		.color	= hpp__color_ ## _name,		\ +		.entry	= hpp__entry_ ## _name,		\ +		.cmp	= hpp__nop_cmp,			\ +		.collapse = hpp__nop_cmp,		\ +		.sort	= hpp__sort_ ## _name,		\ +	} + +#define HPP__COLOR_ACC_PRINT_FNS(_name)			\ +	{						\ +		.header	= hpp__header_ ## _name,	\ +		.width	= hpp__width_ ## _name,		\ +		.color	= hpp__color_ ## _name,		\ +		.entry	= hpp__entry_ ## _name,		\ +		.cmp	= hpp__nop_cmp,			\ +		.collapse = hpp__nop_cmp,		\ +		.sort	= hpp__sort_ ## _name,		\ +	} + +#define HPP__PRINT_FNS(_name)				\ +	{						\ +		.header	= hpp__header_ ## _name,	\ +		.width	= hpp__width_ ## _name,		\ +		.entry	= hpp__entry_ ## _name,		\ +		.cmp	= hpp__nop_cmp,			\ +		.collapse = hpp__nop_cmp,		\ +		.sort	= hpp__sort_ ## _name,		\ +	} + +struct perf_hpp_fmt perf_hpp__format[] = { +	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__COLOR_ACC_PRINT_FNS(overhead_acc), +	HPP__PRINT_FNS(samples), +	HPP__PRINT_FNS(period) +}; + +LIST_HEAD(perf_hpp__list); +LIST_HEAD(perf_hpp__sort_list); + + +#undef HPP__COLOR_PRINT_FNS +#undef HPP__COLOR_ACC_PRINT_FNS +#undef HPP__PRINT_FNS + +#undef HPP_PERCENT_FNS +#undef HPP_PERCENT_ACC_FNS +#undef HPP_RAW_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_SORT_FN +#undef __HPP_SORT_ACC_FN +#undef __HPP_SORT_RAW_FN + + +void perf_hpp__init(void) +{ +	struct list_head *list; +	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 (field_order) +		return; + +	if (symbol_conf.cumulate_callchain) { +		perf_hpp__column_enable(PERF_HPP__OVERHEAD_ACC); + +		perf_hpp__format[PERF_HPP__OVERHEAD].header = +						hpp__header_overhead_self; +	} + +	perf_hpp__column_enable(PERF_HPP__OVERHEAD); + +	if (symbol_conf.show_cpu_utilization) { +		perf_hpp__column_enable(PERF_HPP__OVERHEAD_SYS); +		perf_hpp__column_enable(PERF_HPP__OVERHEAD_US); + +		if (perf_guest) { +			perf_hpp__column_enable(PERF_HPP__OVERHEAD_GUEST_SYS); +			perf_hpp__column_enable(PERF_HPP__OVERHEAD_GUEST_US); +		} +	} + +	if (symbol_conf.show_nr_samples) +		perf_hpp__column_enable(PERF_HPP__SAMPLES); + +	if (symbol_conf.show_total_period) +		perf_hpp__column_enable(PERF_HPP__PERIOD); + +	/* prepend overhead field for backward compatiblity.  */ +	list = &perf_hpp__format[PERF_HPP__OVERHEAD].sort_list; +	if (list_empty(list)) +		list_add(list, &perf_hpp__sort_list); + +	if (symbol_conf.cumulate_callchain) { +		list = &perf_hpp__format[PERF_HPP__OVERHEAD_ACC].sort_list; +		if (list_empty(list)) +			list_add(list, &perf_hpp__sort_list); +	} +} + +void perf_hpp__column_register(struct perf_hpp_fmt *format) +{ +	list_add_tail(&format->list, &perf_hpp__list); +} + +void perf_hpp__column_unregister(struct perf_hpp_fmt *format) +{ +	list_del(&format->list); +} + +void perf_hpp__register_sort_field(struct perf_hpp_fmt *format) +{ +	list_add_tail(&format->sort_list, &perf_hpp__sort_list); +} + +void perf_hpp__column_enable(unsigned col) +{ +	BUG_ON(col >= PERF_HPP__MAX_INDEX); +	perf_hpp__column_register(&perf_hpp__format[col]); +} + +void perf_hpp__column_disable(unsigned col) +{ +	BUG_ON(col >= PERF_HPP__MAX_INDEX); +	perf_hpp__column_unregister(&perf_hpp__format[col]); +} + +void perf_hpp__cancel_cumulate(void) +{ +	if (field_order) +		return; + +	perf_hpp__column_disable(PERF_HPP__OVERHEAD_ACC); +	perf_hpp__format[PERF_HPP__OVERHEAD].header = hpp__header_overhead; +} + +void perf_hpp__setup_output_field(void) +{ +	struct perf_hpp_fmt *fmt; + +	/* append sort keys to output field */ +	perf_hpp__for_each_sort_list(fmt) { +		if (!list_empty(&fmt->list)) +			continue; + +		/* +		 * sort entry fields are dynamically created, +		 * so they can share a same sort key even though +		 * the list is empty. +		 */ +		if (perf_hpp__is_sort_entry(fmt)) { +			struct perf_hpp_fmt *pos; + +			perf_hpp__for_each_format(pos) { +				if (perf_hpp__same_sort_entry(pos, fmt)) +					goto next; +			} +		} + +		perf_hpp__column_register(fmt); +next: +		continue; +	} +} + +void perf_hpp__append_sort_keys(void) +{ +	struct perf_hpp_fmt *fmt; + +	/* append output fields to sort keys */ +	perf_hpp__for_each_format(fmt) { +		if (!list_empty(&fmt->sort_list)) +			continue; + +		/* +		 * sort entry fields are dynamically created, +		 * so they can share a same sort key even though +		 * the list is empty. +		 */ +		if (perf_hpp__is_sort_entry(fmt)) { +			struct perf_hpp_fmt *pos; + +			perf_hpp__for_each_sort_list(pos) { +				if (perf_hpp__same_sort_entry(pos, fmt)) +					goto next; +			} +		} + +		perf_hpp__register_sort_field(fmt); +next: +		continue; +	} +} + +void perf_hpp__reset_output_field(void) +{ +	struct perf_hpp_fmt *fmt, *tmp; + +	/* reset output fields */ +	perf_hpp__for_each_format_safe(fmt, tmp) { +		list_del_init(&fmt->list); +		list_del_init(&fmt->sort_list); +	} + +	/* reset sort keys */ +	perf_hpp__for_each_sort_list_safe(fmt, tmp) { +		list_del_init(&fmt->list); +		list_del_init(&fmt->sort_list); +	} +} + +/* + * See hists__fprintf to match the column widths + */ +unsigned int hists__sort_list_width(struct hists *hists) +{ +	struct perf_hpp_fmt *fmt; +	int ret = 0; +	bool first = true; +	struct perf_hpp dummy_hpp; + +	perf_hpp__for_each_format(fmt) { +		if (perf_hpp__should_skip(fmt)) +			continue; + +		if (first) +			first = false; +		else +			ret += 2; + +		ret += fmt->width(fmt, &dummy_hpp, hists_to_evsel(hists)); +	} + +	if (verbose && sort__has_sym) /* Addr + origin */ +		ret += 3 + BITS_PER_LONG / 4; + +	return ret; +} diff --git a/tools/perf/ui/keysyms.h b/tools/perf/ui/keysyms.h new file mode 100644 index 00000000000..65092d576b4 --- /dev/null +++ b/tools/perf/ui/keysyms.h @@ -0,0 +1,28 @@ +#ifndef _PERF_KEYSYMS_H_ +#define _PERF_KEYSYMS_H_ 1 + +#include "libslang.h" + +#define K_DOWN	SL_KEY_DOWN +#define K_END	SL_KEY_END +#define K_ENTER	'\r' +#define K_ESC	033 +#define K_F1	SL_KEY_F(1) +#define K_HOME	SL_KEY_HOME +#define K_LEFT	SL_KEY_LEFT +#define K_PGDN	SL_KEY_NPAGE +#define K_PGUP	SL_KEY_PPAGE +#define K_RIGHT	SL_KEY_RIGHT +#define K_TAB	'\t' +#define K_UNTAB	SL_KEY_UNTAB +#define K_UP	SL_KEY_UP +#define K_BKSPC 0x7f +#define K_DEL	SL_KEY_DELETE + +/* Not really keys */ +#define K_TIMER	 -1 +#define K_ERROR	 -2 +#define K_RESIZE -3 +#define K_SWITCH_INPUT_DATA -4 + +#endif /* _PERF_KEYSYMS_H_ */ diff --git a/tools/perf/ui/libslang.h b/tools/perf/ui/libslang.h new file mode 100644 index 00000000000..4d54b6450f5 --- /dev/null +++ b/tools/perf/ui/libslang.h @@ -0,0 +1,29 @@ +#ifndef _PERF_UI_SLANG_H_ +#define _PERF_UI_SLANG_H_ 1 +/* + * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks + * the build if it isn't defined. Use the equivalent one that glibc + * has on features.h. + */ +#include <features.h> +#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 + +#define SL_KEY_UNTAB 0x1000 + +#endif /* _PERF_UI_SLANG_H_ */ diff --git a/tools/perf/ui/progress.c b/tools/perf/ui/progress.c new file mode 100644 index 00000000000..a0f24c7115c --- /dev/null +++ b/tools/perf/ui/progress.c @@ -0,0 +1,38 @@ +#include "../cache.h" +#include "progress.h" + +static void null_progress__update(struct ui_progress *p __maybe_unused) +{ +} + +static struct ui_progress_ops null_progress__ops = +{ +	.update = null_progress__update, +}; + +struct ui_progress_ops *ui_progress__ops = &null_progress__ops; + +void ui_progress__update(struct ui_progress *p, u64 adv) +{ +	p->curr += adv; + +	if (p->curr >= p->next) { +		p->next += p->step; +		ui_progress__ops->update(p); +	} +} + +void ui_progress__init(struct ui_progress *p, u64 total, const char *title) +{ +	p->curr = 0; +	p->next = p->step = total / 16; +	p->total = total; +	p->title = title; + +} + +void ui_progress__finish(void) +{ +	if (ui_progress__ops->finish) +		ui_progress__ops->finish(); +} diff --git a/tools/perf/ui/progress.h b/tools/perf/ui/progress.h new file mode 100644 index 00000000000..f34f89eb607 --- /dev/null +++ b/tools/perf/ui/progress.h @@ -0,0 +1,23 @@ +#ifndef _PERF_UI_PROGRESS_H_ +#define _PERF_UI_PROGRESS_H_ 1 + +#include <linux/types.h> + +void ui_progress__finish(void); +  +struct ui_progress { +	const char *title; +	u64 curr, next, step, total; +}; +  +void ui_progress__init(struct ui_progress *p, u64 total, const char *title); +void ui_progress__update(struct ui_progress *p, u64 adv); + +struct ui_progress_ops { +	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 new file mode 100644 index 00000000000..ba51fa8a117 --- /dev/null +++ b/tools/perf/ui/setup.c @@ -0,0 +1,107 @@ +#include <pthread.h> +#include <dlfcn.h> + +#include "../util/cache.h" +#include "../util/debug.h" +#include "../util/hist.h" + +pthread_mutex_t ui__lock = PTHREAD_MUTEX_INITIALIZER; +void *perf_gtk_handle; + +#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 + +void setup_browser(bool fallback_to_pager) +{ +	if (use_browser < 2 && (!isatty(1) || dump_trace)) +		use_browser = 0; + +	/* default to TUI */ +	if (use_browser < 0) +		use_browser = 1; + +	switch (use_browser) { +	case 2: +		if (setup_gtk_browser() == 0) +			break; +		printf("GTK browser requested but could not find %s\n", +		       PERF_GTK_DSO); +		sleep(1); +		/* fall through */ +	case 1: +		use_browser = 1; +		if (ui__init() == 0) +			break; +		/* fall through */ +	default: +		use_browser = 0; +		if (fallback_to_pager) +			setup_pager(); +		break; +	} +} + +void exit_browser(bool wait_for_ok) +{ +	switch (use_browser) { +	case 2: +		exit_gtk_browser(wait_for_ok); +		break; + +	case 1: +		ui__exit(wait_for_ok); +		break; + +	default: +		break; +	} +} diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c new file mode 100644 index 00000000000..90122abd372 --- /dev/null +++ b/tools/perf/ui/stdio/hist.c @@ -0,0 +1,514 @@ +#include <stdio.h> + +#include "../../util/util.h" +#include "../../util/hist.h" +#include "../../util/sort.h" +#include "../../util/evsel.h" + + +static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin) +{ +	int i; +	int ret = fprintf(fp, "            "); + +	for (i = 0; i < left_margin; i++) +		ret += fprintf(fp, " "); + +	return ret; +} + +static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask, +					  int left_margin) +{ +	int i; +	size_t ret = callchain__fprintf_left_margin(fp, left_margin); + +	for (i = 0; i < depth; i++) +		if (depth_mask & (1 << i)) +			ret += fprintf(fp, "|          "); +		else +			ret += fprintf(fp, "           "); + +	ret += fprintf(fp, "\n"); + +	return ret; +} + +static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, +				     int depth, int depth_mask, int period, +				     u64 total_samples, u64 hits, +				     int left_margin) +{ +	int i; +	size_t ret = 0; + +	ret += callchain__fprintf_left_margin(fp, left_margin); +	for (i = 0; i < depth; i++) { +		if (depth_mask & (1 << i)) +			ret += fprintf(fp, "|"); +		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); +		} 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); + +	return ret; +} + +static struct symbol *rem_sq_bracket; +static struct callchain_list rem_hits; + +static void init_rem_hits(void) +{ +	rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6); +	if (!rem_sq_bracket) { +		fprintf(stderr, "Not enough memory to display remaining hits\n"); +		return; +	} + +	strcpy(rem_sq_bracket->name, "[...]"); +	rem_hits.ms.sym = rem_sq_bracket; +} + +static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, +					 u64 total_samples, int depth, +					 int depth_mask, int left_margin) +{ +	struct rb_node *node, *next; +	struct callchain_node *child; +	struct callchain_list *chain; +	int new_depth_mask = depth_mask; +	u64 remaining; +	size_t ret = 0; +	int i; +	uint entries_printed = 0; + +	remaining = total_samples; + +	node = rb_first(root); +	while (node) { +		u64 new_total; +		u64 cumul; + +		child = rb_entry(node, struct callchain_node, rb_node); +		cumul = callchain_cumul_hits(child); +		remaining -= cumul; + +		/* +		 * The depth mask manages the output of pipes that show +		 * the depth. We don't want to keep the pipes of the current +		 * level for the last child of this depth. +		 * Except if we have remaining filtered hits. They will +		 * supersede the last child +		 */ +		next = rb_next(node); +		if (!next && (callchain_param.mode != CHAIN_GRAPH_REL || !remaining)) +			new_depth_mask &= ~(1 << (depth - 1)); + +		/* +		 * But we keep the older depth mask for the line separator +		 * to keep the level link until we reach the last child +		 */ +		ret += ipchain__fprintf_graph_line(fp, depth, depth_mask, +						   left_margin); +		i = 0; +		list_for_each_entry(chain, &child->val, list) { +			ret += ipchain__fprintf_graph(fp, chain, depth, +						      new_depth_mask, i++, +						      total_samples, +						      cumul, +						      left_margin); +		} + +		if (callchain_param.mode == CHAIN_GRAPH_REL) +			new_total = child->children_hit; +		else +			new_total = total_samples; + +		ret += __callchain__fprintf_graph(fp, &child->rb_root, new_total, +						  depth + 1, +						  new_depth_mask | (1 << depth), +						  left_margin); +		node = next; +		if (++entries_printed == callchain_param.print_limit) +			break; +	} + +	if (callchain_param.mode == CHAIN_GRAPH_REL && +		remaining && remaining != total_samples) { + +		if (!rem_sq_bracket) +			return ret; + +		new_depth_mask &= ~(1 << (depth - 1)); +		ret += ipchain__fprintf_graph(fp, &rem_hits, depth, +					      new_depth_mask, 0, total_samples, +					      remaining, left_margin); +	} + +	return ret; +} + +static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, +				       u64 total_samples, int left_margin) +{ +	struct callchain_node *cnode; +	struct callchain_list *chain; +	u32 entries_printed = 0; +	bool printed = false; +	struct rb_node *node; +	int i = 0; +	int ret = 0; + +	/* +	 * 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)) { +		cnode = rb_entry(node, struct callchain_node, rb_node); +		list_for_each_entry(chain, &cnode->val, list) { +			/* +			 * If we sort by symbol, the first entry is the same than +			 * the symbol. No need to print it otherwise it appears as +			 * displayed twice. +			 */ +			if (!i++ && field_order == NULL && +			    sort_order && !prefixcmp(sort_order, "sym")) +				continue; +			if (!printed) { +				ret += callchain__fprintf_left_margin(fp, left_margin); +				ret += fprintf(fp, "|\n"); +				ret += callchain__fprintf_left_margin(fp, left_margin); +				ret += fprintf(fp, "---"); +				left_margin += 3; +				printed = true; +			} 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); + +			if (++entries_printed == callchain_param.print_limit) +				break; +		} +		root = &cnode->rb_root; +	} + +	ret += __callchain__fprintf_graph(fp, root, total_samples, +					  1, 1, left_margin); +	ret += fprintf(fp, "\n"); + +	return ret; +} + +static size_t __callchain__fprintf_flat(FILE *fp, struct callchain_node *node, +					u64 total_samples) +{ +	struct callchain_list *chain; +	size_t ret = 0; + +	if (!node) +		return 0; + +	ret += __callchain__fprintf_flat(fp, node->parent, total_samples); + + +	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); +	} + +	return ret; +} + +static size_t callchain__fprintf_flat(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) { +		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 += __callchain__fprintf_flat(fp, chain, total_samples); +		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) +{ +	switch (callchain_param.mode) { +	case CHAIN_GRAPH_REL: +		return callchain__fprintf_graph(fp, &he->sorted_chain, +						symbol_conf.cumulate_callchain ? +						he->stat_acc->period : he->stat.period, +						left_margin); +		break; +	case CHAIN_GRAPH_ABS: +		return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, +						left_margin); +		break; +	case CHAIN_FLAT: +		return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples); +		break; +	case CHAIN_NONE: +		break; +	default: +		pr_err("Bad callchain mode\n"); +	} + +	return 0; +} + +static size_t hist_entry__callchain_fprintf(struct hist_entry *he, +					    struct hists *hists, +					    FILE *fp) +{ +	int left_margin = 0; +	u64 total_period = hists->stats.total_period; + +	if (field_order == NULL && (sort_order == NULL || +				    !prefixcmp(sort_order, "comm"))) { +		struct perf_hpp_fmt *fmt; + +		perf_hpp__for_each_format(fmt) { +			if (!perf_hpp__is_sort_entry(fmt)) +				continue; + +			/* must be 'comm' sort entry */ +			left_margin = fmt->width(fmt, NULL, hists_to_evsel(hists)); +			left_margin -= thread__comm_len(he->thread); +			break; +		} +	} +	return hist_entry_callchain__fprintf(he, total_period, left_margin, fp); +} + +static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp) +{ +	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__for_each_format(fmt) { +		if (perf_hpp__should_skip(fmt)) +			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); + +		advance_hpp(hpp, ret); +	} + +	return hpp->buf - start; +} + +static int hist_entry__fprintf(struct hist_entry *he, size_t size, +			       struct hists *hists, +			       char *bf, size_t bfsz, FILE *fp) +{ +	int ret; +	struct perf_hpp hpp = { +		.buf		= bf, +		.size		= size, +	}; + +	if (size == 0 || size > bfsz) +		size = hpp.size = bfsz; + +	hist_entry__snprintf(he, &hpp); + +	ret = fprintf(fp, "%s\n", bf); + +	if (symbol_conf.use_callchain) +		ret += hist_entry__callchain_fprintf(he, hists, fp); + +	return ret; +} + +size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, +		      int max_cols, float min_pcnt, FILE *fp) +{ +	struct perf_hpp_fmt *fmt; +	struct rb_node *nd; +	size_t ret = 0; +	unsigned int width; +	const char *sep = symbol_conf.field_sep; +	int nr_rows = 0; +	char bf[96]; +	struct perf_hpp dummy_hpp = { +		.buf	= bf, +		.size	= sizeof(bf), +	}; +	bool first = true; +	size_t linesz; +	char *line = NULL; + +	init_rem_hits(); + + +	perf_hpp__for_each_format(fmt) +		perf_hpp__reset_width(fmt, hists); + +	if (!show_header) +		goto print_entries; + +	fprintf(fp, "# "); + +	perf_hpp__for_each_format(fmt) { +		if (perf_hpp__should_skip(fmt)) +			continue; + +		if (!first) +			fprintf(fp, "%s", sep ?: "  "); +		else +			first = false; + +		fmt->header(fmt, &dummy_hpp, hists_to_evsel(hists)); +		fprintf(fp, "%s", bf); +	} + +	fprintf(fp, "\n"); +	if (max_rows && ++nr_rows >= max_rows) +		goto out; + +	if (sep) +		goto print_entries; + +	first = true; + +	fprintf(fp, "# "); + +	perf_hpp__for_each_format(fmt) { +		unsigned int i; + +		if (perf_hpp__should_skip(fmt)) +			continue; + +		if (!first) +			fprintf(fp, "%s", sep ?: "  "); +		else +			first = false; + +		width = fmt->width(fmt, &dummy_hpp, hists_to_evsel(hists)); +		for (i = 0; i < width; i++) +			fprintf(fp, "."); +	} + +	fprintf(fp, "\n"); +	if (max_rows && ++nr_rows >= max_rows) +		goto out; + +	fprintf(fp, "#\n"); +	if (max_rows && ++nr_rows >= max_rows) +		goto out; + +print_entries: +	linesz = hists__sort_list_width(hists) + 3 + 1; +	linesz += perf_hpp__color_overhead(); +	line = malloc(linesz); +	if (line == NULL) { +		ret = -1; +		goto out; +	} + +	for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { +		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); +		float percent; + +		if (h->filtered) +			continue; + +		percent = hist_entry__get_percent_limit(h); +		if (percent < min_pcnt) +			continue; + +		ret += hist_entry__fprintf(h, max_cols, hists, line, linesz, fp); + +		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); +			fprintf(fp, "%.10s end\n", graph_dotted_line); +		} +	} + +	free(line); +out: +	zfree(&rem_sq_bracket); + +	return ret; +} + +size_t events_stats__fprintf(struct events_stats *stats, FILE *fp) +{ +	int i; +	size_t ret = 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; + +		ret += fprintf(fp, "%16s events: %10d\n", name, +			       stats->nr_events[i]); +	} + +	return ret; +} diff --git a/tools/perf/ui/tui/helpline.c b/tools/perf/ui/tui/helpline.c new file mode 100644 index 00000000000..1c8b9afd5d6 --- /dev/null +++ b/tools/perf/ui/tui/helpline.c @@ -0,0 +1,58 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pthread.h> + +#include "../../util/debug.h" +#include "../helpline.h" +#include "../ui.h" +#include "../libslang.h" + +char ui_helpline__last_msg[1024]; + +static void tui_helpline__pop(void) +{ +} + +static void tui_helpline__push(const char *msg) +{ +	const size_t sz = sizeof(ui_helpline__current); + +	SLsmg_gotorc(SLtt_Screen_Rows - 1, 0); +	SLsmg_set_color(0); +	SLsmg_write_nstring((char *)msg, SLtt_Screen_Cols); +	SLsmg_refresh(); +	strncpy(ui_helpline__current, msg, sz)[sz - 1] = '\0'; +} + +static int tui_helpline__show(const char *format, va_list ap) +{ +	int ret; +	static int backlog; + +	pthread_mutex_lock(&ui__lock); +	ret = vscnprintf(ui_helpline__last_msg + backlog, +			sizeof(ui_helpline__last_msg) - backlog, format, ap); +	backlog += ret; + +	if (ui_helpline__last_msg[backlog - 1] == '\n') { +		ui_helpline__puts(ui_helpline__last_msg); +		SLsmg_refresh(); +		backlog = 0; +	} +	pthread_mutex_unlock(&ui__lock); + +	return ret; +} + +struct ui_helpline tui_helpline_fns = { +	.pop	= tui_helpline__pop, +	.push	= tui_helpline__push, +	.show	= tui_helpline__show, +}; + +void ui_helpline__init(void) +{ +	helpline_fns = &tui_helpline_fns; +	ui_helpline__puts(" "); +} diff --git a/tools/perf/ui/tui/progress.c b/tools/perf/ui/tui/progress.c new file mode 100644 index 00000000000..c61d14b101e --- /dev/null +++ b/tools/perf/ui/tui/progress.c @@ -0,0 +1,44 @@ +#include "../cache.h" +#include "../progress.h" +#include "../libslang.h" +#include "../ui.h" +#include "tui.h" +#include "../browser.h" + +static void tui_progress__update(struct ui_progress *p) +{ +	int bar, y; +	/* +	 * FIXME: We should have a per UI backend way of showing progress, +	 * stdio will just show a percentage as NN%, etc. +	 */ +	if (use_browser <= 0) +		return; + +	if (p->total == 0) +		return; + +	ui__refresh_dimensions(false); +	pthread_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 *)p->title); +	SLsmg_fill_region(y, 1, 1, SLtt_Screen_Cols - 2, ' '); +	SLsmg_set_color(HE_COLORSET_SELECTED); +	bar = ((SLtt_Screen_Cols - 2) * p->curr) / p->total; +	SLsmg_fill_region(y, 1, 1, bar, ' '); +	SLsmg_refresh(); +	pthread_mutex_unlock(&ui__lock); +} + +static struct ui_progress_ops tui_progress__ops = +{ +	.update		= tui_progress__update, +}; + +void tui_progress__init(void) +{ +	ui_progress__ops = &tui_progress__ops; +} diff --git a/tools/perf/ui/tui/setup.c b/tools/perf/ui/tui/setup.c new file mode 100644 index 00000000000..2f612562978 --- /dev/null +++ b/tools/perf/ui/tui/setup.c @@ -0,0 +1,151 @@ +#include <signal.h> +#include <stdbool.h> + +#include "../../util/cache.h" +#include "../../util/debug.h" +#include "../browser.h" +#include "../helpline.h" +#include "../ui.h" +#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 void hist_browser__init_hpp(void); + +void ui__refresh_dimensions(bool force) +{ +	if (force || ui__need_resize) { +		ui__need_resize = 0; +		pthread_mutex_lock(&ui__lock); +		SLtt_get_screen_size(); +		SLsmg_reinit_smg(); +		pthread_mutex_unlock(&ui__lock); +	} +} + +static void ui__sigwinch(int sig __maybe_unused) +{ +	ui__need_resize = 1; +} + +static void ui__setup_sigwinch(void) +{ +	static bool done; + +	if (done) +		return; + +	done = true; +	pthread__unblock_sigwinch(); +	signal(SIGWINCH, ui__sigwinch); +} + +int ui__getch(int delay_secs) +{ +	struct timeval timeout, *ptimeout = delay_secs ? &timeout : NULL; +	fd_set read_set; +	int err, key; + +	ui__setup_sigwinch(); + +	FD_ZERO(&read_set); +	FD_SET(0, &read_set); + +	if (delay_secs) { +		timeout.tv_sec = delay_secs; +		timeout.tv_usec = 0; +	} + +        err = select(1, &read_set, NULL, NULL, ptimeout); + +	if (err == 0) +		return K_TIMER; + +	if (err == -1) { +		if (errno == EINTR) +			return K_RESIZE; +		return K_ERROR; +	} + +	key = SLang_getkey(); +	if (key != K_ESC) +		return key; + +	FD_ZERO(&read_set); +	FD_SET(0, &read_set); +	timeout.tv_sec = 0; +	timeout.tv_usec = 20; +        err = select(1, &read_set, NULL, NULL, &timeout); +	if (err == 0) +		return K_ESC; + +	SLang_ungetkey(key); +	return SLkp_getkey(); +} + +static void ui__signal(int sig) +{ +	ui__exit(false); +	psignal(sig, "perf"); +	exit(0); +} + +int ui__init(void) +{ +	int err; + +	SLutf8_enable(-1); +	SLtt_get_terminfo(); +	SLtt_get_screen_size(); + +	err = SLsmg_init_smg(); +	if (err < 0) +		goto out; +	err = SLang_init_tty(0, 0, 0); +	if (err < 0) +		goto out; + +	err = SLkp_init(); +	if (err < 0) { +		pr_err("TUI initialization failed.\n"); +		goto out; +	} + +	SLkp_define_keysym((char *)"^(kB)", SL_KEY_UNTAB); + +	ui_helpline__init(); +	ui_browser__init(); +	tui_progress__init(); + +	signal(SIGSEGV, ui__signal); +	signal(SIGFPE, ui__signal); +	signal(SIGINT, ui__signal); +	signal(SIGQUIT, ui__signal); +	signal(SIGTERM, ui__signal); + +	perf_error__register(&perf_tui_eops); + +	hist_browser__init_hpp(); +out: +	return err; +} + +void ui__exit(bool wait_for_ok) +{ +	if (wait_for_ok) +		ui__question_window("Fatal Error", +				    ui_helpline__last_msg, +				    "Press any key...", 0); + +	SLtt_set_cursor_visibility(1); +	SLsmg_refresh(); +	SLsmg_reset_smg(); +	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 00000000000..18961c7b6ec --- /dev/null +++ b/tools/perf/ui/tui/tui.h @@ -0,0 +1,6 @@ +#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 new file mode 100644 index 00000000000..bf890f72fe8 --- /dev/null +++ b/tools/perf/ui/tui/util.c @@ -0,0 +1,256 @@ +#include "../../util/util.h" +#include <signal.h> +#include <stdbool.h> +#include <string.h> +#include <sys/ttydefaults.h> + +#include "../../util/cache.h" +#include "../../util/debug.h" +#include "../browser.h" +#include "../keysyms.h" +#include "../helpline.h" +#include "../ui.h" +#include "../util.h" +#include "../libslang.h" + +static void ui_browser__argv_write(struct ui_browser *browser, +				   void *entry, int row) +{ +	char **arg = entry; +	bool current_entry = ui_browser__is_current_entry(browser, row); + +	ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : +						       HE_COLORSET_NORMAL); +	slsmg_write_nstring(*arg, browser->width); +} + +static int popup_menu__run(struct ui_browser *menu) +{ +	int key; + +	if (ui_browser__show(menu, " ", "ESC: exit, ENTER|->: Select option") < 0) +		return -1; + +	while (1) { +		key = ui_browser__run(menu, 0); + +		switch (key) { +		case K_RIGHT: +		case K_ENTER: +			key = menu->index; +			break; +		case K_LEFT: +		case K_ESC: +		case 'q': +		case CTRL('c'): +			key = -1; +			break; +		default: +			continue; +		} + +		break; +	} + +	ui_browser__hide(menu); +	return key; +} + +int ui__popup_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 popup_menu__run(&menu); +} + +int ui_browser__input_window(const char *title, const char *text, char *input, +			     const char *exit_msg, int delay_secs) +{ +	int x, y, len, key; +	int max_len = 60, nr_lines = 0; +	static char buf[50]; +	const char *t; + +	t = text; +	while (1) { +		const char *sep = strchr(t, '\n'); + +		if (sep == NULL) +			sep = strchr(t, '\0'); +		len = sep - t; +		if (max_len < len) +			max_len = len; +		++nr_lines; +		if (*sep == '\0') +			break; +		t = sep + 1; +	} + +	pthread_mutex_lock(&ui__lock); + +	max_len += 2; +	nr_lines += 8; +	y = SLtt_Screen_Rows / 2 - nr_lines / 2; +	x = SLtt_Screen_Cols / 2 - max_len / 2; + +	SLsmg_set_color(0); +	SLsmg_draw_box(y, x++, nr_lines, max_len); +	if (title) { +		SLsmg_gotorc(y, x + 1); +		SLsmg_write_string((char *)title); +	} +	SLsmg_gotorc(++y, x); +	nr_lines -= 7; +	max_len -= 2; +	SLsmg_write_wrapped_string((unsigned char *)text, y, x, +				   nr_lines, max_len, 1); +	y += nr_lines; +	len = 5; +	while (len--) { +		SLsmg_gotorc(y + len - 1, x); +		SLsmg_write_nstring((char *)" ", 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_refresh(); + +	pthread_mutex_unlock(&ui__lock); + +	x += 2; +	len = 0; +	key = ui__getch(delay_secs); +	while (key != K_TIMER && key != K_ENTER && key != K_ESC) { +		pthread_mutex_lock(&ui__lock); + +		if (key == K_BKSPC) { +			if (len == 0) { +				pthread_mutex_unlock(&ui__lock); +				goto next_key; +			} +			SLsmg_gotorc(y, x + --len); +			SLsmg_write_char(' '); +		} else { +			buf[len] = key; +			SLsmg_gotorc(y, x + len++); +			SLsmg_write_char(key); +		} +		SLsmg_refresh(); + +		pthread_mutex_unlock(&ui__lock); + +		/* XXX more graceful overflow handling needed */ +		if (len == sizeof(buf) - 1) { +			ui_helpline__push("maximum size of symbol name reached!"); +			key = K_ENTER; +			break; +		} +next_key: +		key = ui__getch(delay_secs); +	} + +	buf[len] = '\0'; +	strncpy(input, buf, len+1); +	return key; +} + +int ui__question_window(const char *title, const char *text, +			const char *exit_msg, int delay_secs) +{ +	int x, y; +	int max_len = 0, nr_lines = 0; +	const char *t; + +	t = text; +	while (1) { +		const char *sep = strchr(t, '\n'); +		int len; + +		if (sep == NULL) +			sep = strchr(t, '\0'); +		len = sep - t; +		if (max_len < len) +			max_len = len; +		++nr_lines; +		if (*sep == '\0') +			break; +		t = sep + 1; +	} + +	pthread_mutex_lock(&ui__lock); + +	max_len += 2; +	nr_lines += 4; +	y = SLtt_Screen_Rows / 2 - nr_lines / 2, +	x = SLtt_Screen_Cols / 2 - max_len / 2; + +	SLsmg_set_color(0); +	SLsmg_draw_box(y, x++, nr_lines, max_len); +	if (title) { +		SLsmg_gotorc(y, x + 1); +		SLsmg_write_string((char *)title); +	} +	SLsmg_gotorc(++y, x); +	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); +	SLsmg_refresh(); + +	pthread_mutex_unlock(&ui__lock); + +	return ui__getch(delay_secs); +} + +int ui__help_window(const char *text) +{ +	return ui__question_window("Help", text, "Press any key...", 0); +} + +int ui__dialog_yesno(const char *msg) +{ +	return ui__question_window(NULL, msg, "Enter: Yes, ESC: No", 0); +} + +static int __ui__warning(const char *title, const char *format, va_list args) +{ +	char *s; + +	if (vasprintf(&s, format, args) > 0) { +		int key; + +		key = ui__question_window(title, s, "Press any key...", 0); +		free(s); +		return key; +	} + +	fprintf(stderr, "%s\n", title); +	vfprintf(stderr, format, args); +	return K_ESC; +} + +static int perf_tui__error(const char *format, va_list args) +{ +	return __ui__warning("Error:", format, args); +} + +static int perf_tui__warning(const char *format, va_list args) +{ +	return __ui__warning("Warning:", format, args); +} + +struct perf_error_ops perf_tui_eops = { +	.error		= perf_tui__error, +	.warning	= perf_tui__warning, +}; diff --git a/tools/perf/ui/ui.h b/tools/perf/ui/ui.h new file mode 100644 index 00000000000..ab88383f8be --- /dev/null +++ b/tools/perf/ui/ui.h @@ -0,0 +1,29 @@ +#ifndef _PERF_UI_H_ +#define _PERF_UI_H_ 1 + +#include <pthread.h> +#include <stdbool.h> +#include <linux/compiler.h> + +extern pthread_mutex_t 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 HAVE_SLANG_SUPPORT +int ui__init(void); +void ui__exit(bool wait_for_ok); +#else +static inline int ui__init(void) +{ +	return -1; +} +static inline void ui__exit(bool wait_for_ok __maybe_unused) {} +#endif + +void ui__refresh_dimensions(bool force); + +#endif /* _PERF_UI_H_ */ diff --git a/tools/perf/ui/util.c b/tools/perf/ui/util.c new file mode 100644 index 00000000000..e3e0a963d03 --- /dev/null +++ b/tools/perf/ui/util.c @@ -0,0 +1,84 @@ +#include "util.h" +#include "../debug.h" + + +/* + * Default error logging functions + */ +static int perf_stdio__error(const char *format, va_list args) +{ +	fprintf(stderr, "Error:\n"); +	vfprintf(stderr, format, args); +	return 0; +} + +static int perf_stdio__warning(const char *format, va_list args) +{ +	fprintf(stderr, "Warning:\n"); +	vfprintf(stderr, format, args); +	return 0; +} + +static struct perf_error_ops default_eops = +{ +	.error		= perf_stdio__error, +	.warning	= perf_stdio__warning, +}; + +static struct perf_error_ops *perf_eops = &default_eops; + + +int ui__error(const char *format, ...) +{ +	int ret; +	va_list args; + +	va_start(args, format); +	ret = perf_eops->error(format, args); +	va_end(args); + +	return ret; +} + +int ui__warning(const char *format, ...) +{ +	int ret; +	va_list args; + +	va_start(args, format); +	ret = perf_eops->warning(format, args); +	va_end(args); + +	return ret; +} + +/** + * perf_error__register - Register error logging functions + * @eops: The pointer to error logging function struct + * + * Register UI-specific error logging functions. Before calling this, + * other logging functions should be unregistered, if any. + */ +int perf_error__register(struct perf_error_ops *eops) +{ +	if (perf_eops != &default_eops) +		return -1; + +	perf_eops = eops; +	return 0; +} + +/** + * perf_error__unregister - Unregister error logging functions + * @eops: The pointer to error logging function struct + * + * Unregister already registered error logging functions. + */ +int perf_error__unregister(struct perf_error_ops *eops) +{ +	if (perf_eops != eops) +		return -1; + +	perf_eops = &default_eops; +	return 0; +} diff --git a/tools/perf/ui/util.h b/tools/perf/ui/util.h new file mode 100644 index 00000000000..361f08c52d3 --- /dev/null +++ b/tools/perf/ui/util.h @@ -0,0 +1,21 @@ +#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__help_window(const char *text); +int ui__dialog_yesno(const char *msg); +int ui__question_window(const char *title, const char *text, +			const char *exit_msg, int delay_secs); + +struct perf_error_ops { +	int (*error)(const char *format, va_list args); +	int (*warning)(const char *format, va_list args); +}; + +int perf_error__register(struct perf_error_ops *eops); +int perf_error__unregister(struct perf_error_ops *eops); + +#endif /* _PERF_UI_UTIL_H_ */  | 
