Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
perf-ui-y += setup.o
perf-ui-y += helpline.o
perf-ui-y += progress.o
perf-ui-y += util.o
perf-ui-y += hist.o
perf-ui-y += stdio/hist.o
CFLAGS_setup.o += -DLIBDIR="BUILD_STR($(LIBDIR))"
perf-ui-$(CONFIG_SLANG) += browser.o
perf-ui-$(CONFIG_SLANG) += keysyms.o
perf-ui-$(CONFIG_SLANG) += browsers/
perf-ui-$(CONFIG_SLANG) += tui/
+828
View File
@@ -0,0 +1,828 @@
// SPDX-License-Identifier: GPL-2.0
#include "../util/string2.h"
#include "../util/config.h"
#include "libslang.h"
#include "ui.h"
#include "util.h"
#include <linux/compiler.h>
#include <linux/list.h>
#include <linux/rbtree.h>
#include <linux/string.h>
#include <stdlib.h>
#include <sys/ttydefaults.h>
#include "browser.h"
#include "helpline.h"
#include "keysyms.h"
#include "../util/color.h"
#include <linux/ctype.h>
#include <linux/zalloc.h>
static int ui_browser__percent_color(struct ui_browser *browser,
double percent, bool current)
{
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_title(struct ui_browser *browser, int y, int x)
{
SLsmg_gotorc(browser->y + y, browser->x + x);
}
void ui_browser__gotorc(struct ui_browser *browser, int y, int x)
{
SLsmg_gotorc(browser->y + y + browser->extra_title_lines, browser->x + x);
}
void ui_browser__write_nstring(struct ui_browser *browser __maybe_unused, const char *msg,
unsigned int width)
{
SLsmg_write_nstring(msg, width);
}
void ui_browser__vprintf(struct ui_browser *browser __maybe_unused, const char *fmt, va_list args)
{
SLsmg_vprintf(fmt, args);
}
void ui_browser__printf(struct ui_browser *browser __maybe_unused, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
ui_browser__vprintf(browser, fmt, args);
va_end(args);
}
static struct list_head *
ui_browser__list_head_filter_entries(struct ui_browser *browser,
struct list_head *pos)
{
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->rows)
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 = browser->rows = SLtt_Screen_Rows - 2;
browser->rows -= browser->extra_title_lines;
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__warn_unhandled_hotkey(struct ui_browser *browser, int key, int timeout, const char *help)
{
char kname[32];
key_name(key, kname, sizeof(kname));
return ui_browser__warning(browser, timeout, "\n'%s' key not associated%s!\n", kname, help ?: "");
}
int ui_browser__help_window(struct ui_browser *browser, const char *text)
{
int key;
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);
ui_browser__write_nstring(browser, title, browser->width + 1);
}
void ui_browser__show_title(struct ui_browser *browser, const char *title)
{
mutex_lock(&ui__lock);
__ui_browser__show_title(browser, title);
mutex_unlock(&ui__lock);
}
int ui_browser__show(struct ui_browser *browser, const char *title,
const char *helpline, ...)
{
int err;
va_list ap;
if (browser->refresh_dimensions == NULL)
browser->refresh_dimensions = ui_browser__refresh_dimensions;
browser->refresh_dimensions(browser);
mutex_lock(&ui__lock);
__ui_browser__show_title(browser, title);
free(browser->title);
browser->title = strdup(title);
zfree(&browser->helpline);
va_start(ap, helpline);
err = vasprintf(&browser->helpline, helpline, ap);
va_end(ap);
if (err > 0)
ui_helpline__push(browser->helpline);
mutex_unlock(&ui__lock);
return err ? 0 : -1;
}
void ui_browser__hide(struct ui_browser *browser)
{
mutex_lock(&ui__lock);
ui_helpline__pop();
zfree(&browser->helpline);
zfree(&browser->title);
mutex_unlock(&ui__lock);
}
static void ui_browser__scrollbar_set(struct ui_browser *browser)
{
int height = browser->height, h = 0, pct = 0,
col = browser->width,
row = 0;
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->extra_title_lines, browser->x,
browser->rows - row, width, ' ');
if (browser->nr_entries == 0 && browser->no_samples_msg)
__ui__info_window(NULL, browser->no_samples_msg, NULL);
return 0;
}
int ui_browser__refresh(struct ui_browser *browser)
{
mutex_lock(&ui__lock);
__ui_browser__refresh(browser);
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;
mutex_lock(&ui__lock);
err = __ui_browser__refresh(browser);
SLsmg_refresh();
mutex_unlock(&ui__lock);
if (err < 0)
break;
key = ui__getch(delay_secs);
if (key == K_RESIZE) {
ui__refresh_dimensions(false);
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 ||
(browser->columns && (key == K_LEFT || key == K_RIGHT)) ||
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->rows) {
++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_RIGHT:
if (!browser->columns)
goto out;
if (browser->horiz_scroll < browser->columns - 1)
++browser->horiz_scroll;
break;
case K_LEFT:
if (!browser->columns)
goto out;
if (browser->horiz_scroll != 0)
--browser->horiz_scroll;
else
goto out;
break;
case K_PGDN:
case ' ':
if (browser->top_idx + browser->rows > browser->nr_entries - 1)
break;
offset = browser->rows;
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->rows)
offset = browser->top_idx;
else
offset = browser->rows;
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->rows - 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:
out:
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->nr_entries == 0)
return 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->rows)
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 = "yellow",
},
{
.colorset = HE_COLORSET_JUMP_ARROWS,
.name = "jump_arrows",
.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 (!strstarts(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';
bg = skip_spaces(bg + 1);
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 = (char **)browser->top + offset;
break;
case SEEK_END:
browser->top = (char **)browser->entries + browser->nr_entries - 1 + offset;
break;
default:
return;
}
assert((char **)browser->top < (char **)browser->entries + browser->nr_entries);
assert((char **)browser->top >= (char **)browser->entries);
}
unsigned int ui_browser__argv_refresh(struct ui_browser *browser)
{
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 &&
row < (unsigned)SLtt_Screen_Rows - 1) {
assert(pos < (char **)browser->entries + browser->nr_entries);
if (!browser->filter || !browser->filter(browser, *pos)) {
ui_browser__gotorc(browser, row, 0);
browser->write(browser, pos, row);
if (++row == browser->rows)
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->rows) {
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->rows - 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->rows)
end_row = browser->rows - 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->rows) {
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__mark_fused(struct ui_browser *browser, unsigned int column,
unsigned int row, int diff, bool arrow_down)
{
int end_row;
if (diff <= 0)
return;
SLsmg_set_char_set(1);
if (arrow_down) {
if (row + diff <= browser->top_idx)
return;
end_row = row + diff - browser->top_idx;
ui_browser__gotorc(browser, end_row, column - 1);
SLsmg_write_char(SLSMG_LTEE_CHAR);
while (--end_row >= 0 && end_row > (int)(row - browser->top_idx)) {
ui_browser__gotorc(browser, end_row, column - 1);
SLsmg_draw_vline(1);
}
end_row = (int)(row - browser->top_idx);
if (end_row >= 0) {
ui_browser__gotorc(browser, end_row, column - 1);
SLsmg_write_char(SLSMG_ULCORN_CHAR);
ui_browser__gotorc(browser, end_row, column);
SLsmg_draw_hline(2);
}
} else {
if (row < browser->top_idx)
return;
end_row = row - browser->top_idx;
ui_browser__gotorc(browser, end_row, column - 1);
SLsmg_write_char(SLSMG_LTEE_CHAR);
ui_browser__gotorc(browser, end_row, column);
SLsmg_draw_hline(2);
}
SLsmg_set_char_set(0);
}
void ui_browser__init(void)
{
int i = 0;
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);
}
}
+87
View File
@@ -0,0 +1,87 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_BROWSER_H_
#define _PERF_UI_BROWSER_H_ 1
#include <linux/types.h>
#include <stdarg.h>
#include <sys/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_JUMP_ARROWS 54
#define HE_COLORSET_ADDR 55
#define HE_COLORSET_ROOT 56
struct ui_browser {
u64 index, top_idx;
void *top, *entries;
u16 y, x, width, height, rows, columns, horiz_scroll;
u8 extra_title_lines;
int current_color;
void *priv;
char *title;
char *helpline;
const char *no_samples_msg;
void (*refresh_dimensions)(struct ui_browser *browser);
unsigned int (*refresh)(struct ui_browser *browser);
void (*write)(struct ui_browser *browser, void *entry, int row);
void (*seek)(struct ui_browser *browser, off_t offset, int whence);
bool (*filter)(struct ui_browser *browser, void *entry);
u32 nr_entries;
bool navkeypressed;
bool use_navkeypressed;
};
int ui_browser__set_color(struct ui_browser *browser, int color);
void ui_browser__set_percent_color(struct ui_browser *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_title(struct ui_browser *browser, int y, int x);
void ui_browser__gotorc(struct ui_browser *browser, int y, int x);
void ui_browser__write_nstring(struct ui_browser *browser, const char *msg,
unsigned int width);
void ui_browser__vprintf(struct ui_browser *browser, const char *fmt, va_list args);
void ui_browser__printf(struct ui_browser *browser, const char *fmt, ...);
void ui_browser__write_graph(struct ui_browser *browser, int graph);
void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column,
u64 start, u64 end);
void ui_browser__mark_fused(struct ui_browser *browser, unsigned int column,
unsigned int row, int diff, bool arrow_down);
void __ui_browser__show_title(struct ui_browser *browser, const char *title);
void ui_browser__show_title(struct ui_browser *browser, const char *title);
int ui_browser__show(struct ui_browser *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__warn_unhandled_hotkey(struct ui_browser *browser, int key, int timeout, const char *help);
int ui_browser__help_window(struct ui_browser *browser, const char *text);
bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text);
int ui_browser__input_window(const char *title, const char *text, char *input,
const char *exit_msg, int delay_sec);
struct perf_session;
int tui__header_window(struct perf_session *session);
void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence);
unsigned int ui_browser__argv_refresh(struct ui_browser *browser);
void ui_browser__rb_tree_seek(struct ui_browser *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);
#endif /* _PERF_UI_BROWSER_H_ */
+7
View File
@@ -0,0 +1,7 @@
perf-ui-y += annotate.o
perf-ui-y += annotate-data.o
perf-ui-y += hists.o
perf-ui-y += map.o
perf-ui-y += scripts.o
perf-ui-y += header.o
perf-ui-y += res_sample.o
@@ -0,0 +1,614 @@
// SPDX-License-Identifier: GPL-2.0
#include <inttypes.h>
#include <string.h>
#include <linux/zalloc.h>
#include <sys/ttydefaults.h>
#include "ui/browser.h"
#include "ui/helpline.h"
#include "ui/keysyms.h"
#include "ui/ui.h"
#include "util/annotate.h"
#include "util/annotate-data.h"
#include "util/evsel.h"
#include "util/evlist.h"
#include "util/sort.h"
#define FOLDED_SIGN '+'
#define UNFOLD_SIGN '-'
#define NOCHLD_SIGN ' '
struct browser_entry {
struct list_head node;
struct annotated_member *data;
struct type_hist_entry *hists;
struct browser_entry *parent;
struct list_head children;
int indent; /*indentation level, starts from 0 */
int nr_entries; /* # of visible entries: self + descendents */
bool folded; /* only can be false when it has children */
};
struct annotated_data_browser {
struct ui_browser b;
struct list_head entries;
struct browser_entry *curr;
int nr_events;
};
static struct annotated_data_browser *get_browser(struct ui_browser *uib)
{
return container_of(uib, struct annotated_data_browser, b);
}
static void update_hist_entry(struct type_hist_entry *dst,
struct type_hist_entry *src)
{
dst->nr_samples += src->nr_samples;
dst->period += src->period;
}
static int get_member_overhead(struct annotated_data_type *adt,
struct browser_entry *entry,
struct evsel *leader)
{
struct annotated_member *member = entry->data;
int i, k;
for (i = 0; i < member->size; i++) {
struct type_hist *h;
struct evsel *evsel;
int offset = member->offset + i;
k = 0;
for_each_group_evsel(evsel, leader) {
if (symbol_conf.skip_empty &&
evsel__hists(evsel)->stats.nr_samples == 0)
continue;
h = adt->histograms[evsel->core.idx];
update_hist_entry(&entry->hists[k++], &h->addr[offset]);
}
}
return 0;
}
static int add_child_entries(struct annotated_data_browser *browser,
struct browser_entry *parent,
struct annotated_data_type *adt,
struct annotated_member *member,
struct evsel *evsel, int indent)
{
struct annotated_member *pos;
struct browser_entry *entry;
struct list_head *parent_list;
entry = zalloc(sizeof(*entry));
if (entry == NULL)
return -1;
entry->hists = calloc(browser->nr_events, sizeof(*entry->hists));
if (entry->hists == NULL) {
free(entry);
return -1;
}
entry->data = member;
entry->parent = parent;
entry->indent = indent;
if (get_member_overhead(adt, entry, evsel) < 0) {
free(entry);
return -1;
}
INIT_LIST_HEAD(&entry->children);
if (parent)
parent_list = &parent->children;
else
parent_list = &browser->entries;
list_add_tail(&entry->node, parent_list);
list_for_each_entry(pos, &member->children, node) {
int nr = add_child_entries(browser, entry, adt, pos, evsel,
indent + 1);
if (nr < 0)
return nr;
}
/* add an entry for the closing bracket ("}") */
if (!list_empty(&member->children)) {
struct browser_entry *bracket;
bracket = zalloc(sizeof(*bracket));
if (bracket == NULL)
return -1;
bracket->indent = indent;
bracket->parent = entry;
bracket->folded = true;
bracket->nr_entries = 1;
INIT_LIST_HEAD(&bracket->children);
list_add_tail(&bracket->node, &entry->children);
}
/* fold child entries by default */
entry->folded = true;
entry->nr_entries = 1;
return 0;
}
static u32 count_visible_entries(struct annotated_data_browser *browser)
{
int nr = 0;
struct browser_entry *entry;
list_for_each_entry(entry, &browser->entries, node)
nr += entry->nr_entries;
return nr;
}
static int annotated_data_browser__collect_entries(struct annotated_data_browser *browser)
{
struct hist_entry *he = browser->b.priv;
struct annotated_data_type *adt = he->mem_type;
struct evsel *evsel = hists_to_evsel(he->hists);
INIT_LIST_HEAD(&browser->entries);
add_child_entries(browser, /*parent=*/NULL, adt, &adt->self, evsel,
/*indent=*/0);
browser->b.entries = &browser->entries;
browser->b.nr_entries = count_visible_entries(browser);
return 0;
}
static void annotated_data_browser__delete_entries(struct annotated_data_browser *browser)
{
struct browser_entry *pos, *tmp;
list_for_each_entry_safe(pos, tmp, &browser->entries, node) {
list_del_init(&pos->node);
zfree(&pos->hists);
free(pos);
}
}
static struct browser_entry *get_first_child(struct browser_entry *entry)
{
if (list_empty(&entry->children))
return NULL;
return list_first_entry(&entry->children, struct browser_entry, node);
}
static struct browser_entry *get_last_child(struct browser_entry *entry)
{
if (list_empty(&entry->children))
return NULL;
return list_last_entry(&entry->children, struct browser_entry, node);
}
static bool is_first_child(struct browser_entry *entry)
{
/* This will be checked in a different way */
if (entry->parent == NULL)
return false;
return get_first_child(entry->parent) == entry;
}
static bool is_last_child(struct browser_entry *entry)
{
/* This will be checked in a different way */
if (entry->parent == NULL)
return false;
return get_last_child(entry->parent) == entry;
}
static struct browser_entry *browser__prev_entry(struct ui_browser *uib,
struct browser_entry *entry)
{
struct annotated_data_browser *browser = get_browser(uib);
struct browser_entry *first;
first = list_first_entry(&browser->entries, struct browser_entry, node);
while (entry != first) {
if (is_first_child(entry))
entry = entry->parent;
else {
entry = list_prev_entry(entry, node);
while (!entry->folded)
entry = get_last_child(entry);
}
if (!uib->filter || !uib->filter(uib, &entry->node))
return entry;
}
return first;
}
static struct browser_entry *browser__next_entry(struct ui_browser *uib,
struct browser_entry *entry)
{
struct annotated_data_browser *browser = get_browser(uib);
struct browser_entry *last;
last = list_last_entry(&browser->entries, struct browser_entry, node);
while (!last->folded)
last = get_last_child(last);
while (entry != last) {
if (!entry->folded)
entry = get_first_child(entry);
else {
while (is_last_child(entry))
entry = entry->parent;
entry = list_next_entry(entry, node);
}
if (!uib->filter || !uib->filter(uib, &entry->node))
return entry;
}
return last;
}
static void browser__seek(struct ui_browser *uib, off_t offset, int whence)
{
struct annotated_data_browser *browser = get_browser(uib);
struct browser_entry *entry;
if (uib->nr_entries == 0)
return;
switch (whence) {
case SEEK_SET:
entry = list_first_entry(&browser->entries, typeof(*entry), node);
if (uib->filter && uib->filter(uib, &entry->node))
entry = browser__next_entry(uib, entry);
break;
case SEEK_CUR:
entry = list_entry(uib->top, typeof(*entry), node);
break;
case SEEK_END:
entry = list_last_entry(&browser->entries, typeof(*entry), node);
while (!entry->folded)
entry = get_last_child(entry);
if (uib->filter && uib->filter(uib, &entry->node))
entry = browser__prev_entry(uib, entry);
break;
default:
return;
}
assert(entry != NULL);
if (offset > 0) {
while (offset-- != 0)
entry = browser__next_entry(uib, entry);
} else {
while (offset++ != 0)
entry = browser__prev_entry(uib, entry);
}
uib->top = &entry->node;
}
static unsigned int browser__refresh(struct ui_browser *uib)
{
struct annotated_data_browser *browser = get_browser(uib);
struct browser_entry *entry, *next;
int row = 0;
if (uib->top == NULL || uib->top == uib->entries)
browser__seek(uib, SEEK_SET, 0);
entry = list_entry(uib->top, typeof(*entry), node);
while (true) {
if (!uib->filter || !uib->filter(uib, &entry->node)) {
ui_browser__gotorc(uib, row, 0);
uib->write(uib, entry, row);
if (uib->top_idx + row == uib->index)
browser->curr = entry;
if (++row == uib->rows)
break;
}
next = browser__next_entry(uib, entry);
if (next == entry)
break;
entry = next;
}
return row;
}
static int browser__show(struct ui_browser *uib)
{
struct hist_entry *he = uib->priv;
struct annotated_data_type *adt = he->mem_type;
struct annotated_data_browser *browser = get_browser(uib);
const char *help = "Press 'h' for help on key bindings";
char title[256];
snprintf(title, sizeof(title), "Annotate type: '%s' (%d samples)",
adt->self.type_name, he->stat.nr_events);
if (ui_browser__show(uib, title, help) < 0)
return -1;
/* second line header */
ui_browser__gotorc_title(uib, 0, 0);
ui_browser__set_color(uib, HE_COLORSET_ROOT);
if (symbol_conf.show_total_period)
strcpy(title, "Period");
else if (symbol_conf.show_nr_samples)
strcpy(title, "Samples");
else
strcpy(title, "Percent");
ui_browser__printf(uib, "%*s %10s %10s %10s %s",
2 + 11 * (browser->nr_events - 1), "",
title, "Offset", "Size", "Field");
ui_browser__write_nstring(uib, "", uib->width);
return 0;
}
static void browser__write_overhead(struct ui_browser *uib,
struct type_hist *total,
struct type_hist_entry *hist, int row)
{
u64 period = hist->period;
double percent = total->period ? (100.0 * period / total->period) : 0;
bool current = ui_browser__is_current_entry(uib, row);
int nr_samples = 0;
ui_browser__set_percent_color(uib, percent, current);
if (symbol_conf.show_total_period)
ui_browser__printf(uib, " %10" PRIu64, period);
else if (symbol_conf.show_nr_samples)
ui_browser__printf(uib, " %10d", nr_samples);
else
ui_browser__printf(uib, " %10.2f", percent);
ui_browser__set_percent_color(uib, 0, current);
}
static void browser__write(struct ui_browser *uib, void *entry, int row)
{
struct annotated_data_browser *browser = get_browser(uib);
struct browser_entry *be = entry;
struct annotated_member *member = be->data;
struct hist_entry *he = uib->priv;
struct annotated_data_type *adt = he->mem_type;
struct evsel *leader = hists_to_evsel(he->hists);
struct evsel *evsel;
int idx = 0;
bool current = ui_browser__is_current_entry(uib, row);
if (member == NULL) {
/* print the closing bracket */
ui_browser__set_percent_color(uib, 0, current);
ui_browser__printf(uib, "%c ", NOCHLD_SIGN);
ui_browser__write_nstring(uib, "", 11 * browser->nr_events);
ui_browser__printf(uib, " %10s %10s %*s};",
"", "", be->indent * 4, "");
ui_browser__write_nstring(uib, "", uib->width);
return;
}
ui_browser__set_percent_color(uib, 0, current);
if (!list_empty(&be->children))
ui_browser__printf(uib, "%c ", be->folded ? FOLDED_SIGN : UNFOLD_SIGN);
else
ui_browser__printf(uib, "%c ", NOCHLD_SIGN);
/* print the number */
for_each_group_evsel(evsel, leader) {
struct type_hist *h = adt->histograms[evsel->core.idx];
if (symbol_conf.skip_empty &&
evsel__hists(evsel)->stats.nr_samples == 0)
continue;
browser__write_overhead(uib, h, &be->hists[idx++], row);
}
/* print type info */
if (be->indent == 0 && !member->var_name) {
ui_browser__printf(uib, " %#10x %#10x %s%s",
member->offset, member->size,
member->type_name,
list_empty(&member->children) || be->folded? ";" : " {");
} else {
ui_browser__printf(uib, " %#10x %#10x %*s%s\t%s%s",
member->offset, member->size,
be->indent * 4, "", member->type_name,
member->var_name ?: "",
list_empty(&member->children) || be->folded ? ";" : " {");
}
/* fill the rest */
ui_browser__write_nstring(uib, "", uib->width);
}
static void annotated_data_browser__fold(struct annotated_data_browser *browser,
struct browser_entry *entry,
bool recursive)
{
struct browser_entry *child;
if (list_empty(&entry->children))
return;
if (entry->folded && !recursive)
return;
if (recursive) {
list_for_each_entry(child, &entry->children, node)
annotated_data_browser__fold(browser, child, true);
}
entry->nr_entries = 1;
entry->folded = true;
}
static void annotated_data_browser__unfold(struct annotated_data_browser *browser,
struct browser_entry *entry,
bool recursive)
{
struct browser_entry *child;
int nr_entries;
if (list_empty(&entry->children))
return;
if (!entry->folded && !recursive)
return;
nr_entries = 1; /* for self */
list_for_each_entry(child, &entry->children, node) {
if (recursive)
annotated_data_browser__unfold(browser, child, true);
nr_entries += child->nr_entries;
}
entry->nr_entries = nr_entries;
entry->folded = false;
}
static void annotated_data_browser__toggle_fold(struct annotated_data_browser *browser,
bool recursive)
{
struct browser_entry *curr = browser->curr;
struct browser_entry *parent;
parent = curr->parent;
while (parent) {
parent->nr_entries -= curr->nr_entries;
parent = parent->parent;
}
browser->b.nr_entries -= curr->nr_entries;
if (curr->folded)
annotated_data_browser__unfold(browser, curr, recursive);
else
annotated_data_browser__fold(browser, curr, recursive);
parent = curr->parent;
while (parent) {
parent->nr_entries += curr->nr_entries;
parent = parent->parent;
}
browser->b.nr_entries += curr->nr_entries;
assert(browser->b.nr_entries == count_visible_entries(browser));
}
static int annotated_data_browser__run(struct annotated_data_browser *browser,
struct evsel *evsel __maybe_unused,
struct hist_browser_timer *hbt)
{
int delay_secs = hbt ? hbt->refresh : 0;
int key;
if (browser__show(&browser->b) < 0)
return -1;
while (1) {
key = ui_browser__run(&browser->b, delay_secs);
switch (key) {
case K_TIMER:
if (hbt)
hbt->timer(hbt->arg);
continue;
case K_F1:
case 'h':
ui_browser__help_window(&browser->b,
"UP/DOWN/PGUP\n"
"PGDN/SPACE Navigate\n"
"</> Move to prev/next symbol\n"
"e Expand/Collapse current entry\n"
"E Expand/Collapse all children of the current\n"
"q/ESC/CTRL+C Exit\n\n");
continue;
case 'e':
annotated_data_browser__toggle_fold(browser,
/*recursive=*/false);
break;
case 'E':
annotated_data_browser__toggle_fold(browser,
/*recursive=*/true);
break;
case K_LEFT:
case '<':
case '>':
case K_ESC:
case 'q':
case CTRL('c'):
goto out;
default:
ui_browser__warn_unhandled_hotkey(&browser->b, key, delay_secs, ", use 'h'/F1 to see actions");
continue;
}
}
out:
ui_browser__hide(&browser->b);
return key;
}
int hist_entry__annotate_data_tui(struct hist_entry *he, struct evsel *evsel,
struct hist_browser_timer *hbt)
{
struct annotated_data_browser browser = {
.b = {
.refresh = browser__refresh,
.seek = browser__seek,
.write = browser__write,
.priv = he,
.extra_title_lines = 1,
},
.nr_events = 1,
};
int ret;
ui_helpline__push("Press ESC to exit");
if (evsel__is_group_event(evsel)) {
struct evsel *pos;
int nr = 0;
for_each_group_evsel(pos, evsel) {
if (!symbol_conf.skip_empty ||
evsel__hists(pos)->stats.nr_samples)
nr++;
}
browser.nr_events = nr;
}
ret = annotated_data_browser__collect_entries(&browser);
if (ret < 0)
goto out;
/* To get the top and current entry */
browser__refresh(&browser.b);
/* Show the first-level child entries by default */
annotated_data_browser__toggle_fold(&browser, /*recursive=*/false);
ret = annotated_data_browser__run(&browser, evsel, hbt);
out:
annotated_data_browser__delete_entries(&browser);
return ret;
}
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-2.0
#include "ui/browser.h"
#include "ui/keysyms.h"
#include "ui/ui.h"
#include "ui/util.h"
#include "ui/libslang.h"
#include "util/header.h"
#include "util/session.h"
#include <sys/ttydefaults.h>
static void ui_browser__argv_write(struct ui_browser *browser,
void *entry, int row)
{
char **arg = entry;
char *str = *arg;
char empty[] = " ";
bool current_entry = ui_browser__is_current_entry(browser, row);
unsigned long offset = (unsigned long)browser->priv;
if (offset >= strlen(str))
str = empty;
else
str = str + offset;
ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
HE_COLORSET_NORMAL);
ui_browser__write_nstring(browser, str, browser->width);
}
static int list_menu__run(struct ui_browser *menu)
{
int key;
unsigned long offset;
static const char help[] =
"h/?/F1 Show this window\n"
"UP/DOWN/PGUP\n"
"PGDN/SPACE\n"
"LEFT/RIGHT Navigate\n"
"q/ESC/CTRL+C Exit browser";
if (ui_browser__show(menu, "Header information", "Press 'q' to exit") < 0)
return -1;
while (1) {
key = ui_browser__run(menu, 0);
switch (key) {
case K_RIGHT:
offset = (unsigned long)menu->priv;
offset += 10;
menu->priv = (void *)offset;
continue;
case K_LEFT:
offset = (unsigned long)menu->priv;
if (offset >= 10)
offset -= 10;
menu->priv = (void *)offset;
continue;
case K_F1:
case 'h':
case '?':
ui_browser__help_window(menu, help);
continue;
case K_ESC:
case 'q':
case CTRL('c'):
key = -1;
break;
default:
ui_browser__warn_unhandled_hotkey(menu, key, 0, ", use 'h'/'?'/F1 to see actions");
continue;
}
break;
}
ui_browser__hide(menu);
return key;
}
static int ui__list_menu(int argc, char * const argv[])
{
struct ui_browser menu = {
.entries = (void *)argv,
.refresh = ui_browser__argv_refresh,
.seek = ui_browser__argv_seek,
.write = ui_browser__argv_write,
.nr_entries = argc,
};
return list_menu__run(&menu);
}
int tui__header_window(struct perf_session *session)
{
int i, argc = 0;
char **argv;
char *ptr, *pos;
size_t size;
FILE *fp = open_memstream(&ptr, &size);
perf_header__fprintf_info(session, fp, true);
fclose(fp);
for (pos = ptr, argc = 0; (pos = strchr(pos, '\n')) != NULL; pos++)
argc++;
argv = calloc(argc + 1, sizeof(*argv));
if (argv == NULL)
goto out;
argv[0] = pos = ptr;
for (i = 1; (pos = strchr(pos, '\n')) != NULL; i++) {
*pos++ = '\0';
argv[i] = pos;
}
BUG_ON(i != argc + 1);
ui__list_menu(argc, argv);
out:
free(argv);
free(ptr);
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_BROWSER_HISTS_H_
#define _PERF_UI_BROWSER_HISTS_H_ 1
#include "ui/browser.h"
struct evsel;
struct hist_browser {
struct ui_browser b;
struct hists *hists;
struct hist_entry *he_selection;
struct map_symbol *selection;
struct hist_browser_timer *hbt;
struct pstack *pstack;
struct perf_env *env;
struct evsel *block_evsel;
int print_seq;
bool show_dso;
bool show_headers;
float min_pcnt;
u64 nr_non_filtered_entries;
u64 nr_hierarchy_entries;
u64 nr_callchain_rows;
bool c2c_filter;
/* Get title string. */
int (*title)(struct hist_browser *browser,
char *bf, size_t size);
};
struct hist_browser *hist_browser__new(struct hists *hists);
void hist_browser__delete(struct hist_browser *browser);
int hist_browser__run(struct hist_browser *browser, const char *help,
bool warn_lost_event, int key);
void hist_browser__init(struct hist_browser *browser,
struct hists *hists);
#endif /* _PERF_UI_BROWSER_HISTS_H_ */
+136
View File
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0
#include <elf.h>
#include <inttypes.h>
#include <sys/ttydefaults.h>
#include <stdlib.h>
#include <string.h>
#include <linux/bitops.h>
#include "../../util/debug.h"
#include "../../util/map.h"
#include "../../util/dso.h"
#include "../../util/symbol.h"
#include "../browser.h"
#include "../helpline.h"
#include "../keysyms.h"
#include "map.h"
#include <linux/ctype.h>
struct map_browser {
struct ui_browser b;
struct map *map;
u8 addrlen;
};
static void map_browser__write(struct ui_browser *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);
ui_browser__printf(browser, "%*" PRIx64 " %*" PRIx64 " %c ",
mb->addrlen, sym->start, mb->addrlen, sym->end,
symbol__binding(sym) == STB_GLOBAL ? 'g' :
symbol__binding(sym) == STB_LOCAL ? 'l' : 'w');
width = browser->width - ((mb->addrlen * 2) + 4);
if (width > 0)
ui_browser__write_nstring(browser, 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);
} else
sym = map__find_symbol_by_name(browser->map, target);
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, dso__long_name(map__dso(browser->map)),
"Press ESC to exit, %s / to search",
verbose > 0 ? "" : "restart with -v to use") < 0)
return -1;
while (1) {
key = ui_browser__run(&browser->b, 0);
switch (key) {
case '/':
if (verbose > 0)
map_browser__search(browser);
/* fall thru */
default:
ui_browser__warn_unhandled_hotkey(&browser->b, key, 0, NULL);
continue;
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 = dso__symbols(map__dso(map)),
.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 > 0) {
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);
}
+7
View File
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_MAP_BROWSER_H_
#define _PERF_UI_MAP_BROWSER_H_ 1
struct map;
int map__browse(struct map *map);
#endif /* _PERF_UI_MAP_BROWSER_H_ */
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: GPL-2.0
/* Display a menu with individual samples to browse with perf script */
#include "hist.h"
#include "evsel.h"
#include "hists.h"
#include "sort.h"
#include "config.h"
#include "time-utils.h"
#include "../util.h"
#include "../../util/util.h" // perf_exe()
#include "../../perf.h"
#include <stdlib.h>
#include <string.h>
#include <linux/time64.h>
#include <linux/zalloc.h>
static u64 context_len = 10 * NSEC_PER_MSEC;
static int res_sample_config(const char *var, const char *value, void *data __maybe_unused)
{
if (!strcmp(var, "samples.context"))
return perf_config_u64(&context_len, var, value);
return 0;
}
void res_sample_init(void)
{
perf_config(res_sample_config, NULL);
}
int res_sample_browse(struct res_sample *res_samples, int num_res,
struct evsel *evsel, enum rstype rstype)
{
char **names;
int i, n;
int choice;
char *cmd;
char pbuf[256], tidbuf[32], cpubuf[32];
const char *perf = perf_exe(pbuf, sizeof pbuf);
char trange[128], tsample[64];
struct res_sample *r;
char extra_format[256];
names = calloc(num_res, sizeof(char *));
if (!names)
return -1;
for (i = 0; i < num_res; i++) {
char tbuf[64];
timestamp__scnprintf_nsec(res_samples[i].time, tbuf, sizeof tbuf);
if (asprintf(&names[i], "%s: CPU %d tid %d", tbuf,
res_samples[i].cpu, res_samples[i].tid) < 0) {
while (--i >= 0)
zfree(&names[i]);
free(names);
return -1;
}
}
choice = ui__popup_menu(num_res, names, NULL);
for (i = 0; i < num_res; i++)
zfree(&names[i]);
free(names);
if (choice < 0 || choice >= num_res)
return -1;
r = &res_samples[choice];
n = timestamp__scnprintf_nsec(r->time - context_len, trange, sizeof trange);
trange[n++] = ',';
timestamp__scnprintf_nsec(r->time + context_len, trange + n, sizeof trange - n);
timestamp__scnprintf_nsec(r->time, tsample, sizeof tsample);
attr_to_script(extra_format, &evsel->core.attr);
if (asprintf(&cmd, "%s script %s%s --time %s %s%s %s%s --ns %s %s %s %s %s | less +/%s",
perf,
input_name ? "-i " : "",
input_name ? input_name : "",
trange,
r->cpu >= 0 ? "--cpu " : "",
r->cpu >= 0 ? (sprintf(cpubuf, "%d", r->cpu), cpubuf) : "",
r->tid ? "--tid " : "",
r->tid ? (sprintf(tidbuf, "%d", r->tid), tidbuf) : "",
extra_format,
rstype == A_ASM ? "-F +disasm" :
rstype == A_SOURCE ? "-F +srcline,+srccode" : "",
symbol_conf.inline_name ? "--inline" : "",
"--show-lost-events ",
r->tid ? "--show-switch-events --show-task-events " : "",
tsample) < 0)
return -1;
run_script(cmd);
free(cmd);
return 0;
}
@@ -0,0 +1,367 @@
// SPDX-License-Identifier: GPL-2.0
#include "../../util/util.h" // perf_exe()
#include "../util.h"
#include "../../util/evlist.h"
#include "../../util/hist.h"
#include "../../util/debug.h"
#include "../../util/session.h"
#include "../../util/symbol.h"
#include "../browser.h"
#include "../libslang.h"
#include "config.h"
#include <linux/err.h>
#include <linux/string.h>
#include <linux/zalloc.h>
#include <subcmd/exec-cmd.h>
#include <stdlib.h>
#define SCRIPT_NAMELEN 128
#define SCRIPT_MAX_NO 64
/*
* 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
struct script_config {
const char **names;
char **paths;
int index;
const char *perf;
char extra_format[256];
};
void attr_to_script(char *extra_format, struct perf_event_attr *attr)
{
extra_format[0] = 0;
if (attr->read_format & PERF_FORMAT_GROUP)
strcat(extra_format, " -F +metric");
if (attr->sample_type & PERF_SAMPLE_BRANCH_STACK)
strcat(extra_format, " -F +brstackinsn --xed");
if (attr->sample_type & PERF_SAMPLE_REGS_INTR)
strcat(extra_format, " -F +iregs");
if (attr->sample_type & PERF_SAMPLE_REGS_USER)
strcat(extra_format, " -F +uregs");
if (attr->sample_type & PERF_SAMPLE_PHYS_ADDR)
strcat(extra_format, " -F +phys_addr");
}
static int add_script_option(const char *name, const char *opt,
struct script_config *c)
{
c->names[c->index] = name;
if (asprintf(&c->paths[c->index],
"%s script %s -F +metric %s %s",
c->perf, opt, symbol_conf.inline_name ? " --inline" : "",
c->extra_format) < 0)
return -1;
c->index++;
return 0;
}
static int scripts_config(const char *var, const char *value, void *data)
{
struct script_config *c = data;
if (!strstarts(var, "scripts."))
return -1;
if (c->index >= SCRIPT_MAX_NO)
return -1;
c->names[c->index] = strdup(var + 7);
if (!c->names[c->index])
return -1;
if (asprintf(&c->paths[c->index], "%s %s", value,
c->extra_format) < 0)
return -1;
c->index++;
return 0;
}
/*
* Some scripts specify the required events in their "xxx-record" file,
* this function will check if the events in perf.data match those
* mentioned in the "xxx-record".
*
* Fixme: All existing "xxx-record" are all in good formats "-e event ",
* which is covered well now. And new parsing code should be added to
* cover the future complex formats like event groups etc.
*/
static int check_ev_match(int dir_fd, const char *scriptname, struct perf_session *session)
{
char line[BUFSIZ];
FILE *fp;
{
char filename[NAME_MAX + 5];
int fd;
scnprintf(filename, sizeof(filename), "bin/%s-record", scriptname);
fd = openat(dir_fd, filename, O_RDONLY);
if (fd == -1)
return -1;
fp = fdopen(fd, "r");
if (!fp)
return -1;
}
while (fgets(line, sizeof(line), fp)) {
char *p = skip_spaces(line);
if (*p == '#')
continue;
while (strlen(p)) {
int match, len;
struct evsel *pos;
char evname[128];
p = strstr(p, "-e");
if (!p)
break;
p += 2;
p = skip_spaces(p);
len = strcspn(p, " \t");
if (!len)
break;
snprintf(evname, len + 1, "%s", p);
match = 0;
evlist__for_each_entry(session->evlist, pos) {
if (evsel__name_is(pos, evname)) {
match = 1;
break;
}
}
if (!match) {
fclose(fp);
return -1;
}
}
}
fclose(fp);
return 0;
}
/*
* Return -1 if none is found, otherwise the actual scripts number.
*
* Currently the only user of this function is the script browser, which
* will list all statically runnable scripts, select one, execute it and
* show the output in a perf browser.
*/
static int find_scripts(char **scripts_array, char **scripts_path_array, int num,
int pathlen)
{
struct dirent *script_dirent, *lang_dirent;
int scripts_dir_fd, lang_dir_fd;
DIR *scripts_dir, *lang_dir;
struct perf_session *session;
struct perf_data data = {
.path = input_name,
.mode = PERF_DATA_MODE_READ,
};
char *temp;
int i = 0;
const char *exec_path = get_argv_exec_path();
session = perf_session__new(&data, NULL);
if (IS_ERR(session))
return PTR_ERR(session);
{
char scripts_path[PATH_MAX];
snprintf(scripts_path, sizeof(scripts_path), "%s/scripts", exec_path);
scripts_dir_fd = open(scripts_path, O_DIRECTORY);
pr_err("Failed to open directory '%s'", scripts_path);
if (scripts_dir_fd == -1) {
perf_session__delete(session);
return -1;
}
}
scripts_dir = fdopendir(scripts_dir_fd);
if (!scripts_dir) {
close(scripts_dir_fd);
perf_session__delete(session);
return -1;
}
while ((lang_dirent = readdir(scripts_dir)) != NULL) {
if (lang_dirent->d_type != DT_DIR &&
(lang_dirent->d_type == DT_UNKNOWN &&
!is_directory_at(scripts_dir_fd, lang_dirent->d_name)))
continue;
if (!strcmp(lang_dirent->d_name, ".") || !strcmp(lang_dirent->d_name, ".."))
continue;
#ifndef HAVE_LIBPERL_SUPPORT
if (strstr(lang_dirent->d_name, "perl"))
continue;
#endif
#ifndef HAVE_LIBPYTHON_SUPPORT
if (strstr(lang_dirent->d_name, "python"))
continue;
#endif
lang_dir_fd = openat(scripts_dir_fd, lang_dirent->d_name, O_DIRECTORY);
if (lang_dir_fd == -1)
continue;
lang_dir = fdopendir(lang_dir_fd);
if (!lang_dir) {
close(lang_dir_fd);
continue;
}
while ((script_dirent = readdir(lang_dir)) != NULL) {
if (script_dirent->d_type == DT_DIR)
continue;
if (script_dirent->d_type == DT_UNKNOWN &&
is_directory_at(lang_dir_fd, script_dirent->d_name))
continue;
/* Skip those real time scripts: xxxtop.p[yl] */
if (strstr(script_dirent->d_name, "top."))
continue;
if (i >= num)
break;
scnprintf(scripts_path_array[i], pathlen, "%s/scripts/%s/%s",
exec_path,
lang_dirent->d_name,
script_dirent->d_name);
temp = strchr(script_dirent->d_name, '.');
snprintf(scripts_array[i],
(temp - script_dirent->d_name) + 1,
"%s", script_dirent->d_name);
if (check_ev_match(lang_dir_fd, scripts_array[i], session))
continue;
i++;
}
closedir(lang_dir);
}
closedir(scripts_dir);
perf_session__delete(session);
return i;
}
/*
* When success, will copy the full path of the selected script
* into the buffer pointed by script_name, and return 0.
* Return -1 on failure.
*/
static int list_scripts(char *script_name, bool *custom,
struct evsel *evsel)
{
char *buf, *paths[SCRIPT_MAX_NO], *names[SCRIPT_MAX_NO];
int i, num, choice;
int ret = 0;
int max_std, custom_perf;
char pbuf[256];
const char *perf = perf_exe(pbuf, sizeof pbuf);
struct script_config scriptc = {
.names = (const char **)names,
.paths = paths,
.perf = perf
};
script_name[0] = 0;
/* Preset the script name to SCRIPT_NAMELEN */
buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN));
if (!buf)
return -1;
if (evsel)
attr_to_script(scriptc.extra_format, &evsel->core.attr);
add_script_option("Show individual samples", "", &scriptc);
add_script_option("Show individual samples with assembler", "-F +disasm",
&scriptc);
add_script_option("Show individual samples with source", "-F +srcline,+srccode",
&scriptc);
perf_config(scripts_config, &scriptc);
custom_perf = scriptc.index;
add_script_option("Show samples with custom perf script arguments", "", &scriptc);
i = scriptc.index;
max_std = i;
for (; i < SCRIPT_MAX_NO; i++) {
names[i] = buf + (i - max_std) * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN);
paths[i] = names[i] + SCRIPT_NAMELEN;
}
num = find_scripts(names + max_std, paths + max_std, SCRIPT_MAX_NO - max_std,
SCRIPT_FULLPATH_LEN);
if (num < 0)
num = 0;
choice = ui__popup_menu(num + max_std, (char * const *)names, NULL);
if (choice < 0) {
ret = -1;
goto out;
}
if (choice == custom_perf) {
char script_args[50];
int key = ui_browser__input_window("perf script command",
"Enter perf script command line (without perf script prefix)",
script_args, "", 0);
if (key != K_ENTER) {
ret = -1;
goto out;
}
sprintf(script_name, "%s script %s", perf, script_args);
} else if (choice < num + max_std) {
strcpy(script_name, paths[choice]);
}
*custom = choice >= max_std;
out:
free(buf);
for (i = 0; i < max_std; i++)
zfree(&paths[i]);
return ret;
}
void run_script(char *cmd)
{
pr_debug("Running %s\n", cmd);
SLang_reset_tty();
if (system(cmd) < 0)
pr_warning("Cannot run %s\n", cmd);
/*
* SLang doesn't seem to reset the whole terminal, so be more
* forceful to get back to the original state.
*/
printf("\033[c\033[H\033[J");
fflush(stdout);
SLang_init_tty(0, 0, 0);
SLtty_set_suspend_state(true);
SLsmg_refresh();
}
int script_browse(const char *script_opt, struct evsel *evsel)
{
char *cmd, script_name[SCRIPT_FULLPATH_LEN];
bool custom = false;
memset(script_name, 0, SCRIPT_FULLPATH_LEN);
if (list_scripts(script_name, &custom, evsel))
return -1;
if (asprintf(&cmd, "%s%s %s %s%s 2>&1 | less",
custom ? "perf script -s " : "",
script_name,
script_opt ? script_opt : "",
input_name ? "-i " : "",
input_name ? input_name : "") < 0)
return -1;
run_script(cmd);
free(cmd);
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
CFLAGS_gtk += -fPIC $(GTK_CFLAGS) -Wno-deprecated-declarations
gtk-y += browser.o
gtk-y += hists.o
gtk-y += setup.o
gtk-y += util.o
gtk-y += helpline.o
gtk-y += progress.o
gtk-y += annotate.o
gtk-y += zalloc.o
$(OUTPUT)ui/gtk/zalloc.o: ../lib/zalloc.c FORCE
$(call rule_mkdir)
$(call if_changed_dep,cc_o_c)
+278
View File
@@ -0,0 +1,278 @@
// SPDX-License-Identifier: GPL-2.0
#include "gtk.h"
#include "util/sort.h"
#include "util/debug.h"
#include "util/annotate.h"
#include "util/evlist.h"
#include "util/evsel.h"
#include "util/map.h"
#include "util/dso.h"
#include "util/symbol.h"
#include "ui/helpline.h"
#include <inttypes.h>
#include <signal.h>
enum {
ANN_COL__PERCENT,
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, const struct evsel *evsel)
{
struct annotation *notes;
struct sym_hist *symhist;
struct sym_hist_entry *entry;
double percent = 0.0;
const char *markup;
int ret = 0;
u64 nr_samples = 0;
strcpy(buf, "");
if (dl->al.offset == (s64) -1)
return 0;
notes = symbol__annotation(sym);
symhist = annotation__histogram(notes, evsel);
entry = annotated_source__hist_entry(notes->src, evsel, dl->al.offset);
if (entry)
nr_samples = entry->nr_samples;
if (!symbol_conf.event_group && nr_samples == 0)
return 0;
percent = 100.0 * nr_samples / symhist->nr_samples;
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 map_symbol *ms,
struct disasm_line *dl)
{
u64 start = map__rip_2objdump(ms->map, ms->sym->start);
strcpy(buf, "");
if (dl->al.offset == (s64) -1)
return 0;
return scnprintf(buf, size, "%"PRIx64, start + dl->al.offset);
}
static int perf_gtk__get_line(char *buf, size_t size, struct disasm_line *dl)
{
int ret = 0;
char *line = g_markup_escape_text(dl->al.line, -1);
const char *markup = "<span fgcolor='gray'>";
strcpy(buf, "");
if (!line)
return 0;
if (dl->al.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 map_symbol *ms,
struct evsel *evsel,
struct hist_browser_timer *hbt __maybe_unused)
{
struct symbol *sym = ms->sym;
struct disasm_line *pos, *n;
struct annotation *notes;
GType col_types[MAX_ANN_COLS];
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, &notes->src->source, al.node) {
GtkTreeIter iter;
int ret = 0;
gtk_list_store_append(store, &iter);
if (evsel__is_group_event(evsel)) {
struct evsel *cur_evsel;
for_each_group_evsel(cur_evsel, evsel__leader(evsel)) {
ret += perf_gtk__get_percent(s + ret,
sizeof(s) - ret,
sym, pos,
cur_evsel);
ret += scnprintf(s + ret, sizeof(s) - ret, " ");
}
} else {
ret = perf_gtk__get_percent(s, sizeof(s), sym, pos, evsel);
}
if (ret)
gtk_list_store_set(store, &iter, ANN_COL__PERCENT, s, -1);
if (perf_gtk__get_offset(s, sizeof(s), ms, pos))
gtk_list_store_set(store, &iter, ANN_COL__OFFSET, s, -1);
if (perf_gtk__get_line(s, sizeof(s), pos))
gtk_list_store_set(store, &iter, ANN_COL__LINE, s, -1);
}
gtk_container_add(GTK_CONTAINER(window), view);
list_for_each_entry_safe(pos, n, &notes->src->source, al.node) {
list_del_init(&pos->al.node);
disasm_line__free(pos);
}
return 0;
}
static int symbol__gtk_annotate(struct map_symbol *ms, struct evsel *evsel,
struct hist_browser_timer *hbt)
{
struct dso *dso = map__dso(ms->map);
struct symbol *sym = ms->sym;
GtkWidget *window;
GtkWidget *notebook;
GtkWidget *scrolled_window;
GtkWidget *tab_label;
int err;
if (dso__annotate_warned(dso))
return -1;
err = symbol__annotate(ms, evsel, NULL);
if (err) {
char msg[BUFSIZ];
dso__set_annotate_warned(dso);
symbol__strerror_disassemble(ms, err, msg, sizeof(msg));
ui__error("Couldn't annotate %s: %s\n", sym->name, msg);
return -1;
}
symbol__calc_percent(sym, evsel);
if (perf_gtk__is_active_context(pgctx)) {
window = pgctx->main_window;
notebook = pgctx->notebook;
} 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, ms, evsel, hbt);
return 0;
}
int hist_entry__gtk_annotate(struct hist_entry *he,
struct evsel *evsel,
struct hist_browser_timer *hbt)
{
return symbol__gtk_annotate(&he->ms, evsel, hbt);
}
void perf_gtk__show_annotations(void)
{
GtkWidget *window;
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);
}
+86
View File
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0
#include "gtk.h"
#include "../evsel.h"
#include "../sort.h"
#include "../hist.h"
#include "../helpline.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;
}
+67
View File
@@ -0,0 +1,67 @@
/* SPDX-License-Identifier: GPL-2.0 */
#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 evsel;
struct evlist;
struct hist_entry;
struct hist_browser_timer;
int evlist__gtk_browse_hists(struct evlist *evlist, const char *help,
struct hist_browser_timer *hbt, float min_pcnt);
int hist_entry__gtk_annotate(struct hist_entry *he,
struct evsel *evsel,
struct hist_browser_timer *hbt);
void perf_gtk__show_annotations(void);
#endif /* _PERF_GTK_H_ */
+58
View File
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0
#include "gtk.h"
#include <stdio.h>
#include <string.h>
#include <linux/kernel.h>
#include "../ui.h"
#include "../helpline.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 = &gtk_helpline_fns;
}
+681
View File
@@ -0,0 +1,681 @@
// SPDX-License-Identifier: GPL-2.0
#include "gtk.h"
#include "../evlist.h"
#include "../callchain.h"
#include "../evsel.h"
#include "../sort.h"
#include "../hist.h"
#include "../helpline.h"
#include "../string2.h"
#include <signal.h>
#include <stdlib.h>
#include <linux/string.h>
#define MAX_COLUMNS 32
static int __percent_color_snprintf(struct perf_hpp *hpp, const char *fmt, ...)
{
int ret = 0;
int len;
va_list args;
double percent;
const char *markup;
char *buf = hpp->buf;
size_t size = hpp->size;
va_start(args, fmt);
len = va_arg(args, int);
percent = va_arg(args, double);
va_end(args);
markup = perf_gtk__get_percent_color(percent);
if (markup)
ret += scnprintf(buf, size, markup);
ret += scnprintf(buf + ret, size - ret, fmt, len, 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, \
struct perf_hpp *hpp, \
struct hist_entry *he) \
{ \
return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \
__percent_color_snprintf, true); \
}
#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
static u64 he_get_acc_##_field(struct hist_entry *he) \
{ \
return he->stat_acc->_field; \
} \
\
static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
struct perf_hpp *hpp, \
struct hist_entry *he) \
{ \
return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.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 perf_gtk__add_callchain_flat(struct rb_root *root, GtkTreeStore *store,
GtkTreeIter *parent, int col, u64 total)
{
struct rb_node *nd;
bool has_single_node = (rb_first(root) == rb_last(root));
for (nd = rb_first(root); nd; nd = rb_next(nd)) {
struct callchain_node *node;
struct callchain_list *chain;
GtkTreeIter iter, new_parent;
bool need_new_parent;
node = rb_entry(nd, struct callchain_node, rb_node);
new_parent = *parent;
need_new_parent = !has_single_node;
callchain_node__make_parent_list(node);
list_for_each_entry(chain, &node->parent_val, list) {
char buf[128];
gtk_tree_store_append(store, &iter, &new_parent);
callchain_node__scnprintf_value(node, buf, sizeof(buf), total);
gtk_tree_store_set(store, &iter, 0, buf, -1);
callchain_list__sym_name(chain, buf, sizeof(buf), false);
gtk_tree_store_set(store, &iter, col, buf, -1);
if (need_new_parent) {
/*
* Only show the top-most symbol in a callchain
* if it's not the only callchain.
*/
new_parent = iter;
need_new_parent = false;
}
}
list_for_each_entry(chain, &node->val, list) {
char buf[128];
gtk_tree_store_append(store, &iter, &new_parent);
callchain_node__scnprintf_value(node, buf, sizeof(buf), total);
gtk_tree_store_set(store, &iter, 0, buf, -1);
callchain_list__sym_name(chain, buf, sizeof(buf), false);
gtk_tree_store_set(store, &iter, col, buf, -1);
if (need_new_parent) {
/*
* Only show the top-most symbol in a callchain
* if it's not the only callchain.
*/
new_parent = iter;
need_new_parent = false;
}
}
}
}
static void perf_gtk__add_callchain_folded(struct rb_root *root, GtkTreeStore *store,
GtkTreeIter *parent, int col, u64 total)
{
struct rb_node *nd;
for (nd = rb_first(root); nd; nd = rb_next(nd)) {
struct callchain_node *node;
struct callchain_list *chain;
GtkTreeIter iter;
char buf[64];
char *str, *str_alloc = NULL;
bool first = true;
node = rb_entry(nd, struct callchain_node, rb_node);
callchain_node__make_parent_list(node);
list_for_each_entry(chain, &node->parent_val, list) {
char name[1024];
callchain_list__sym_name(chain, name, sizeof(name), false);
if (asprintf(&str, "%s%s%s",
first ? "" : str_alloc,
first ? "" : symbol_conf.field_sep ?: "; ",
name) < 0)
return;
first = false;
free(str_alloc);
str_alloc = str;
}
list_for_each_entry(chain, &node->val, list) {
char name[1024];
callchain_list__sym_name(chain, name, sizeof(name), false);
if (asprintf(&str, "%s%s%s",
first ? "" : str_alloc,
first ? "" : symbol_conf.field_sep ?: "; ",
name) < 0)
return;
first = false;
free(str_alloc);
str_alloc = str;
}
gtk_tree_store_append(store, &iter, parent);
callchain_node__scnprintf_value(node, buf, sizeof(buf), total);
gtk_tree_store_set(store, &iter, 0, buf, -1);
gtk_tree_store_set(store, &iter, col, str, -1);
free(str_alloc);
}
}
static void perf_gtk__add_callchain_graph(struct rb_root *root, GtkTreeStore *store,
GtkTreeIter *parent, int col, u64 total)
{
struct rb_node *nd;
bool has_single_node = (rb_first(root) == rb_last(root));
for (nd = rb_first(root); nd; nd = rb_next(nd)) {
struct callchain_node *node;
struct callchain_list *chain;
GtkTreeIter iter, new_parent;
bool need_new_parent;
u64 child_total;
node = rb_entry(nd, struct callchain_node, rb_node);
new_parent = *parent;
need_new_parent = !has_single_node && (node->val_nr > 1);
list_for_each_entry(chain, &node->val, list) {
char buf[128];
gtk_tree_store_append(store, &iter, &new_parent);
callchain_node__scnprintf_value(node, buf, sizeof(buf), total);
gtk_tree_store_set(store, &iter, 0, buf, -1);
callchain_list__sym_name(chain, buf, sizeof(buf), false);
gtk_tree_store_set(store, &iter, col, buf, -1);
if (need_new_parent) {
/*
* Only show the top-most symbol in a callchain
* if it's not the only callchain.
*/
new_parent = iter;
need_new_parent = false;
}
}
if (callchain_param.mode == CHAIN_GRAPH_REL)
child_total = node->children_hit;
else
child_total = total;
/* Now 'iter' contains info of the last callchain_list */
perf_gtk__add_callchain_graph(&node->rb_root, store, &iter, col,
child_total);
}
}
static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store,
GtkTreeIter *parent, int col, u64 total)
{
if (callchain_param.mode == CHAIN_FLAT)
perf_gtk__add_callchain_flat(root, store, parent, col, total);
else if (callchain_param.mode == CHAIN_FOLDED)
perf_gtk__add_callchain_folded(root, store, parent, col, total);
else
perf_gtk__add_callchain_graph(root, store, parent, col, total);
}
static void on_row_activated(GtkTreeView *view, GtkTreePath *path,
GtkTreeViewColumn *col __maybe_unused,
gpointer user_data __maybe_unused)
{
bool expanded = gtk_tree_view_row_expanded(view, path);
if (expanded)
gtk_tree_view_collapse_row(view, path);
else
gtk_tree_view_expand_row(view, path, FALSE);
}
static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
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;
hists__for_each_format(hists, 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;
hists__for_each_format(hists, fmt) {
if (perf_hpp__should_skip(fmt, hists))
continue;
/*
* XXX no way to determine where symcol column is..
* Just use last column for now.
*/
if (perf_hpp__is_sort_entry(fmt))
sym_col = col_idx;
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
-1, fmt->name,
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_cached(&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;
hists__for_each_format(hists, fmt) {
if (perf_hpp__should_skip(fmt, h->hists))
continue;
if (fmt->color)
fmt->color(fmt, &hpp, h);
else
fmt->entry(fmt, &hpp, h);
gtk_tree_store_set(store, &iter, col_idx++, s, -1);
}
if (hist_entry__has_callchains(h) &&
symbol_conf.use_callchain && hists__has(hists, sym)) {
if (callchain_param.mode == CHAIN_GRAPH_REL)
total = symbol_conf.cumulate_callchain ?
h->stat_acc->period : h->stat.period;
perf_gtk__add_callchain(&h->sorted_chain, store, &iter,
sym_col, total);
}
}
gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
g_signal_connect(view, "row-activated",
G_CALLBACK(on_row_activated), NULL);
gtk_container_add(GTK_CONTAINER(window), view);
}
static void perf_gtk__add_hierarchy_entries(struct hists *hists,
struct rb_root_cached *root,
GtkTreeStore *store,
GtkTreeIter *parent,
struct perf_hpp *hpp,
float min_pcnt)
{
int col_idx = 0;
struct rb_node *node;
struct hist_entry *he;
struct perf_hpp_fmt *fmt;
struct perf_hpp_list_node *fmt_node;
u64 total = hists__total_period(hists);
int size;
for (node = rb_first_cached(root); node; node = rb_next(node)) {
GtkTreeIter iter;
float percent;
char *bf;
he = rb_entry(node, struct hist_entry, rb_node);
if (he->filtered)
continue;
percent = hist_entry__get_percent_limit(he);
if (percent < min_pcnt)
continue;
gtk_tree_store_append(store, &iter, parent);
col_idx = 0;
/* the first hpp_list_node is for overhead columns */
fmt_node = list_first_entry(&hists->hpp_formats,
struct perf_hpp_list_node, list);
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
if (fmt->color)
fmt->color(fmt, hpp, he);
else
fmt->entry(fmt, hpp, he);
gtk_tree_store_set(store, &iter, col_idx++, hpp->buf, -1);
}
bf = hpp->buf;
size = hpp->size;
perf_hpp_list__for_each_format(he->hpp_list, fmt) {
int ret;
if (fmt->color)
ret = fmt->color(fmt, hpp, he);
else
ret = fmt->entry(fmt, hpp, he);
snprintf(hpp->buf + ret, hpp->size - ret, " ");
advance_hpp(hpp, ret + 2);
}
gtk_tree_store_set(store, &iter, col_idx, strim(bf), -1);
if (!he->leaf) {
hpp->buf = bf;
hpp->size = size;
perf_gtk__add_hierarchy_entries(hists, &he->hroot_out,
store, &iter, hpp,
min_pcnt);
if (!hist_entry__has_hierarchy_children(he, min_pcnt)) {
char buf[32];
GtkTreeIter child;
snprintf(buf, sizeof(buf), "no entry >= %.2f%%",
min_pcnt);
gtk_tree_store_append(store, &child, &iter);
gtk_tree_store_set(store, &child, col_idx, buf, -1);
}
}
if (he->leaf && hist_entry__has_callchains(he) && symbol_conf.use_callchain) {
if (callchain_param.mode == CHAIN_GRAPH_REL)
total = symbol_conf.cumulate_callchain ?
he->stat_acc->period : he->stat.period;
perf_gtk__add_callchain(&he->sorted_chain, store, &iter,
col_idx, total);
}
}
}
static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists,
float min_pcnt)
{
struct perf_hpp_fmt *fmt;
struct perf_hpp_list_node *fmt_node;
GType col_types[MAX_COLUMNS];
GtkCellRenderer *renderer;
GtkTreeStore *store;
GtkWidget *view;
int col_idx;
int nr_cols = 0;
char s[512];
char buf[512];
bool first_node, first_col;
struct perf_hpp hpp = {
.buf = s,
.size = sizeof(s),
};
hists__for_each_format(hists, fmt) {
if (perf_hpp__is_sort_entry(fmt) ||
perf_hpp__is_dynamic_entry(fmt))
break;
col_types[nr_cols++] = G_TYPE_STRING;
}
col_types[nr_cols++] = G_TYPE_STRING;
store = gtk_tree_store_newv(nr_cols, col_types);
view = gtk_tree_view_new();
renderer = gtk_cell_renderer_text_new();
col_idx = 0;
/* the first hpp_list_node is for overhead columns */
fmt_node = list_first_entry(&hists->hpp_formats,
struct perf_hpp_list_node, list);
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
-1, fmt->name,
renderer, "markup",
col_idx++, NULL);
}
/* construct merged column header since sort keys share single column */
buf[0] = '\0';
first_node = true;
list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
if (!first_node)
strcat(buf, " / ");
first_node = false;
first_col = true;
perf_hpp_list__for_each_format(&fmt_node->hpp ,fmt) {
if (perf_hpp__should_skip(fmt, hists))
continue;
if (!first_col)
strcat(buf, "+");
first_col = false;
fmt->header(fmt, &hpp, hists, 0, NULL);
strcat(buf, strim(hpp.buf));
}
}
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
-1, buf,
renderer, "markup",
col_idx++, NULL);
for (col_idx = 0; col_idx < nr_cols; col_idx++) {
GtkTreeViewColumn *column;
column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx);
gtk_tree_view_column_set_resizable(column, TRUE);
if (col_idx == 0) {
gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view),
column);
}
}
gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
g_object_unref(GTK_TREE_MODEL(store));
perf_gtk__add_hierarchy_entries(hists, &hists->entries, store,
NULL, &hpp, min_pcnt);
gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
g_signal_connect(view, "row-activated",
G_CALLBACK(on_row_activated), NULL);
gtk_container_add(GTK_CONTAINER(window), view);
}
int evlist__gtk_browse_hists(struct evlist *evlist, const char *help,
struct hist_browser_timer *hbt __maybe_unused, float min_pcnt)
{
struct 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_entry(evlist, pos) {
struct hists *hists = evsel__hists(pos);
const char *evname = evsel__name(pos);
GtkWidget *scrolled_window;
GtkWidget *tab_label;
char buf[512];
size_t size = sizeof(buf);
if (symbol_conf.event_group) {
if (!evsel__is_group_leader(pos))
continue;
if (pos->core.nr_members > 1) {
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);
if (symbol_conf.report_hierarchy)
perf_gtk__show_hierarchy(scrolled_window, hists, min_pcnt);
else
perf_gtk__show_hists(scrolled_window, hists, min_pcnt);
tab_label = gtk_label_new(evname);
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;
}
+59
View File
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0
#include <inttypes.h>
#include "gtk.h"
#include "../progress.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 = &gtk_ui_progress__ops;
}
+24
View File
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0
#include "gtk.h"
#include <linux/compiler.h>
#include "../util.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();
}
+113
View File
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0
#include "../util.h"
#include "gtk.h"
#include <stdlib.h>
#include <string.h>
#include <linux/zalloc.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
};
+83
View File
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <stdlib.h>
#include <string.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);
}
void ui_helpline__printf(const char *fmt, ...)
{
va_list ap;
ui_helpline__pop();
va_start(ap, fmt);
ui_helpline__vpush(fmt, ap);
va_end(ap);
}
+29
View File
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_HELPLINE_H_
#define _PERF_UI_HELPLINE_H_ 1
#include <stdio.h>
#include <stdarg.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);
void ui_helpline__printf(const char *fmt, ...);
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_ */
File diff suppressed because it is too large Load Diff
+44
View File
@@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include "keysyms.h"
#include <linux/ctype.h>
#include <linux/kernel.h>
const char *key_name(int key, char *bf, size_t size)
{
if (isprint(key)) {
scnprintf(bf, size, "%c", key);
} else if (key < 32) {
scnprintf(bf, size, "Ctrl+%c", key + '@');
} else {
const char *name = NULL;
switch (key) {
case K_DOWN: name = "Down"; break;
case K_END: name = "End"; break;
case K_ENTER: name = "Enter"; break;
case K_ESC: name = "ESC"; break;
case K_F1: name = "F1"; break;
case K_HOME: name = "Home"; break;
case K_LEFT: name = "Left"; break;
case K_PGDN: name = "PgDown"; break;
case K_PGUP: name = "PgUp"; break;
case K_RIGHT: name = "Right"; break;
case K_TAB: name = "Tab"; break;
case K_UNTAB: name = "Untab"; break;
case K_UP: name = "Up"; break;
case K_BKSPC: name = "Backspace"; break;
case K_DEL: name = "Del"; break;
default:
if (key >= SL_KEY_F(1) && key <= SL_KEY_F(63))
scnprintf(bf, size, "F%d", key - SL_KEY_F(0));
else
scnprintf(bf, size, "Unknown (%d)", key);
}
if (name)
scnprintf(bf, size, "%s", name);
}
return bf;
}
+32
View File
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0 */
#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
#define K_RELOAD -5
const char *key_name(int key, char *bf, size_t size);
#endif /* _PERF_KEYSYMS_H_ */
+22
View File
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
#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
/* Enable future slang's corrected function prototypes. */
#define ENABLE_SLFUTURE_CONST 1
#define ENABLE_SLFUTURE_VOID 1
#include <slang.h>
#define SL_KEY_UNTAB 0x1000
#endif /* _PERF_UI_SLANG_H_ */
+47
View File
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.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)
{
u64 last = p->curr;
p->curr += adv;
if (p->curr >= p->next) {
u64 nr = DIV_ROUND_UP(p->curr - last, p->step);
p->next += nr * p->step;
ui_progress__ops->update(p);
}
}
void __ui_progress__init(struct ui_progress *p, u64 total,
const char *title, bool size)
{
p->curr = 0;
p->next = p->step = total / 16 ?: 1;
p->total = total;
p->title = title;
p->size = size;
if (ui_progress__ops->init)
ui_progress__ops->init(p);
}
void ui_progress__finish(void)
{
if (ui_progress__ops->finish)
ui_progress__ops->finish();
}
+34
View File
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0 */
#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;
bool size;
};
void __ui_progress__init(struct ui_progress *p, u64 total,
const char *title, bool size);
#define ui_progress__init(p, total, title) \
__ui_progress__init(p, total, title, false)
#define ui_progress__init_size(p, total, title) \
__ui_progress__init(p, total, title, true)
void ui_progress__update(struct ui_progress *p, u64 adv);
struct ui_progress_ops {
void (*init)(struct ui_progress *p);
void (*update)(struct ui_progress *p);
void (*finish)(void);
};
extern struct ui_progress_ops *ui_progress__ops;
#endif
+141
View File
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0
#include <dlfcn.h>
#include <signal.h>
#include <unistd.h>
#include <subcmd/pager.h>
#include "../util/debug.h"
#include "../util/hist.h"
#include "ui.h"
struct mutex ui__lock;
void *perf_gtk_handle;
int use_browser = -1;
#define PERF_GTK_DSO "libperf-gtk.so"
#ifdef HAVE_GTK2_SUPPORT
static int setup_gtk_browser(void)
{
int (*perf_ui_init)(void);
if (perf_gtk_handle)
return 0;
perf_gtk_handle = dlopen(PERF_GTK_DSO, RTLD_LAZY);
if (perf_gtk_handle == NULL) {
char buf[PATH_MAX];
scnprintf(buf, sizeof(buf), "%s/%s", LIBDIR, PERF_GTK_DSO);
perf_gtk_handle = dlopen(buf, RTLD_LAZY);
}
if (perf_gtk_handle == NULL)
return -1;
perf_ui_init = dlsym(perf_gtk_handle, "perf_gtk__init");
if (perf_ui_init == NULL)
goto out_close;
if (perf_ui_init() == 0)
return 0;
out_close:
dlclose(perf_gtk_handle);
return -1;
}
static void exit_gtk_browser(bool wait_for_ok)
{
void (*perf_ui_exit)(bool);
if (perf_gtk_handle == NULL)
return;
perf_ui_exit = dlsym(perf_gtk_handle, "perf_gtk__exit");
if (perf_ui_exit == NULL)
goto out_close;
perf_ui_exit(wait_for_ok);
out_close:
dlclose(perf_gtk_handle);
perf_gtk_handle = NULL;
}
#else
static inline int setup_gtk_browser(void) { return -1; }
static inline void exit_gtk_browser(bool wait_for_ok __maybe_unused) {}
#endif
int stdio__config_color(const struct option *opt __maybe_unused,
const char *mode, int unset __maybe_unused)
{
perf_use_color_default = perf_config_colorbool("color.ui", mode, -1);
return 0;
}
void setup_browser(bool fallback_to_pager)
{
mutex_init(&ui__lock);
if (use_browser < 2 && (!isatty(1) || dump_trace))
use_browser = 0;
/* 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);
use_browser = 1;
/* fall through */
case 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;
}
mutex_destroy(&ui__lock);
}
void pthread__block_sigwinch(void)
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGWINCH);
pthread_sigmask(SIG_BLOCK, &set, NULL);
}
void pthread__unblock_sigwinch(void)
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGWINCH);
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
}
+943
View File
@@ -0,0 +1,943 @@
// SPDX-License-Identifier: GPL-2.0
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/string.h>
#include "../../util/callchain.h"
#include "../../util/debug.h"
#include "../../util/event.h"
#include "../../util/hist.h"
#include "../../util/map.h"
#include "../../util/maps.h"
#include "../../util/symbol.h"
#include "../../util/sort.h"
#include "../../util/evsel.h"
#include "../../util/srcline.h"
#include "../../util/string2.h"
#include "../../util/thread.h"
#include "../../util/block-info.h"
#include <linux/ctype.h>
#include <linux/zalloc.h>
static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin)
{
int i;
int ret = fprintf(fp, " ");
if (left_margin > USHRT_MAX)
left_margin = USHRT_MAX;
for (i = 0; i < left_margin; i++)
ret += fprintf(fp, " ");
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_node *node,
struct callchain_list *chain,
int depth, int depth_mask, int period,
u64 total_samples, int left_margin)
{
int i;
size_t ret = 0;
char bf[1024], *alloc_str = NULL;
char buf[64];
const char *str;
ret += callchain__fprintf_left_margin(fp, left_margin);
for (i = 0; i < depth; i++) {
if (depth_mask & (1 << i))
ret += fprintf(fp, "|");
else
ret += fprintf(fp, " ");
if (!period && i == depth - 1) {
ret += fprintf(fp, "--");
ret += callchain_node__fprintf_value(node, fp, total_samples);
ret += fprintf(fp, "--");
} else
ret += fprintf(fp, "%s", " ");
}
str = callchain_list__sym_name(chain, bf, sizeof(bf), false);
if (symbol_conf.show_branchflag_count) {
callchain_list_counts__printf_value(chain, NULL,
buf, sizeof(buf));
if (asprintf(&alloc_str, "%s%s", str, buf) < 0)
str = "Not enough memory!";
else
str = alloc_str;
}
fputs(str, fp);
fputc('\n', fp);
free(alloc_str);
return ret;
}
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 = NULL;
struct callchain_list *chain;
int new_depth_mask = depth_mask;
u64 remaining;
size_t ret = 0;
int i;
uint entries_printed = 0;
int cumul_count = 0;
remaining = total_samples;
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;
cumul_count += callchain_cumul_counts(child);
/*
* 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, child, chain, depth,
new_depth_mask, i++,
total_samples,
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) {
struct callchain_node rem_node = {
.hit = remaining,
};
if (!rem_sq_bracket)
return ret;
if (callchain_param.value == CCVAL_COUNT && child && child->parent) {
rem_node.count = child->parent->children_count - cumul_count;
if (rem_node.count <= 0)
return ret;
}
new_depth_mask &= ~(1 << (depth - 1));
ret += ipchain__fprintf_graph(fp, &rem_node, &rem_hits, depth,
new_depth_mask, 0, total_samples,
left_margin);
}
return ret;
}
/*
* If have one single callchain root, don't bother printing
* its percentage (100 % in fractal mode and the same percentage
* than the hist in graph mode). This also avoid one level of column.
*
* However when percent-limit applied, it's possible that single callchain
* node have different (non-100% in fractal mode) percentage.
*/
static bool need_percent_display(struct rb_node *node, u64 parent_samples)
{
struct callchain_node *cnode;
if (rb_next(node))
return true;
cnode = rb_entry(node, struct callchain_node, rb_node);
return callchain_cumul_hits(cnode) != parent_samples;
}
static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root,
u64 total_samples, u64 parent_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;
char bf[1024];
node = rb_first(root);
if (node && !need_percent_display(node, parent_samples)) {
cnode = rb_entry(node, struct callchain_node, rb_node);
list_for_each_entry(chain, &cnode->val, list) {
/*
* 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 && strstarts(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);
ret += fprintf(fp, "%s",
callchain_list__sym_name(chain, bf,
sizeof(bf),
false));
if (symbol_conf.show_branchflag_count)
ret += callchain_list_counts__printf_value(
chain, fp, NULL, 0);
ret += fprintf(fp, "\n");
if (++entries_printed == callchain_param.print_limit)
break;
}
root = &cnode->rb_root;
}
if (callchain_param.mode == CHAIN_GRAPH_REL)
total_samples = parent_samples;
ret += __callchain__fprintf_graph(fp, root, total_samples,
1, 1, left_margin);
if (ret) {
/* do not add a blank line if it printed nothing */
ret += fprintf(fp, "\n");
}
return ret;
}
static size_t __callchain__fprintf_flat(FILE *fp, struct callchain_node *node,
u64 total_samples)
{
struct callchain_list *chain;
size_t ret = 0;
char bf[1024];
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;
ret += fprintf(fp, " %s\n", callchain_list__sym_name(chain,
bf, sizeof(bf), false));
}
return ret;
}
static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *tree,
u64 total_samples)
{
size_t ret = 0;
u32 entries_printed = 0;
struct callchain_node *chain;
struct rb_node *rb_node = rb_first(tree);
while (rb_node) {
chain = rb_entry(rb_node, struct callchain_node, rb_node);
ret += fprintf(fp, " ");
ret += callchain_node__fprintf_value(chain, fp, total_samples);
ret += fprintf(fp, "\n");
ret += __callchain__fprintf_flat(fp, chain, total_samples);
ret += fprintf(fp, "\n");
if (++entries_printed == callchain_param.print_limit)
break;
rb_node = rb_next(rb_node);
}
return ret;
}
static size_t __callchain__fprintf_folded(FILE *fp, struct callchain_node *node)
{
const char *sep = symbol_conf.field_sep ?: ";";
struct callchain_list *chain;
size_t ret = 0;
char bf[1024];
bool first;
if (!node)
return 0;
ret += __callchain__fprintf_folded(fp, node->parent);
first = (ret == 0);
list_for_each_entry(chain, &node->val, list) {
if (chain->ip >= PERF_CONTEXT_MAX)
continue;
ret += fprintf(fp, "%s%s", first ? "" : sep,
callchain_list__sym_name(chain,
bf, sizeof(bf), false));
first = false;
}
return ret;
}
static size_t callchain__fprintf_folded(FILE *fp, struct rb_root *tree,
u64 total_samples)
{
size_t ret = 0;
u32 entries_printed = 0;
struct callchain_node *chain;
struct rb_node *rb_node = rb_first(tree);
while (rb_node) {
chain = rb_entry(rb_node, struct callchain_node, rb_node);
ret += callchain_node__fprintf_value(chain, fp, total_samples);
ret += fprintf(fp, " ");
ret += __callchain__fprintf_folded(fp, chain);
ret += fprintf(fp, "\n");
if (++entries_printed == callchain_param.print_limit)
break;
rb_node = rb_next(rb_node);
}
return ret;
}
static size_t hist_entry_callchain__fprintf(struct hist_entry *he,
u64 total_samples, int left_margin,
FILE *fp)
{
u64 parent_samples = he->stat.period;
if (symbol_conf.cumulate_callchain)
parent_samples = he->stat_acc->period;
switch (callchain_param.mode) {
case CHAIN_GRAPH_REL:
return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples,
parent_samples, left_margin);
break;
case CHAIN_GRAPH_ABS:
return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples,
parent_samples, left_margin);
break;
case CHAIN_FLAT:
return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples);
break;
case CHAIN_FOLDED:
return callchain__fprintf_folded(fp, &he->sorted_chain, total_samples);
break;
case CHAIN_NONE:
break;
default:
pr_err("Bad callchain mode\n");
}
return 0;
}
int __hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp,
struct perf_hpp_list *hpp_list)
{
const char *sep = symbol_conf.field_sep;
struct perf_hpp_fmt *fmt;
char *start = hpp->buf;
int ret;
bool first = true;
if (symbol_conf.exclude_other && !he->parent)
return 0;
perf_hpp_list__for_each_format(hpp_list, fmt) {
if (perf_hpp__should_skip(fmt, he->hists))
continue;
/*
* If there's no field_sep, we still need
* to display initial ' '.
*/
if (!sep || !first) {
ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " ");
advance_hpp(hpp, ret);
} else
first = false;
if (perf_hpp__use_color() && fmt->color)
ret = fmt->color(fmt, hpp, he);
else
ret = fmt->entry(fmt, hpp, he);
ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret);
advance_hpp(hpp, ret);
}
return hpp->buf - start;
}
static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp)
{
return __hist_entry__snprintf(he, hpp, he->hists->hpp_list);
}
static int hist_entry__hierarchy_fprintf(struct hist_entry *he,
struct perf_hpp *hpp,
struct hists *hists,
FILE *fp)
{
const char *sep = symbol_conf.field_sep;
struct perf_hpp_fmt *fmt;
struct perf_hpp_list_node *fmt_node;
char *buf = hpp->buf;
size_t size = hpp->size;
int ret, printed = 0;
bool first = true;
if (symbol_conf.exclude_other && !he->parent)
return 0;
ret = scnprintf(hpp->buf, hpp->size, "%*s", he->depth * HIERARCHY_INDENT, "");
advance_hpp(hpp, ret);
/* the first hpp_list_node is for overhead columns */
fmt_node = list_first_entry(&hists->hpp_formats,
struct perf_hpp_list_node, list);
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
/*
* If there's no field_sep, we still need
* to display initial ' '.
*/
if (!sep || !first) {
ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " ");
advance_hpp(hpp, ret);
} else
first = false;
if (perf_hpp__use_color() && fmt->color)
ret = fmt->color(fmt, hpp, he);
else
ret = fmt->entry(fmt, hpp, he);
ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret);
advance_hpp(hpp, ret);
}
if (!sep)
ret = scnprintf(hpp->buf, hpp->size, "%*s",
(hists->nr_hpp_node - 2) * HIERARCHY_INDENT, "");
advance_hpp(hpp, ret);
printed += fprintf(fp, "%s", buf);
perf_hpp_list__for_each_format(he->hpp_list, fmt) {
hpp->buf = buf;
hpp->size = size;
/*
* No need to call hist_entry__snprintf_alignment() since this
* fmt is always the last column in the hierarchy mode.
*/
if (perf_hpp__use_color() && fmt->color)
fmt->color(fmt, hpp, he);
else
fmt->entry(fmt, hpp, he);
/*
* dynamic entries are right-aligned but we want left-aligned
* in the hierarchy mode
*/
printed += fprintf(fp, "%s%s", sep ?: " ", skip_spaces(buf));
}
printed += putc('\n', fp);
if (he->leaf && hist_entry__has_callchains(he) && symbol_conf.use_callchain) {
u64 total = hists__total_period(hists);
printed += hist_entry_callchain__fprintf(he, total, 0, fp);
goto out;
}
out:
return printed;
}
static int hist_entry__block_fprintf(struct hist_entry *he,
char *bf, size_t size,
FILE *fp)
{
struct block_hist *bh = container_of(he, struct block_hist, he);
int ret = 0;
for (unsigned int i = 0; i < bh->block_hists.nr_entries; i++) {
struct perf_hpp hpp = {
.buf = bf,
.size = size,
.skip = false,
};
bh->block_idx = i;
hist_entry__snprintf(he, &hpp);
if (!hpp.skip)
ret += fprintf(fp, "%s\n", bf);
}
return ret;
}
static int hist_entry__individual_block_fprintf(struct hist_entry *he,
char *bf, size_t size,
FILE *fp)
{
int ret = 0;
struct perf_hpp hpp = {
.buf = bf,
.size = size,
.skip = false,
};
hist_entry__snprintf(he, &hpp);
if (!hpp.skip)
ret += fprintf(fp, "%s\n", bf);
return ret;
}
static int hist_entry__fprintf(struct hist_entry *he, size_t size,
char *bf, size_t bfsz, FILE *fp,
bool ignore_callchains)
{
int ret;
int callchain_ret = 0;
struct perf_hpp hpp = {
.buf = bf,
.size = size,
};
struct hists *hists = he->hists;
u64 total_period = hists->stats.total_period;
if (size == 0 || size > bfsz)
size = hpp.size = bfsz;
if (symbol_conf.report_hierarchy)
return hist_entry__hierarchy_fprintf(he, &hpp, hists, fp);
if (symbol_conf.report_block)
return hist_entry__block_fprintf(he, bf, size, fp);
if (symbol_conf.report_individual_block)
return hist_entry__individual_block_fprintf(he, bf, size, fp);
hist_entry__snprintf(he, &hpp);
ret = fprintf(fp, "%s\n", bf);
if (hist_entry__has_callchains(he) && !ignore_callchains)
callchain_ret = hist_entry_callchain__fprintf(he, total_period,
0, fp);
ret += callchain_ret;
return ret;
}
static int print_hierarchy_indent(const char *sep, int indent,
const char *line, FILE *fp)
{
int width;
if (sep != NULL || indent < 2)
return 0;
width = (indent - 2) * HIERARCHY_INDENT;
return fprintf(fp, "%-*.*s", width, width, line);
}
static int hists__fprintf_hierarchy_headers(struct hists *hists,
struct perf_hpp *hpp, FILE *fp)
{
bool first_node, first_col;
int indent;
int depth;
unsigned width = 0;
unsigned header_width = 0;
struct perf_hpp_fmt *fmt;
struct perf_hpp_list_node *fmt_node;
struct perf_hpp_list *hpp_list = hists->hpp_list;
const char *sep = symbol_conf.field_sep;
indent = hists->nr_hpp_node;
/* the first hpp_list_node is for overhead columns */
fmt_node = list_first_entry(&hists->hpp_formats,
struct perf_hpp_list_node, list);
for (int line = 0; line < hpp_list->nr_header_lines; line++) {
/* first # is displayed one level up */
if (line)
fprintf(fp, "# ");
/* preserve max indent depth for column headers */
print_hierarchy_indent(sep, indent, " ", fp);
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
fmt->header(fmt, hpp, hists, line, NULL);
fprintf(fp, "%s%s", hpp->buf, sep ?: " ");
}
if (line < hpp_list->nr_header_lines - 1)
goto next_line;
/* combine sort headers with ' / ' */
first_node = true;
list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
if (!first_node)
header_width += fprintf(fp, " / ");
first_node = false;
first_col = true;
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
if (perf_hpp__should_skip(fmt, hists))
continue;
if (!first_col)
header_width += fprintf(fp, "+");
first_col = false;
fmt->header(fmt, hpp, hists, line, NULL);
header_width += fprintf(fp, "%s", strim(hpp->buf));
}
}
next_line:
fprintf(fp, "\n");
}
fprintf(fp, "# ");
/* preserve max indent depth for initial dots */
print_hierarchy_indent(sep, indent, dots, fp);
/* the first hpp_list_node is for overhead columns */
fmt_node = list_first_entry(&hists->hpp_formats,
struct perf_hpp_list_node, list);
first_col = true;
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
if (!first_col)
fprintf(fp, "%s", sep ?: "..");
first_col = false;
width = fmt->width(fmt, hpp, hists);
fprintf(fp, "%.*s", width, dots);
}
depth = 0;
list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
first_col = true;
width = depth * HIERARCHY_INDENT;
perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
if (perf_hpp__should_skip(fmt, hists))
continue;
if (!first_col)
width++; /* for '+' sign between column header */
first_col = false;
width += fmt->width(fmt, hpp, hists);
}
if (width > header_width)
header_width = width;
depth++;
}
fprintf(fp, "%s%-.*s", sep ?: " ", header_width, dots);
fprintf(fp, "\n#\n");
return 2;
}
static void fprintf_line(struct hists *hists, struct perf_hpp *hpp,
int line, FILE *fp)
{
struct perf_hpp_fmt *fmt;
const char *sep = symbol_conf.field_sep;
bool first = true;
int span = 0;
hists__for_each_format(hists, fmt) {
if (perf_hpp__should_skip(fmt, hists))
continue;
if (!first && !span)
fprintf(fp, "%s", sep ?: " ");
else
first = false;
fmt->header(fmt, hpp, hists, line, &span);
if (!span)
fprintf(fp, "%s", hpp->buf);
}
}
static int
hists__fprintf_standard_headers(struct hists *hists,
struct perf_hpp *hpp,
FILE *fp)
{
struct perf_hpp_list *hpp_list = hists->hpp_list;
struct perf_hpp_fmt *fmt;
unsigned int width;
const char *sep = symbol_conf.field_sep;
bool first = true;
int line;
for (line = 0; line < hpp_list->nr_header_lines; line++) {
/* first # is displayed one level up */
if (line)
fprintf(fp, "# ");
fprintf_line(hists, hpp, line, fp);
fprintf(fp, "\n");
}
if (sep)
return hpp_list->nr_header_lines;
first = true;
fprintf(fp, "# ");
hists__for_each_format(hists, fmt) {
unsigned int i;
if (perf_hpp__should_skip(fmt, hists))
continue;
if (!first)
fprintf(fp, "%s", sep ?: " ");
else
first = false;
width = fmt->width(fmt, hpp, hists);
for (i = 0; i < width; i++)
fprintf(fp, ".");
}
fprintf(fp, "\n");
fprintf(fp, "#\n");
return hpp_list->nr_header_lines + 2;
}
int hists__fprintf_headers(struct hists *hists, FILE *fp)
{
char bf[1024];
struct perf_hpp dummy_hpp = {
.buf = bf,
.size = sizeof(bf),
};
fprintf(fp, "# ");
if (symbol_conf.report_hierarchy)
return hists__fprintf_hierarchy_headers(hists, &dummy_hpp, fp);
else
return hists__fprintf_standard_headers(hists, &dummy_hpp, fp);
}
size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
int max_cols, float min_pcnt, FILE *fp,
bool ignore_callchains)
{
struct rb_node *nd;
size_t ret = 0;
const char *sep = symbol_conf.field_sep;
int nr_rows = 0;
size_t linesz;
char *line = NULL;
unsigned indent;
init_rem_hits();
hists__reset_column_width(hists);
if (symbol_conf.col_width_list_str)
perf_hpp__set_user_width(symbol_conf.col_width_list_str);
if (show_header)
nr_rows += hists__fprintf_headers(hists, fp);
if (max_rows && nr_rows >= max_rows)
goto out;
linesz = hists__sort_list_width(hists) + 3 + 1;
linesz += perf_hpp__color_overhead();
line = malloc(linesz);
if (line == NULL) {
ret = -1;
goto out;
}
indent = hists__overhead_width(hists) + 4;
for (nd = rb_first_cached(&hists->entries); nd;
nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD)) {
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
float percent;
if (h->filtered)
continue;
if (symbol_conf.report_individual_block)
percent = block_info__total_cycles_percent(h);
else
percent = hist_entry__get_percent_limit(h);
if (percent < min_pcnt)
continue;
ret += hist_entry__fprintf(h, max_cols, line, linesz, fp, ignore_callchains);
if (max_rows && ++nr_rows >= max_rows)
break;
/*
* If all children are filtered out or percent-limited,
* display "no entry >= x.xx%" message.
*/
if (!h->leaf && !hist_entry__has_hierarchy_children(h, min_pcnt)) {
int depth = hists->nr_hpp_node + h->depth + 1;
print_hierarchy_indent(sep, depth, " ", fp);
fprintf(fp, "%*sno entry >= %.2f%%\n", indent, "", min_pcnt);
if (max_rows && ++nr_rows >= max_rows)
break;
}
if (h->ms.map == NULL && verbose > 1) {
maps__fprintf(thread__maps(h->thread), 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;
u32 total = stats->nr_events[0];
for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) {
const char *name;
name = perf_event__name(i);
if (!strcmp(name, "UNKNOWN"))
continue;
if (symbol_conf.skip_empty && !stats->nr_events[i])
continue;
if (i && total) {
ret += fprintf(fp, "%20s events: %10d (%4.1f%%)\n",
name, stats->nr_events[i],
100.0 * stats->nr_events[i] / total);
} else {
ret += fprintf(fp, "%20s events: %10d\n",
name, stats->nr_events[i]);
}
}
return ret;
}
+4
View File
@@ -0,0 +1,4 @@
perf-ui-y += setup.o
perf-ui-y += util.o
perf-ui-y += helpline.o
perf-ui-y += progress.o
+62
View File
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include "../helpline.h"
#include "../ui.h"
#include "../libslang.h"
char ui_helpline__last_msg[1024];
bool tui_helpline__set;
static void tui_helpline__pop(void)
{
}
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(msg, SLtt_Screen_Cols);
SLsmg_refresh();
strlcpy(ui_helpline__current, msg, sz);
}
static int tui_helpline__show(const char *format, va_list ap)
{
int ret;
static int backlog;
mutex_lock(&ui__lock);
ret = vscnprintf(ui_helpline__last_msg + backlog,
sizeof(ui_helpline__last_msg) - backlog, format, ap);
backlog += ret;
tui_helpline__set = true;
if (ui_helpline__last_msg[backlog - 1] == '\n') {
ui_helpline__puts(ui_helpline__last_msg);
SLsmg_refresh();
backlog = 0;
}
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(" ");
}
+87
View File
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include "../progress.h"
#include "../libslang.h"
#include "../ui.h"
#include "tui.h"
#include "units.h"
#include "../browser.h"
static void __tui_progress__init(struct ui_progress *p)
{
p->next = p->step = p->total / (SLtt_Screen_Cols - 2) ?: 1;
}
static int get_title(struct ui_progress *p, char *buf, size_t size)
{
char buf_cur[20];
char buf_tot[20];
int ret;
ret = unit_number__scnprintf(buf_cur, sizeof(buf_cur), p->curr);
ret += unit_number__scnprintf(buf_tot, sizeof(buf_tot), p->total);
return ret + scnprintf(buf, size, "%s [%s/%s]",
p->title, buf_cur, buf_tot);
}
static void tui_progress__update(struct ui_progress *p)
{
char buf[100], *title = (char *) p->title;
int bar, y;
/*
* FIXME: We should have a per UI backend way of showing progress,
* stdio will just show a percentage as NN%, etc.
*/
if (use_browser <= 0)
return;
if (p->total == 0)
return;
if (p->size) {
get_title(p, buf, sizeof(buf));
title = buf;
}
ui__refresh_dimensions(false);
mutex_lock(&ui__lock);
y = SLtt_Screen_Rows / 2 - 2;
SLsmg_set_color(0);
SLsmg_draw_box(y, 0, 3, SLtt_Screen_Cols);
SLsmg_gotorc(y++, 1);
SLsmg_write_string(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();
mutex_unlock(&ui__lock);
}
static void tui_progress__finish(void)
{
int y;
if (use_browser <= 0)
return;
ui__refresh_dimensions(false);
mutex_lock(&ui__lock);
y = SLtt_Screen_Rows / 2 - 2;
SLsmg_set_color(0);
SLsmg_fill_region(y, 0, 3, SLtt_Screen_Cols, ' ');
SLsmg_refresh();
mutex_unlock(&ui__lock);
}
static struct ui_progress_ops tui_progress__ops = {
.init = __tui_progress__init,
.update = tui_progress__update,
.finish = tui_progress__finish,
};
void tui_progress__init(void)
{
ui_progress__ops = &tui_progress__ops;
}
+201
View File
@@ -0,0 +1,201 @@
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <linux/kernel.h>
#ifdef HAVE_BACKTRACE_SUPPORT
#include <execinfo.h>
#endif
#include "../../util/color.h"
#include "../../util/debug.h"
#include "../browser.h"
#include "../helpline.h"
#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 bool tui_helpline__set;
extern void hist_browser__init_hpp(void);
void ui__refresh_dimensions(bool force)
{
if (force || ui__need_resize) {
ui__need_resize = 0;
mutex_lock(&ui__lock);
SLtt_get_screen_size();
SLsmg_reinit_smg();
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();
}
#ifdef HAVE_BACKTRACE_SUPPORT
static void ui__signal_backtrace(int sig)
{
void *stackdump[32];
size_t size;
ui__exit(false);
psignal(sig, "perf");
printf("-------- backtrace --------\n");
size = backtrace(stackdump, ARRAY_SIZE(stackdump));
__dump_stack(stdout, stackdump, size);
exit(0);
}
#else
# define ui__signal_backtrace ui__signal
#endif
static void ui__signal(int sig)
{
ui__exit(false);
psignal(sig, "perf");
exit(0);
}
static void ui__sigcont(int sig)
{
static struct termios tty;
if (sig == SIGTSTP) {
while (tcgetattr(SLang_TT_Read_FD, &tty) == -1 && errno == EINTR)
;
while (write(SLang_TT_Read_FD, PERF_COLOR_RESET, sizeof(PERF_COLOR_RESET) - 1) == -1 && errno == EINTR)
;
raise(SIGSTOP);
} else {
while (tcsetattr(SLang_TT_Read_FD, TCSADRAIN, &tty) == -1 && errno == EINTR)
;
raise(SIGWINCH);
}
}
int ui__init(void)
{
int err;
SLutf8_enable(-1);
SLtt_get_terminfo();
SLtt_get_screen_size();
err = SLsmg_init_smg();
if (err < 0)
goto out;
err = SLang_init_tty(-1, 0, 0);
if (err < 0)
goto out;
SLtty_set_suspend_state(true);
err = SLkp_init();
if (err < 0) {
pr_err("TUI initialization failed.\n");
goto out;
}
SLkp_define_keysym("^(kB)", SL_KEY_UNTAB);
signal(SIGSEGV, ui__signal_backtrace);
signal(SIGFPE, ui__signal_backtrace);
signal(SIGINT, ui__signal);
signal(SIGQUIT, ui__signal);
signal(SIGTERM, ui__signal);
signal(SIGTSTP, ui__sigcont);
signal(SIGCONT, ui__sigcont);
perf_error__register(&perf_tui_eops);
ui_helpline__init();
ui_browser__init();
tui_progress__init();
hist_browser__init_hpp();
out:
return err;
}
void ui__exit(bool wait_for_ok)
{
if (wait_for_ok && tui_helpline__set)
ui__question_window("Fatal Error",
ui_helpline__last_msg,
"Press any key...", 0);
SLtt_set_cursor_visibility(1);
if (mutex_trylock(&ui__lock)) {
SLsmg_refresh();
SLsmg_reset_smg();
mutex_unlock(&ui__lock);
}
SLang_reset_tty();
perf_error__unregister(&perf_tui_eops);
}
+7
View File
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_TUI_H_
#define _PERF_TUI_H_ 1
void tui_progress__init(void);
#endif /* _PERF_TUI_H_ */
+274
View File
@@ -0,0 +1,274 @@
// SPDX-License-Identifier: GPL-2.0
#include <signal.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ttydefaults.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);
ui_browser__write_nstring(browser, *arg, browser->width);
}
static int popup_menu__run(struct ui_browser *menu, int *keyp)
{
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:
if (keyp) {
*keyp = key;
key = menu->nr_entries;
break;
}
continue;
}
break;
}
ui_browser__hide(menu);
return key;
}
int ui__popup_menu(int argc, char * const argv[], int *keyp)
{
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, keyp);
}
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;
}
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(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(" ", max_len);
}
SLsmg_draw_box(y++, x + 1, 3, max_len - 2);
SLsmg_gotorc(y + 3, x);
SLsmg_write_nstring(exit_msg, max_len);
SLsmg_refresh();
mutex_unlock(&ui__lock);
x += 2;
len = 0;
key = ui__getch(delay_secs);
while (key != K_TIMER && key != K_ENTER && key != K_ESC) {
mutex_lock(&ui__lock);
if (key == K_BKSPC) {
if (len == 0) {
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();
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;
}
void __ui__info_window(const char *title, const char *text, const char *exit_msg)
{
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;
}
max_len += 2;
nr_lines += 2;
if (exit_msg)
nr_lines += 2;
y = SLtt_Screen_Rows / 2 - nr_lines / 2,
x = SLtt_Screen_Cols / 2 - max_len / 2;
SLsmg_set_color(0);
SLsmg_draw_box(y, x++, nr_lines, max_len);
if (title) {
SLsmg_gotorc(y, x + 1);
SLsmg_write_string(title);
}
SLsmg_gotorc(++y, x);
if (exit_msg)
nr_lines -= 2;
max_len -= 2;
SLsmg_write_wrapped_string((unsigned char *)text, y, x,
nr_lines, max_len, 1);
if (exit_msg) {
SLsmg_gotorc(y + nr_lines - 2, x);
SLsmg_write_nstring(" ", max_len);
SLsmg_gotorc(y + nr_lines - 1, x);
SLsmg_write_nstring(exit_msg, max_len);
}
}
void ui__info_window(const char *title, const char *text)
{
mutex_lock(&ui__lock);
__ui__info_window(title, text, NULL);
SLsmg_refresh();
mutex_unlock(&ui__lock);
}
int ui__question_window(const char *title, const char *text,
const char *exit_msg, int delay_secs)
{
mutex_lock(&ui__lock);
__ui__info_window(title, text, exit_msg);
SLsmg_refresh();
mutex_unlock(&ui__lock);
return ui__getch(delay_secs);
}
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,
};
+37
View File
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_H_
#define _PERF_UI_H_ 1
#include "../util/mutex.h"
#include <stdbool.h>
#include <linux/compiler.h>
extern struct mutex ui__lock;
extern void *perf_gtk_handle;
extern int use_browser;
void setup_browser(bool fallback_to_pager);
void exit_browser(bool wait_for_ok);
#ifdef 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);
struct option;
int stdio__config_color(const struct option *opt, const char *mode, int unset);
void pthread__block_sigwinch(void);
void pthread__unblock_sigwinch(void);
#endif /* _PERF_UI_H_ */
+90
View File
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0
#include "util.h"
#include "../util/debug.h"
#include <stdio.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)
{
if (quiet)
return 0;
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;
if (quiet)
return 0;
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;
}
+24
View File
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PERF_UI_UTIL_H_
#define _PERF_UI_UTIL_H_ 1
#include <stdarg.h>
int ui__getch(int delay_secs);
int ui__popup_menu(int argc, char * const argv[], int *keyp);
int ui__help_window(const char *text);
int ui__dialog_yesno(const char *msg);
void __ui__info_window(const char *title, const char *text, const char *exit_msg);
void ui__info_window(const char *title, const char *text);
int ui__question_window(const char *title, const char *text,
const char *exit_msg, int delay_secs);
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_ */