2010-03-12 07:12:44 +08:00
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
|
|
#undef _GNU_SOURCE
|
2010-08-11 01:54:09 +08:00
|
|
|
#include "ui/libslang.h"
|
2010-07-30 21:06:06 +08:00
|
|
|
#include <signal.h>
|
2010-03-12 07:12:44 +08:00
|
|
|
#include <stdlib.h>
|
2010-08-06 04:00:42 +08:00
|
|
|
#include <elf.h>
|
2010-03-12 07:12:44 +08:00
|
|
|
#include <newt.h>
|
2010-03-12 21:48:12 +08:00
|
|
|
#include <sys/ttydefaults.h>
|
2010-03-12 07:12:44 +08:00
|
|
|
|
|
|
|
#include "cache.h"
|
|
|
|
#include "hist.h"
|
2010-05-15 07:05:21 +08:00
|
|
|
#include "pstack.h"
|
2010-03-12 07:12:44 +08:00
|
|
|
#include "session.h"
|
|
|
|
#include "sort.h"
|
|
|
|
#include "symbol.h"
|
2010-08-07 04:35:02 +08:00
|
|
|
#include "ui/browser.h"
|
2010-08-09 06:48:31 +08:00
|
|
|
#include "ui/helpline.h"
|
2010-08-11 02:37:34 +08:00
|
|
|
#include "ui/browsers/map.h"
|
2010-03-12 07:12:44 +08:00
|
|
|
|
2010-08-07 04:35:02 +08:00
|
|
|
newtComponent newt_form__new(void);
|
|
|
|
|
2010-08-11 01:54:09 +08:00
|
|
|
char browser__last_msg[1024];
|
2010-03-27 08:16:22 +08:00
|
|
|
|
|
|
|
int browser__show_help(const char *format, va_list ap)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
static int backlog;
|
|
|
|
|
|
|
|
ret = vsnprintf(browser__last_msg + backlog,
|
|
|
|
sizeof(browser__last_msg) - backlog, format, ap);
|
|
|
|
backlog += ret;
|
|
|
|
|
|
|
|
if (browser__last_msg[backlog - 1] == '\n') {
|
2010-05-12 05:01:23 +08:00
|
|
|
ui_helpline__puts(browser__last_msg);
|
2010-03-27 08:16:22 +08:00
|
|
|
newtRefresh();
|
|
|
|
backlog = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-03-12 21:48:12 +08:00
|
|
|
static void newt_form__set_exit_keys(newtComponent self)
|
|
|
|
{
|
2010-05-17 07:29:38 +08:00
|
|
|
newtFormAddHotKey(self, NEWT_KEY_LEFT);
|
2010-03-12 21:48:12 +08:00
|
|
|
newtFormAddHotKey(self, NEWT_KEY_ESCAPE);
|
|
|
|
newtFormAddHotKey(self, 'Q');
|
|
|
|
newtFormAddHotKey(self, 'q');
|
|
|
|
newtFormAddHotKey(self, CTRL('c'));
|
|
|
|
}
|
|
|
|
|
2010-08-07 04:35:02 +08:00
|
|
|
newtComponent newt_form__new(void)
|
2010-03-12 21:48:12 +08:00
|
|
|
{
|
|
|
|
newtComponent self = newtForm(NULL, NULL, 0);
|
|
|
|
if (self)
|
|
|
|
newt_form__set_exit_keys(self);
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2010-04-04 03:30:44 +08:00
|
|
|
static int popup_menu(int argc, char * const argv[])
|
2010-03-25 03:40:14 +08:00
|
|
|
{
|
|
|
|
struct newtExitStruct es;
|
|
|
|
int i, rc = -1, max_len = 5;
|
|
|
|
newtComponent listbox, form = newt_form__new();
|
|
|
|
|
|
|
|
if (form == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT);
|
|
|
|
if (listbox == NULL)
|
|
|
|
goto out_destroy_form;
|
|
|
|
|
2010-05-10 21:51:25 +08:00
|
|
|
newtFormAddComponent(form, listbox);
|
2010-03-25 03:40:14 +08:00
|
|
|
|
|
|
|
for (i = 0; i < argc; ++i) {
|
|
|
|
int len = strlen(argv[i]);
|
|
|
|
if (len > max_len)
|
|
|
|
max_len = len;
|
|
|
|
if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i))
|
|
|
|
goto out_destroy_form;
|
|
|
|
}
|
|
|
|
|
|
|
|
newtCenteredWindow(max_len, argc, NULL);
|
|
|
|
newtFormRun(form, &es);
|
|
|
|
rc = newtListboxGetCurrent(listbox) - NULL;
|
|
|
|
if (es.reason == NEWT_EXIT_HOTKEY)
|
|
|
|
rc = -1;
|
|
|
|
newtPopWindow();
|
|
|
|
out_destroy_form:
|
|
|
|
newtFormDestroy(form);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2010-05-17 08:04:27 +08:00
|
|
|
static int ui__help_window(const char *text)
|
|
|
|
{
|
|
|
|
struct newtExitStruct es;
|
|
|
|
newtComponent tb, form = newt_form__new();
|
|
|
|
int rc = -1;
|
|
|
|
int max_len = 0, nr_lines = 0;
|
|
|
|
const char *t;
|
|
|
|
|
|
|
|
if (form == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
tb = newtTextbox(0, 0, max_len, nr_lines, 0);
|
|
|
|
if (tb == NULL)
|
|
|
|
goto out_destroy_form;
|
|
|
|
|
|
|
|
newtTextboxSetText(tb, text);
|
|
|
|
newtFormAddComponent(form, tb);
|
|
|
|
newtCenteredWindow(max_len, nr_lines, NULL);
|
|
|
|
newtFormRun(form, &es);
|
|
|
|
newtPopWindow();
|
|
|
|
rc = 0;
|
|
|
|
out_destroy_form:
|
|
|
|
newtFormDestroy(form);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2010-03-25 03:40:14 +08:00
|
|
|
static bool dialog_yesno(const char *msg)
|
|
|
|
{
|
|
|
|
/* newtWinChoice should really be accepting const char pointers... */
|
|
|
|
char yes[] = "Yes", no[] = "No";
|
2010-04-05 23:04:23 +08:00
|
|
|
return newtWinChoice(NULL, yes, no, (char *)msg) == 1;
|
2010-03-25 03:40:14 +08:00
|
|
|
}
|
|
|
|
|
2010-03-23 04:52:49 +08:00
|
|
|
static char *callchain_list__sym_name(struct callchain_list *self,
|
|
|
|
char *bf, size_t bfsize)
|
|
|
|
{
|
2010-03-25 03:40:18 +08:00
|
|
|
if (self->ms.sym)
|
|
|
|
return self->ms.sym->name;
|
2010-03-23 04:52:49 +08:00
|
|
|
|
|
|
|
snprintf(bf, bfsize, "%#Lx", self->ip);
|
|
|
|
return bf;
|
|
|
|
}
|
|
|
|
|
2010-04-03 22:25:56 +08:00
|
|
|
struct hist_browser {
|
2010-07-27 04:13:40 +08:00
|
|
|
struct ui_browser b;
|
|
|
|
struct hists *hists;
|
|
|
|
struct hist_entry *he_selection;
|
|
|
|
struct map_symbol *selection;
|
2010-04-03 22:25:56 +08:00
|
|
|
};
|
|
|
|
|
2010-07-27 04:13:40 +08:00
|
|
|
static void hist_browser__reset(struct hist_browser *self);
|
|
|
|
static int hist_browser__run(struct hist_browser *self, const char *title,
|
|
|
|
struct newtExitStruct *es);
|
2010-08-06 04:02:54 +08:00
|
|
|
static unsigned int hist_browser__refresh(struct ui_browser *self);
|
2010-07-27 04:13:40 +08:00
|
|
|
static void ui_browser__hists_seek(struct ui_browser *self,
|
|
|
|
off_t offset, int whence);
|
|
|
|
|
|
|
|
static struct hist_browser *hist_browser__new(struct hists *hists)
|
2010-04-03 22:25:56 +08:00
|
|
|
{
|
2010-07-27 04:13:40 +08:00
|
|
|
struct hist_browser *self = zalloc(sizeof(*self));
|
2010-04-03 22:25:56 +08:00
|
|
|
|
2010-07-27 04:13:40 +08:00
|
|
|
if (self) {
|
|
|
|
self->hists = hists;
|
2010-08-06 04:02:54 +08:00
|
|
|
self->b.refresh = hist_browser__refresh;
|
2010-07-27 04:13:40 +08:00
|
|
|
self->b.seek = ui_browser__hists_seek;
|
|
|
|
}
|
2010-04-03 22:25:56 +08:00
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hist_browser__delete(struct hist_browser *self)
|
|
|
|
{
|
2010-07-27 04:13:40 +08:00
|
|
|
newtFormDestroy(self->b.form);
|
2010-04-03 22:25:56 +08:00
|
|
|
newtPopWindow();
|
|
|
|
free(self);
|
|
|
|
}
|
|
|
|
|
2010-05-12 10:18:06 +08:00
|
|
|
static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
|
2010-04-04 09:44:37 +08:00
|
|
|
{
|
2010-07-27 04:13:40 +08:00
|
|
|
return self->he_selection;
|
2010-05-12 10:18:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct thread *hist_browser__selected_thread(struct hist_browser *self)
|
|
|
|
{
|
2010-07-27 04:13:40 +08:00
|
|
|
return self->he_selection->thread;
|
2010-04-04 09:44:37 +08:00
|
|
|
}
|
|
|
|
|
2010-05-24 09:36:51 +08:00
|
|
|
static int hist_browser__title(char *bf, size_t size, const char *ev_name,
|
2010-04-05 23:02:18 +08:00
|
|
|
const struct dso *dso, const struct thread *thread)
|
|
|
|
{
|
|
|
|
int printed = 0;
|
|
|
|
|
|
|
|
if (thread)
|
|
|
|
printed += snprintf(bf + printed, size - printed,
|
|
|
|
"Thread: %s(%d)",
|
|
|
|
(thread->comm_set ? thread->comm : ""),
|
|
|
|
thread->pid);
|
|
|
|
if (dso)
|
|
|
|
printed += snprintf(bf + printed, size - printed,
|
|
|
|
"%sDSO: %s", thread ? " " : "",
|
|
|
|
dso->short_name);
|
2010-05-24 09:36:51 +08:00
|
|
|
return printed ?: snprintf(bf, size, "Event: %s", ev_name);
|
2010-04-05 23:02:18 +08:00
|
|
|
}
|
|
|
|
|
2010-05-24 09:36:51 +08:00
|
|
|
int hists__browse(struct hists *self, const char *helpline, const char *ev_name)
|
2010-04-03 22:25:56 +08:00
|
|
|
{
|
2010-07-27 04:13:40 +08:00
|
|
|
struct hist_browser *browser = hist_browser__new(self);
|
2010-05-24 09:36:51 +08:00
|
|
|
struct pstack *fstack;
|
2010-04-05 23:02:18 +08:00
|
|
|
const struct thread *thread_filter = NULL;
|
|
|
|
const struct dso *dso_filter = NULL;
|
2010-04-03 22:25:56 +08:00
|
|
|
struct newtExitStruct es;
|
2010-04-05 23:02:18 +08:00
|
|
|
char msg[160];
|
2010-05-24 09:36:51 +08:00
|
|
|
int key = -1;
|
2010-04-03 22:25:56 +08:00
|
|
|
|
|
|
|
if (browser == NULL)
|
|
|
|
return -1;
|
|
|
|
|
2010-05-15 07:05:21 +08:00
|
|
|
fstack = pstack__new(2);
|
|
|
|
if (fstack == NULL)
|
|
|
|
goto out;
|
|
|
|
|
2010-05-12 05:01:23 +08:00
|
|
|
ui_helpline__push(helpline);
|
2010-04-03 22:25:56 +08:00
|
|
|
|
2010-05-24 09:36:51 +08:00
|
|
|
hist_browser__title(msg, sizeof(msg), ev_name,
|
2010-04-05 23:02:18 +08:00
|
|
|
dso_filter, thread_filter);
|
2010-03-12 07:12:44 +08:00
|
|
|
|
|
|
|
while (1) {
|
2010-04-04 09:44:37 +08:00
|
|
|
const struct thread *thread;
|
2010-04-05 23:02:18 +08:00
|
|
|
const struct dso *dso;
|
2010-04-04 03:30:44 +08:00
|
|
|
char *options[16];
|
|
|
|
int nr_options = 0, choice = 0, i,
|
2010-08-06 04:00:42 +08:00
|
|
|
annotate = -2, zoom_dso = -2, zoom_thread = -2,
|
|
|
|
browse_map = -2;
|
2010-03-12 07:12:44 +08:00
|
|
|
|
2010-07-27 04:13:40 +08:00
|
|
|
if (hist_browser__run(browser, msg, &es))
|
|
|
|
break;
|
2010-05-16 08:15:01 +08:00
|
|
|
|
|
|
|
thread = hist_browser__selected_thread(browser);
|
|
|
|
dso = browser->selection->map ? browser->selection->map->dso : NULL;
|
|
|
|
|
2010-03-25 03:40:14 +08:00
|
|
|
if (es.reason == NEWT_EXIT_HOTKEY) {
|
2010-05-24 09:36:51 +08:00
|
|
|
key = es.u.key;
|
|
|
|
|
|
|
|
switch (key) {
|
|
|
|
case NEWT_KEY_F1:
|
2010-05-17 08:04:27 +08:00
|
|
|
goto do_help;
|
2010-05-24 09:36:51 +08:00
|
|
|
case NEWT_KEY_TAB:
|
|
|
|
case NEWT_KEY_UNTAB:
|
|
|
|
/*
|
|
|
|
* Exit the browser, let hists__browser_tree
|
|
|
|
* go to the next or previous
|
|
|
|
*/
|
|
|
|
goto out_free_stack;
|
|
|
|
default:;
|
|
|
|
}
|
2010-05-17 08:04:27 +08:00
|
|
|
|
2010-05-24 09:36:51 +08:00
|
|
|
key = toupper(key);
|
|
|
|
switch (key) {
|
2010-05-16 08:15:01 +08:00
|
|
|
case 'A':
|
2010-05-22 22:20:24 +08:00
|
|
|
if (browser->selection->map == NULL &&
|
|
|
|
browser->selection->map->dso->annotate_warned)
|
|
|
|
continue;
|
2010-03-25 03:40:19 +08:00
|
|
|
goto do_annotate;
|
2010-05-16 08:15:01 +08:00
|
|
|
case 'D':
|
|
|
|
goto zoom_dso;
|
|
|
|
case 'T':
|
|
|
|
goto zoom_thread;
|
2010-05-17 08:04:27 +08:00
|
|
|
case 'H':
|
|
|
|
case '?':
|
|
|
|
do_help:
|
|
|
|
ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n"
|
|
|
|
"<- Zoom out\n"
|
|
|
|
"a Annotate current symbol\n"
|
|
|
|
"h/?/F1 Show this window\n"
|
|
|
|
"d Zoom into current DSO\n"
|
|
|
|
"t Zoom into current Thread\n"
|
|
|
|
"q/CTRL+C Exit browser");
|
|
|
|
continue;
|
2010-05-16 08:15:01 +08:00
|
|
|
default:;
|
|
|
|
}
|
2010-05-24 09:36:51 +08:00
|
|
|
if (is_exit_key(key)) {
|
|
|
|
if (key == NEWT_KEY_ESCAPE) {
|
2010-05-22 22:25:40 +08:00
|
|
|
if (dialog_yesno("Do you really want to exit?"))
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
} else
|
2010-03-25 03:40:14 +08:00
|
|
|
break;
|
|
|
|
}
|
2010-05-15 07:05:21 +08:00
|
|
|
|
|
|
|
if (es.u.key == NEWT_KEY_LEFT) {
|
|
|
|
const void *top;
|
|
|
|
|
|
|
|
if (pstack__empty(fstack))
|
|
|
|
continue;
|
|
|
|
top = pstack__pop(fstack);
|
|
|
|
if (top == &dso_filter)
|
|
|
|
goto zoom_out_dso;
|
|
|
|
if (top == &thread_filter)
|
|
|
|
goto zoom_out_thread;
|
|
|
|
continue;
|
|
|
|
}
|
2010-03-25 03:40:14 +08:00
|
|
|
}
|
|
|
|
|
2010-04-04 03:30:44 +08:00
|
|
|
if (browser->selection->sym != NULL &&
|
2010-05-22 22:20:24 +08:00
|
|
|
!browser->selection->map->dso->annotate_warned &&
|
2010-04-04 03:30:44 +08:00
|
|
|
asprintf(&options[nr_options], "Annotate %s",
|
|
|
|
browser->selection->sym->name) > 0)
|
|
|
|
annotate = nr_options++;
|
|
|
|
|
2010-04-04 09:44:37 +08:00
|
|
|
if (thread != NULL &&
|
|
|
|
asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
|
2010-04-05 23:02:18 +08:00
|
|
|
(thread_filter ? "out of" : "into"),
|
|
|
|
(thread->comm_set ? thread->comm : ""),
|
|
|
|
thread->pid) > 0)
|
2010-04-04 09:44:37 +08:00
|
|
|
zoom_thread = nr_options++;
|
|
|
|
|
2010-04-05 23:02:18 +08:00
|
|
|
if (dso != NULL &&
|
|
|
|
asprintf(&options[nr_options], "Zoom %s %s DSO",
|
|
|
|
(dso_filter ? "out of" : "into"),
|
|
|
|
(dso->kernel ? "the Kernel" : dso->short_name)) > 0)
|
|
|
|
zoom_dso = nr_options++;
|
|
|
|
|
2010-08-06 04:00:42 +08:00
|
|
|
if (browser->selection->map != NULL &&
|
|
|
|
asprintf(&options[nr_options], "Browse map details") > 0)
|
|
|
|
browse_map = nr_options++;
|
|
|
|
|
2010-04-04 03:30:44 +08:00
|
|
|
options[nr_options++] = (char *)"Exit";
|
2010-03-25 03:40:14 +08:00
|
|
|
|
|
|
|
choice = popup_menu(nr_options, options);
|
2010-04-04 03:30:44 +08:00
|
|
|
|
|
|
|
for (i = 0; i < nr_options - 1; ++i)
|
|
|
|
free(options[i]);
|
|
|
|
|
2010-03-25 03:40:14 +08:00
|
|
|
if (choice == nr_options - 1)
|
2010-03-12 07:12:44 +08:00
|
|
|
break;
|
2010-04-04 09:44:37 +08:00
|
|
|
|
|
|
|
if (choice == -1)
|
|
|
|
continue;
|
2010-05-16 07:45:31 +08:00
|
|
|
|
2010-04-04 03:30:44 +08:00
|
|
|
if (choice == annotate) {
|
2010-05-12 10:18:06 +08:00
|
|
|
struct hist_entry *he;
|
2010-05-16 07:45:31 +08:00
|
|
|
do_annotate:
|
2010-04-03 22:25:56 +08:00
|
|
|
if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) {
|
2010-05-22 22:20:24 +08:00
|
|
|
browser->selection->map->dso->annotate_warned = 1;
|
2010-05-12 05:01:23 +08:00
|
|
|
ui_helpline__puts("No vmlinux file found, can't "
|
2010-03-25 03:40:19 +08:00
|
|
|
"annotate with just a "
|
|
|
|
"kallsyms file");
|
|
|
|
continue;
|
|
|
|
}
|
2010-05-12 10:18:06 +08:00
|
|
|
|
|
|
|
he = hist_browser__selected_entry(browser);
|
|
|
|
if (he == NULL)
|
|
|
|
continue;
|
|
|
|
|
2010-05-22 22:25:40 +08:00
|
|
|
hist_entry__tui_annotate(he);
|
2010-08-06 04:00:42 +08:00
|
|
|
} else if (choice == browse_map)
|
|
|
|
map__browse(browser->selection->map);
|
|
|
|
else if (choice == zoom_dso) {
|
2010-05-16 08:15:01 +08:00
|
|
|
zoom_dso:
|
2010-04-05 23:02:18 +08:00
|
|
|
if (dso_filter) {
|
2010-05-15 07:05:21 +08:00
|
|
|
pstack__remove(fstack, &dso_filter);
|
|
|
|
zoom_out_dso:
|
2010-05-12 05:01:23 +08:00
|
|
|
ui_helpline__pop();
|
2010-04-05 23:02:18 +08:00
|
|
|
dso_filter = NULL;
|
|
|
|
} else {
|
2010-05-16 08:15:01 +08:00
|
|
|
if (dso == NULL)
|
|
|
|
continue;
|
2010-05-15 07:05:21 +08:00
|
|
|
ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
|
2010-05-12 05:01:23 +08:00
|
|
|
dso->kernel ? "the Kernel" : dso->short_name);
|
2010-04-05 23:02:18 +08:00
|
|
|
dso_filter = dso;
|
2010-05-15 07:05:21 +08:00
|
|
|
pstack__push(fstack, &dso_filter);
|
2010-04-05 23:02:18 +08:00
|
|
|
}
|
2010-05-11 22:10:15 +08:00
|
|
|
hists__filter_by_dso(self, dso_filter);
|
2010-05-24 09:36:51 +08:00
|
|
|
hist_browser__title(msg, sizeof(msg), ev_name,
|
2010-04-05 23:02:18 +08:00
|
|
|
dso_filter, thread_filter);
|
2010-07-27 04:13:40 +08:00
|
|
|
hist_browser__reset(browser);
|
2010-04-04 09:44:37 +08:00
|
|
|
} else if (choice == zoom_thread) {
|
2010-05-16 08:15:01 +08:00
|
|
|
zoom_thread:
|
2010-04-05 23:02:18 +08:00
|
|
|
if (thread_filter) {
|
2010-05-15 07:05:21 +08:00
|
|
|
pstack__remove(fstack, &thread_filter);
|
|
|
|
zoom_out_thread:
|
2010-05-12 05:01:23 +08:00
|
|
|
ui_helpline__pop();
|
2010-04-05 23:02:18 +08:00
|
|
|
thread_filter = NULL;
|
|
|
|
} else {
|
2010-05-15 07:05:21 +08:00
|
|
|
ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
|
2010-05-12 05:01:23 +08:00
|
|
|
thread->comm_set ? thread->comm : "",
|
|
|
|
thread->pid);
|
2010-04-05 23:02:18 +08:00
|
|
|
thread_filter = thread;
|
2010-05-15 07:05:21 +08:00
|
|
|
pstack__push(fstack, &thread_filter);
|
2010-04-05 23:02:18 +08:00
|
|
|
}
|
2010-05-11 22:10:15 +08:00
|
|
|
hists__filter_by_thread(self, thread_filter);
|
2010-05-24 09:36:51 +08:00
|
|
|
hist_browser__title(msg, sizeof(msg), ev_name,
|
2010-04-05 23:02:18 +08:00
|
|
|
dso_filter, thread_filter);
|
2010-07-27 04:13:40 +08:00
|
|
|
hist_browser__reset(browser);
|
2010-03-25 03:40:19 +08:00
|
|
|
}
|
2010-03-12 07:12:44 +08:00
|
|
|
}
|
2010-05-15 07:05:21 +08:00
|
|
|
out_free_stack:
|
|
|
|
pstack__delete(fstack);
|
2010-04-03 22:25:56 +08:00
|
|
|
out:
|
|
|
|
hist_browser__delete(browser);
|
2010-05-24 09:36:51 +08:00
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
|
|
|
int hists__tui_browse_tree(struct rb_root *self, const char *help)
|
|
|
|
{
|
|
|
|
struct rb_node *first = rb_first(self), *nd = first, *next;
|
|
|
|
int key = 0;
|
|
|
|
|
|
|
|
while (nd) {
|
|
|
|
struct hists *hists = rb_entry(nd, struct hists, rb_node);
|
|
|
|
const char *ev_name = __event_name(hists->type, hists->config);
|
|
|
|
|
|
|
|
key = hists__browse(hists, help, ev_name);
|
|
|
|
|
|
|
|
if (is_exit_key(key))
|
|
|
|
break;
|
|
|
|
|
|
|
|
switch (key) {
|
|
|
|
case NEWT_KEY_TAB:
|
|
|
|
next = rb_next(nd);
|
|
|
|
if (next)
|
|
|
|
nd = next;
|
|
|
|
break;
|
|
|
|
case NEWT_KEY_UNTAB:
|
|
|
|
if (nd == first)
|
|
|
|
continue;
|
|
|
|
nd = rb_prev(nd);
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return key;
|
2010-03-12 07:12:44 +08:00
|
|
|
}
|
|
|
|
|
2010-07-30 21:06:06 +08:00
|
|
|
static void newt_suspend(void *d __used)
|
|
|
|
{
|
|
|
|
newtSuspend();
|
|
|
|
raise(SIGTSTP);
|
|
|
|
newtResume();
|
|
|
|
}
|
|
|
|
|
2010-03-12 07:12:44 +08:00
|
|
|
void setup_browser(void)
|
|
|
|
{
|
2010-05-22 22:25:40 +08:00
|
|
|
if (!isatty(1) || !use_browser || dump_trace) {
|
2010-05-27 00:22:26 +08:00
|
|
|
use_browser = 0;
|
perf tui: Allow disabling the TUI on a per command basis in ~/.perfconfig
Using the same scheme as for git's/perf's pager setup, i.e. if one
doesn't want to, on a newt enabled perf binary, to disable the TUI for
'perf report', its just a matter of doing:
[root@doppio linux-2.6-tip]# printf "[tui]\n\nreport = off\n" >
/root/.perfconfig
[root@doppio linux-2.6-tip]# cat /root/.perfconfig
[tui]
report = off
[root@doppio linux-2.6-tip]#
System wide settings are also possible, by editing /etc/perfconfig, etc,
i.e. the git machinery for config files applies to perf as well, so when
in doubt where to put your settings, consult the git documentation, if
it fails, please let us know.
Suggested-by: Ingo Molnar <mingo@elte.hu>
Discussed-with: Stephane Eranian <eranian@google.com>
Cc: Frédéric Weisbecker <fweisbec@gmail.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Stephane Eranian <eranian@google.com>
Cc: Tom Zanussi <tzanussi@gmail.com>
LKML-Reference: <new-submission>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2010-05-21 09:01:10 +08:00
|
|
|
setup_pager();
|
2010-03-12 07:12:44 +08:00
|
|
|
return;
|
perf tui: Allow disabling the TUI on a per command basis in ~/.perfconfig
Using the same scheme as for git's/perf's pager setup, i.e. if one
doesn't want to, on a newt enabled perf binary, to disable the TUI for
'perf report', its just a matter of doing:
[root@doppio linux-2.6-tip]# printf "[tui]\n\nreport = off\n" >
/root/.perfconfig
[root@doppio linux-2.6-tip]# cat /root/.perfconfig
[tui]
report = off
[root@doppio linux-2.6-tip]#
System wide settings are also possible, by editing /etc/perfconfig, etc,
i.e. the git machinery for config files applies to perf as well, so when
in doubt where to put your settings, consult the git documentation, if
it fails, please let us know.
Suggested-by: Ingo Molnar <mingo@elte.hu>
Discussed-with: Stephane Eranian <eranian@google.com>
Cc: Frédéric Weisbecker <fweisbec@gmail.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Stephane Eranian <eranian@google.com>
Cc: Tom Zanussi <tzanussi@gmail.com>
LKML-Reference: <new-submission>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2010-05-21 09:01:10 +08:00
|
|
|
}
|
2010-03-12 07:12:44 +08:00
|
|
|
|
perf tui: Allow disabling the TUI on a per command basis in ~/.perfconfig
Using the same scheme as for git's/perf's pager setup, i.e. if one
doesn't want to, on a newt enabled perf binary, to disable the TUI for
'perf report', its just a matter of doing:
[root@doppio linux-2.6-tip]# printf "[tui]\n\nreport = off\n" >
/root/.perfconfig
[root@doppio linux-2.6-tip]# cat /root/.perfconfig
[tui]
report = off
[root@doppio linux-2.6-tip]#
System wide settings are also possible, by editing /etc/perfconfig, etc,
i.e. the git machinery for config files applies to perf as well, so when
in doubt where to put your settings, consult the git documentation, if
it fails, please let us know.
Suggested-by: Ingo Molnar <mingo@elte.hu>
Discussed-with: Stephane Eranian <eranian@google.com>
Cc: Frédéric Weisbecker <fweisbec@gmail.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Stephane Eranian <eranian@google.com>
Cc: Tom Zanussi <tzanussi@gmail.com>
LKML-Reference: <new-submission>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2010-05-21 09:01:10 +08:00
|
|
|
use_browser = 1;
|
2010-03-12 07:12:44 +08:00
|
|
|
newtInit();
|
|
|
|
newtCls();
|
2010-07-30 21:06:06 +08:00
|
|
|
newtSetSuspendCallback(newt_suspend, NULL);
|
2010-05-12 05:01:23 +08:00
|
|
|
ui_helpline__puts(" ");
|
2010-08-07 04:35:02 +08:00
|
|
|
ui_browser__init();
|
2010-03-12 07:12:44 +08:00
|
|
|
}
|
|
|
|
|
2010-03-23 00:10:25 +08:00
|
|
|
void exit_browser(bool wait_for_ok)
|
2010-03-12 07:12:44 +08:00
|
|
|
{
|
perf tui: Allow disabling the TUI on a per command basis in ~/.perfconfig
Using the same scheme as for git's/perf's pager setup, i.e. if one
doesn't want to, on a newt enabled perf binary, to disable the TUI for
'perf report', its just a matter of doing:
[root@doppio linux-2.6-tip]# printf "[tui]\n\nreport = off\n" >
/root/.perfconfig
[root@doppio linux-2.6-tip]# cat /root/.perfconfig
[tui]
report = off
[root@doppio linux-2.6-tip]#
System wide settings are also possible, by editing /etc/perfconfig, etc,
i.e. the git machinery for config files applies to perf as well, so when
in doubt where to put your settings, consult the git documentation, if
it fails, please let us know.
Suggested-by: Ingo Molnar <mingo@elte.hu>
Discussed-with: Stephane Eranian <eranian@google.com>
Cc: Frédéric Weisbecker <fweisbec@gmail.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Stephane Eranian <eranian@google.com>
Cc: Tom Zanussi <tzanussi@gmail.com>
LKML-Reference: <new-submission>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2010-05-21 09:01:10 +08:00
|
|
|
if (use_browser > 0) {
|
2010-03-23 00:10:25 +08:00
|
|
|
if (wait_for_ok) {
|
|
|
|
char title[] = "Fatal Error", ok[] = "Ok";
|
|
|
|
newtWinMessage(title, ok, browser__last_msg);
|
|
|
|
}
|
2010-03-12 07:12:44 +08:00
|
|
|
newtFinished();
|
2010-03-23 00:10:25 +08:00
|
|
|
}
|
2010-03-12 07:12:44 +08:00
|
|
|
}
|
2010-07-27 04:13:40 +08:00
|
|
|
|
|
|
|
static void hist_browser__refresh_dimensions(struct hist_browser *self)
|
|
|
|
{
|
|
|
|
/* 3 == +/- toggle symbol before actual hist_entry rendering */
|
|
|
|
self->b.width = 3 + (hists__sort_list_width(self->hists) +
|
|
|
|
sizeof("[k]"));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hist_browser__reset(struct hist_browser *self)
|
|
|
|
{
|
|
|
|
self->b.nr_entries = self->hists->nr_entries;
|
|
|
|
hist_browser__refresh_dimensions(self);
|
|
|
|
ui_browser__reset_index(&self->b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char tree__folded_sign(bool unfolded)
|
|
|
|
{
|
|
|
|
return unfolded ? '-' : '+';
|
|
|
|
}
|
|
|
|
|
|
|
|
static char map_symbol__folded(const struct map_symbol *self)
|
|
|
|
{
|
|
|
|
return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
|
|
|
|
}
|
|
|
|
|
|
|
|
static char hist_entry__folded(const struct hist_entry *self)
|
|
|
|
{
|
|
|
|
return map_symbol__folded(&self->ms);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char callchain_list__folded(const struct callchain_list *self)
|
|
|
|
{
|
|
|
|
return map_symbol__folded(&self->ms);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool map_symbol__toggle_fold(struct map_symbol *self)
|
|
|
|
{
|
|
|
|
if (!self->has_children)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
self->unfolded = !self->unfolded;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define LEVEL_OFFSET_STEP 3
|
|
|
|
|
|
|
|
static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self,
|
|
|
|
struct callchain_node *chain_node,
|
|
|
|
u64 total, int level,
|
|
|
|
unsigned short row,
|
|
|
|
off_t *row_offset,
|
|
|
|
bool *is_current_entry)
|
|
|
|
{
|
|
|
|
struct rb_node *node;
|
|
|
|
int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
|
|
|
|
u64 new_total, remaining;
|
|
|
|
|
|
|
|
if (callchain_param.mode == CHAIN_GRAPH_REL)
|
|
|
|
new_total = chain_node->children_hit;
|
|
|
|
else
|
|
|
|
new_total = total;
|
|
|
|
|
|
|
|
remaining = new_total;
|
|
|
|
node = rb_first(&chain_node->rb_root);
|
|
|
|
while (node) {
|
|
|
|
struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
|
|
|
|
struct rb_node *next = rb_next(node);
|
|
|
|
u64 cumul = cumul_hits(child);
|
|
|
|
struct callchain_list *chain;
|
|
|
|
char folded_sign = ' ';
|
|
|
|
int first = true;
|
|
|
|
int extra_offset = 0;
|
|
|
|
|
|
|
|
remaining -= cumul;
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str;
|
|
|
|
const char *str;
|
|
|
|
int color;
|
|
|
|
bool was_first = first;
|
|
|
|
|
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
chain->ms.has_children = chain->list.next != &child->val ||
|
|
|
|
rb_first(&child->rb_root) != NULL;
|
|
|
|
} else {
|
|
|
|
extra_offset = LEVEL_OFFSET_STEP;
|
|
|
|
chain->ms.has_children = chain->list.next == &child->val &&
|
|
|
|
rb_first(&child->rb_root) != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
folded_sign = callchain_list__folded(chain);
|
|
|
|
if (*row_offset != 0) {
|
|
|
|
--*row_offset;
|
|
|
|
goto do_next;
|
|
|
|
}
|
|
|
|
|
|
|
|
alloc_str = NULL;
|
|
|
|
str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
|
|
|
|
if (was_first) {
|
|
|
|
double percent = cumul * 100.0 / new_total;
|
|
|
|
|
|
|
|
if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
|
|
|
|
str = "Not enough memory!";
|
|
|
|
else
|
|
|
|
str = alloc_str;
|
|
|
|
}
|
|
|
|
|
|
|
|
color = HE_COLORSET_NORMAL;
|
|
|
|
width = self->b.width - (offset + extra_offset + 2);
|
|
|
|
if (ui_browser__is_current_entry(&self->b, row)) {
|
|
|
|
self->selection = &chain->ms;
|
|
|
|
color = HE_COLORSET_SELECTED;
|
|
|
|
*is_current_entry = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
SLsmg_set_color(color);
|
2010-08-08 00:56:04 +08:00
|
|
|
SLsmg_gotorc(self->b.y + row, self->b.x);
|
2010-07-27 04:13:40 +08:00
|
|
|
slsmg_write_nstring(" ", offset + extra_offset);
|
|
|
|
slsmg_printf("%c ", folded_sign);
|
|
|
|
slsmg_write_nstring(str, width);
|
|
|
|
free(alloc_str);
|
|
|
|
|
|
|
|
if (++row == self->b.height)
|
|
|
|
goto out;
|
|
|
|
do_next:
|
|
|
|
if (folded_sign == '+')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (folded_sign == '-') {
|
|
|
|
const int new_level = level + (extra_offset ? 2 : 1);
|
|
|
|
row += hist_browser__show_callchain_node_rb_tree(self, child, new_total,
|
|
|
|
new_level, row, row_offset,
|
|
|
|
is_current_entry);
|
|
|
|
}
|
|
|
|
if (row == self->b.height)
|
|
|
|
goto out;
|
|
|
|
node = next;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
return row - first_row;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hist_browser__show_callchain_node(struct hist_browser *self,
|
|
|
|
struct callchain_node *node,
|
|
|
|
int level, unsigned short row,
|
|
|
|
off_t *row_offset,
|
|
|
|
bool *is_current_entry)
|
|
|
|
{
|
|
|
|
struct callchain_list *chain;
|
|
|
|
int first_row = row,
|
|
|
|
offset = level * LEVEL_OFFSET_STEP,
|
|
|
|
width = self->b.width - offset;
|
|
|
|
char folded_sign = ' ';
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &node->val, list) {
|
|
|
|
char ipstr[BITS_PER_LONG / 4 + 1], *s;
|
|
|
|
int color;
|
|
|
|
/*
|
|
|
|
* FIXME: This should be moved to somewhere else,
|
|
|
|
* probably when the callchain is created, so as not to
|
|
|
|
* traverse it all over again
|
|
|
|
*/
|
|
|
|
chain->ms.has_children = rb_first(&node->rb_root) != NULL;
|
|
|
|
folded_sign = callchain_list__folded(chain);
|
|
|
|
|
|
|
|
if (*row_offset != 0) {
|
|
|
|
--*row_offset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
color = HE_COLORSET_NORMAL;
|
|
|
|
if (ui_browser__is_current_entry(&self->b, row)) {
|
|
|
|
self->selection = &chain->ms;
|
|
|
|
color = HE_COLORSET_SELECTED;
|
|
|
|
*is_current_entry = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
|
2010-08-08 00:56:04 +08:00
|
|
|
SLsmg_gotorc(self->b.y + row, self->b.x);
|
2010-07-27 04:13:40 +08:00
|
|
|
SLsmg_set_color(color);
|
|
|
|
slsmg_write_nstring(" ", offset);
|
|
|
|
slsmg_printf("%c ", folded_sign);
|
|
|
|
slsmg_write_nstring(s, width - 2);
|
|
|
|
|
|
|
|
if (++row == self->b.height)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (folded_sign == '-')
|
|
|
|
row += hist_browser__show_callchain_node_rb_tree(self, node,
|
|
|
|
self->hists->stats.total_period,
|
|
|
|
level + 1, row,
|
|
|
|
row_offset,
|
|
|
|
is_current_entry);
|
|
|
|
out:
|
|
|
|
return row - first_row;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hist_browser__show_callchain(struct hist_browser *self,
|
|
|
|
struct rb_root *chain,
|
|
|
|
int level, unsigned short row,
|
|
|
|
off_t *row_offset,
|
|
|
|
bool *is_current_entry)
|
|
|
|
{
|
|
|
|
struct rb_node *nd;
|
|
|
|
int first_row = row;
|
|
|
|
|
|
|
|
for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
|
|
|
|
struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
|
|
|
|
row += hist_browser__show_callchain_node(self, node, level,
|
|
|
|
row, row_offset,
|
|
|
|
is_current_entry);
|
|
|
|
if (row == self->b.height)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return row - first_row;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hist_browser__show_entry(struct hist_browser *self,
|
|
|
|
struct hist_entry *entry,
|
|
|
|
unsigned short row)
|
|
|
|
{
|
|
|
|
char s[256];
|
|
|
|
double percent;
|
|
|
|
int printed = 0;
|
|
|
|
int color, width = self->b.width;
|
|
|
|
char folded_sign = ' ';
|
|
|
|
bool current_entry = ui_browser__is_current_entry(&self->b, row);
|
|
|
|
off_t row_offset = entry->row_offset;
|
|
|
|
|
|
|
|
if (current_entry) {
|
|
|
|
self->he_selection = entry;
|
|
|
|
self->selection = &entry->ms;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (symbol_conf.use_callchain) {
|
|
|
|
entry->ms.has_children = !RB_EMPTY_ROOT(&entry->sorted_chain);
|
|
|
|
folded_sign = hist_entry__folded(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (row_offset == 0) {
|
|
|
|
hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false,
|
|
|
|
0, false, self->hists->stats.total_period);
|
|
|
|
percent = (entry->period * 100.0) / self->hists->stats.total_period;
|
|
|
|
|
|
|
|
color = HE_COLORSET_SELECTED;
|
|
|
|
if (!current_entry) {
|
|
|
|
if (percent >= MIN_RED)
|
|
|
|
color = HE_COLORSET_TOP;
|
|
|
|
else if (percent >= MIN_GREEN)
|
|
|
|
color = HE_COLORSET_MEDIUM;
|
|
|
|
else
|
|
|
|
color = HE_COLORSET_NORMAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
SLsmg_set_color(color);
|
2010-08-08 00:56:04 +08:00
|
|
|
SLsmg_gotorc(self->b.y + row, self->b.x);
|
2010-07-27 04:13:40 +08:00
|
|
|
if (symbol_conf.use_callchain) {
|
|
|
|
slsmg_printf("%c ", folded_sign);
|
|
|
|
width -= 2;
|
|
|
|
}
|
|
|
|
slsmg_write_nstring(s, width);
|
|
|
|
++row;
|
|
|
|
++printed;
|
|
|
|
} else
|
|
|
|
--row_offset;
|
|
|
|
|
|
|
|
if (folded_sign == '-' && row != self->b.height) {
|
|
|
|
printed += hist_browser__show_callchain(self, &entry->sorted_chain,
|
|
|
|
1, row, &row_offset,
|
|
|
|
¤t_entry);
|
|
|
|
if (current_entry)
|
|
|
|
self->he_selection = entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
return printed;
|
|
|
|
}
|
|
|
|
|
2010-08-06 04:02:54 +08:00
|
|
|
static unsigned int hist_browser__refresh(struct ui_browser *self)
|
2010-07-27 04:13:40 +08:00
|
|
|
{
|
|
|
|
unsigned row = 0;
|
|
|
|
struct rb_node *nd;
|
|
|
|
struct hist_browser *hb = container_of(self, struct hist_browser, b);
|
|
|
|
|
2010-08-08 00:56:04 +08:00
|
|
|
if (self->top == NULL)
|
|
|
|
self->top = rb_first(&hb->hists->entries);
|
2010-07-27 04:13:40 +08:00
|
|
|
|
2010-08-08 00:56:04 +08:00
|
|
|
for (nd = self->top; nd; nd = rb_next(nd)) {
|
2010-07-27 04:13:40 +08:00
|
|
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
|
|
|
|
if (h->filtered)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
row += hist_browser__show_entry(hb, h, row);
|
|
|
|
if (row == self->height)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return row;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
|
|
|
|
{
|
|
|
|
struct rb_node *nd = rb_first(&self->rb_root);
|
|
|
|
|
|
|
|
for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
|
|
|
|
struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
struct callchain_list *chain;
|
|
|
|
int first = true;
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
chain->ms.has_children = chain->list.next != &child->val ||
|
|
|
|
rb_first(&child->rb_root) != NULL;
|
|
|
|
} else
|
|
|
|
chain->ms.has_children = chain->list.next == &child->val &&
|
|
|
|
rb_first(&child->rb_root) != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
callchain_node__init_have_children_rb_tree(child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void callchain_node__init_have_children(struct callchain_node *self)
|
|
|
|
{
|
|
|
|
struct callchain_list *chain;
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &self->val, list)
|
|
|
|
chain->ms.has_children = rb_first(&self->rb_root) != NULL;
|
|
|
|
|
|
|
|
callchain_node__init_have_children_rb_tree(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void callchain__init_have_children(struct rb_root *self)
|
|
|
|
{
|
|
|
|
struct rb_node *nd;
|
|
|
|
|
|
|
|
for (nd = rb_first(self); nd; nd = rb_next(nd)) {
|
|
|
|
struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
callchain_node__init_have_children(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hist_entry__init_have_children(struct hist_entry *self)
|
|
|
|
{
|
|
|
|
if (!self->init_have_children) {
|
|
|
|
callchain__init_have_children(&self->sorted_chain);
|
|
|
|
self->init_have_children = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct rb_node *hists__filter_entries(struct rb_node *nd)
|
|
|
|
{
|
|
|
|
while (nd != NULL) {
|
|
|
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
if (!h->filtered)
|
|
|
|
return nd;
|
|
|
|
|
|
|
|
nd = rb_next(nd);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
|
|
|
|
{
|
|
|
|
while (nd != NULL) {
|
|
|
|
struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
if (!h->filtered)
|
|
|
|
return nd;
|
|
|
|
|
|
|
|
nd = rb_prev(nd);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ui_browser__hists_seek(struct ui_browser *self,
|
|
|
|
off_t offset, int whence)
|
|
|
|
{
|
|
|
|
struct hist_entry *h;
|
|
|
|
struct rb_node *nd;
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
switch (whence) {
|
|
|
|
case SEEK_SET:
|
|
|
|
nd = hists__filter_entries(rb_first(self->entries));
|
|
|
|
break;
|
|
|
|
case SEEK_CUR:
|
2010-08-08 00:56:04 +08:00
|
|
|
nd = self->top;
|
2010-07-27 04:13:40 +08:00
|
|
|
goto do_offset;
|
|
|
|
case SEEK_END:
|
|
|
|
nd = hists__filter_prev_entries(rb_last(self->entries));
|
|
|
|
first = false;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Moves not relative to the first visible entry invalidates its
|
|
|
|
* row_offset:
|
|
|
|
*/
|
2010-08-08 00:56:04 +08:00
|
|
|
h = rb_entry(self->top, struct hist_entry, rb_node);
|
2010-07-27 04:13:40 +08:00
|
|
|
h->row_offset = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Here we have to check if nd is expanded (+), if it is we can't go
|
|
|
|
* the next top level hist_entry, instead we must compute an offset of
|
|
|
|
* what _not_ to show and not change the first visible entry.
|
|
|
|
*
|
|
|
|
* This offset increments when we are going from top to bottom and
|
|
|
|
* decreases when we're going from bottom to top.
|
|
|
|
*
|
|
|
|
* As we don't have backpointers to the top level in the callchains
|
|
|
|
* structure, we need to always print the whole hist_entry callchain,
|
|
|
|
* skipping the first ones that are before the first visible entry
|
|
|
|
* and stop when we printed enough lines to fill the screen.
|
|
|
|
*/
|
|
|
|
do_offset:
|
|
|
|
if (offset > 0) {
|
|
|
|
do {
|
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
if (h->ms.unfolded) {
|
|
|
|
u16 remaining = h->nr_rows - h->row_offset;
|
|
|
|
if (offset > remaining) {
|
|
|
|
offset -= remaining;
|
|
|
|
h->row_offset = 0;
|
|
|
|
} else {
|
|
|
|
h->row_offset += offset;
|
|
|
|
offset = 0;
|
2010-08-08 00:56:04 +08:00
|
|
|
self->top = nd;
|
2010-07-27 04:13:40 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
nd = hists__filter_entries(rb_next(nd));
|
|
|
|
if (nd == NULL)
|
|
|
|
break;
|
|
|
|
--offset;
|
2010-08-08 00:56:04 +08:00
|
|
|
self->top = nd;
|
2010-07-27 04:13:40 +08:00
|
|
|
} while (offset != 0);
|
|
|
|
} else if (offset < 0) {
|
|
|
|
while (1) {
|
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
if (h->ms.unfolded) {
|
|
|
|
if (first) {
|
|
|
|
if (-offset > h->row_offset) {
|
|
|
|
offset += h->row_offset;
|
|
|
|
h->row_offset = 0;
|
|
|
|
} else {
|
|
|
|
h->row_offset += offset;
|
|
|
|
offset = 0;
|
2010-08-08 00:56:04 +08:00
|
|
|
self->top = nd;
|
2010-07-27 04:13:40 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (-offset > h->nr_rows) {
|
|
|
|
offset += h->nr_rows;
|
|
|
|
h->row_offset = 0;
|
|
|
|
} else {
|
|
|
|
h->row_offset = h->nr_rows + offset;
|
|
|
|
offset = 0;
|
2010-08-08 00:56:04 +08:00
|
|
|
self->top = nd;
|
2010-07-27 04:13:40 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nd = hists__filter_prev_entries(rb_prev(nd));
|
|
|
|
if (nd == NULL)
|
|
|
|
break;
|
|
|
|
++offset;
|
2010-08-08 00:56:04 +08:00
|
|
|
self->top = nd;
|
2010-07-27 04:13:40 +08:00
|
|
|
if (offset == 0) {
|
|
|
|
/*
|
|
|
|
* Last unfiltered hist_entry, check if it is
|
|
|
|
* unfolded, if it is then we should have
|
|
|
|
* row_offset at its last entry.
|
|
|
|
*/
|
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
if (h->ms.unfolded)
|
|
|
|
h->row_offset = h->nr_rows;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
} else {
|
2010-08-08 00:56:04 +08:00
|
|
|
self->top = nd;
|
2010-07-27 04:13:40 +08:00
|
|
|
h = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
h->row_offset = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
|
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
struct rb_node *nd;
|
|
|
|
|
|
|
|
for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
|
|
|
|
struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
struct callchain_list *chain;
|
|
|
|
char folded_sign = ' '; /* No children */
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
++n;
|
|
|
|
/* We need this because we may not have children */
|
|
|
|
folded_sign = callchain_list__folded(chain);
|
|
|
|
if (folded_sign == '+')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (folded_sign == '-') /* Have children and they're unfolded */
|
|
|
|
n += callchain_node__count_rows_rb_tree(child);
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int callchain_node__count_rows(struct callchain_node *node)
|
|
|
|
{
|
|
|
|
struct callchain_list *chain;
|
|
|
|
bool unfolded = false;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(chain, &node->val, list) {
|
|
|
|
++n;
|
|
|
|
unfolded = chain->ms.unfolded;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unfolded)
|
|
|
|
n += callchain_node__count_rows_rb_tree(node);
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int callchain__count_rows(struct rb_root *chain)
|
|
|
|
{
|
|
|
|
struct rb_node *nd;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
|
|
|
|
struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
|
|
|
|
n += callchain_node__count_rows(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool hist_browser__toggle_fold(struct hist_browser *self)
|
|
|
|
{
|
|
|
|
if (map_symbol__toggle_fold(self->selection)) {
|
|
|
|
struct hist_entry *he = self->he_selection;
|
|
|
|
|
|
|
|
hist_entry__init_have_children(he);
|
|
|
|
self->hists->nr_entries -= he->nr_rows;
|
|
|
|
|
|
|
|
if (he->ms.unfolded)
|
|
|
|
he->nr_rows = callchain__count_rows(&he->sorted_chain);
|
|
|
|
else
|
|
|
|
he->nr_rows = 0;
|
|
|
|
self->hists->nr_entries += he->nr_rows;
|
|
|
|
self->b.nr_entries = self->hists->nr_entries;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If it doesn't have children, no toggling performed */
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hist_browser__run(struct hist_browser *self, const char *title,
|
|
|
|
struct newtExitStruct *es)
|
|
|
|
{
|
|
|
|
char str[256], unit;
|
|
|
|
unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE];
|
|
|
|
|
|
|
|
self->b.entries = &self->hists->entries;
|
|
|
|
self->b.nr_entries = self->hists->nr_entries;
|
|
|
|
|
|
|
|
hist_browser__refresh_dimensions(self);
|
|
|
|
|
|
|
|
nr_events = convert_unit(nr_events, &unit);
|
|
|
|
snprintf(str, sizeof(str), "Events: %lu%c ",
|
|
|
|
nr_events, unit);
|
|
|
|
newtDrawRootText(0, 0, str);
|
|
|
|
|
|
|
|
if (ui_browser__show(&self->b, title) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
newtFormAddHotKey(self->b.form, 'A');
|
|
|
|
newtFormAddHotKey(self->b.form, 'a');
|
|
|
|
newtFormAddHotKey(self->b.form, '?');
|
|
|
|
newtFormAddHotKey(self->b.form, 'h');
|
|
|
|
newtFormAddHotKey(self->b.form, 'H');
|
|
|
|
newtFormAddHotKey(self->b.form, 'd');
|
|
|
|
|
|
|
|
newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT);
|
|
|
|
newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT);
|
|
|
|
newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
ui_browser__run(&self->b, es);
|
|
|
|
|
|
|
|
if (es->reason != NEWT_EXIT_HOTKEY)
|
|
|
|
break;
|
|
|
|
switch (es->u.key) {
|
|
|
|
case 'd': { /* Debug */
|
|
|
|
static int seq;
|
2010-08-08 00:56:04 +08:00
|
|
|
struct hist_entry *h = rb_entry(self->b.top,
|
2010-07-27 04:13:40 +08:00
|
|
|
struct hist_entry, rb_node);
|
|
|
|
ui_helpline__pop();
|
|
|
|
ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
|
|
|
|
seq++, self->b.nr_entries,
|
|
|
|
self->hists->nr_entries,
|
|
|
|
self->b.height,
|
|
|
|
self->b.index,
|
2010-08-08 00:56:04 +08:00
|
|
|
self->b.top_idx,
|
2010-07-27 04:13:40 +08:00
|
|
|
h->row_offset, h->nr_rows);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
case NEWT_KEY_ENTER:
|
|
|
|
if (hist_browser__toggle_fold(self))
|
|
|
|
break;
|
|
|
|
/* fall thru */
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|