perf report: Implement initial UI using newt
authorArnaldo Carvalho de Melo <acme@redhat.com>
Thu, 11 Mar 2010 23:12:44 +0000 (20:12 -0300)
committerIngo Molnar <mingo@elte.hu>
Fri, 12 Mar 2010 09:28:46 +0000 (10:28 +0100)
Newt has widespread availability and provides a rather simple
API as can be seen by the size of this patch.

The work needed to support it will benefit other frontends too.

In this initial patch it just checks if the output is a tty, if
not it falls back to the previous behaviour, also if
newt-devel/libnewt-dev is not installed the previous behaviour
is maintaned.

Pressing enter on a symbol will annotate it, ESC in the
annotation window will return to the report symbol list.

More work will be done to remove the special casing in
color_fprintf, stop using fmemopen/FILE in the printing of
hist_entries, etc.

Also the annotation doesn't need to be done via spawning "perf
annotate" and then browsing its output, we can do better by
calling directly the builtin-annotate.c functions, that would
then be moved to tools/perf/util/annotate.c and shared with perf
top, etc

But lets go by baby steps, this patch already improves perf
usability by allowing to quickly do annotations on symbols from
the report screen and provides a first experimentation with
libnewt/TUI integration of tools.

Tested on RHEL5 and Fedora12 X86_64 and on Debian PARISC64 to
browse a perf.data file collected on a Fedora12 x86_64 box.

Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Avi Kivity <avi@redhat.com>
Cc: Frédéric Weisbecker <fweisbec@gmail.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Paul Mackerras <paulus@samba.org>
LKML-Reference: <1268349164-5822-5-git-send-email-acme@infradead.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
tools/perf/Makefile
tools/perf/builtin-report.c
tools/perf/perf.c
tools/perf/util/cache.h
tools/perf/util/color.c
tools/perf/util/debug.c
tools/perf/util/debug.h
tools/perf/util/hist.h
tools/perf/util/newt.c [new file with mode: 0644]
tools/perf/util/session.h

index 8a8f52db7e385c28b2092f255ea07fb532fa5607..0abd25ee595f8f7350ca053d3f74c05820a19564 100644 (file)
@@ -513,6 +513,14 @@ else
        LIB_OBJS += util/probe-finder.o
 endif
 
+ifneq ($(shell sh -c "(echo '\#include <newt.h>'; echo 'int main(void) { newtInit(); newtCls(); return newtFinished(); }') | $(CC) -x c - $(ALL_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -lnewt -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) "$(QUIET_STDERR)" && echo y"), y)
+       msg := $(warning newt not found, disables TUI support. Please install newt-devel or libnewt-dev);
+       BASIC_CFLAGS += -DNO_NEWT_SUPPORT
+else
+       EXTLIBS += -lnewt
+       LIB_OBJS += util/newt.o
+endif
+
 ifndef NO_LIBPERL
 PERL_EMBED_LDOPTS = `perl -MExtUtils::Embed -e ldopts 2>/dev/null`
 PERL_EMBED_CCOPTS = `perl -MExtUtils::Embed -e ccopts 2>/dev/null`
index f815de25d0fc5b76eef680411930589654e793e8..1f9f8695f055a3327fbff0658d4e2df6864dc0db 100644 (file)
@@ -267,6 +267,7 @@ static int __cmd_report(void)
        int ret = -EINVAL;
        struct perf_session *session;
        struct rb_node *next;
+       const char *help = "For a higher level overview, try: perf report --sort comm,dso";
 
        session = perf_session__new(input_name, O_RDONLY, force);
        if (session == NULL)
@@ -301,30 +302,38 @@ static int __cmd_report(void)
                stats = rb_entry(next, struct event_stat_id, rb_node);
                perf_session__collapse_resort(&stats->hists);
                perf_session__output_resort(&stats->hists, stats->stats.total);
-               if (rb_first(&session->stats_by_id) ==
-                   rb_last(&session->stats_by_id))
-                       fprintf(stdout, "# Samples: %Ld\n#\n",
-                               stats->stats.total);
-               else
-                       fprintf(stdout, "# Samples: %Ld %s\n#\n",
-                               stats->stats.total,
-                               __event_name(stats->type, stats->config));
 
-               perf_session__fprintf_hists(&stats->hists, NULL, false, stdout,
+               if (use_browser)
+                       perf_session__browse_hists(&stats->hists,
+                                                  stats->stats.total, help);
+               else {
+                       if (rb_first(&session->stats_by_id) ==
+                           rb_last(&session->stats_by_id))
+                               fprintf(stdout, "# Samples: %Ld\n#\n",
+                                       stats->stats.total);
+                       else
+                               fprintf(stdout, "# Samples: %Ld %s\n#\n",
+                                       stats->stats.total,
+                                       __event_name(stats->type, stats->config));
+
+                       perf_session__fprintf_hists(&stats->hists, NULL, false, stdout,
                                            stats->stats.total);
-               fprintf(stdout, "\n\n");
+                       fprintf(stdout, "\n\n");
+               }
+
                next = rb_next(&stats->rb_node);
        }
 
-       if (sort_order == default_sort_order &&
-           parent_pattern == default_parent_pattern)
-               fprintf(stdout, "#\n# (For a higher level overview, try: perf report --sort comm,dso)\n#\n");
+       if (!use_browser && sort_order == default_sort_order &&
+           parent_pattern == default_parent_pattern) {
+               fprintf(stdout, "#\n# (%s)\n#\n", help);
 
-       if (show_threads) {
-               bool raw_printing_style = !strcmp(pretty_printing_style, "raw");
-               perf_read_values_display(stdout, &show_threads_values,
-                                        raw_printing_style);
-               perf_read_values_destroy(&show_threads_values);
+               if (show_threads) {
+                       bool style = !strcmp(pretty_printing_style, "raw");
+                       perf_read_values_display(stdout, &show_threads_values,
+                                                style);
+                       perf_read_values_destroy(&show_threads_values);
+               }
        }
 out_delete:
        perf_session__delete(session);
@@ -447,7 +456,7 @@ int cmd_report(int argc, const char **argv, const char *prefix __used)
 {
        argc = parse_options(argc, argv, options, report_usage, 0);
 
-       setup_pager();
+       setup_browser();
 
        if (symbol__init() < 0)
                return -1;
index 57cb107c1f13291a7c94272d473bfea5862f6281..9ff186b57cb7715f5d9cfdae8e7590543038ffce 100644 (file)
@@ -265,6 +265,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
        if (status)
                return status & 0xff;
 
+       exit_browser();
+
        /* Somebody closed stdout? */
        if (fstat(fileno(stdout), &st))
                return 0;
index 918eb376abe3943375cf1ea1843d4f724ac0f621..47b12a3d11bf92a8d48fa6b9944f1c64067a93a6 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef __PERF_CACHE_H
 #define __PERF_CACHE_H
 
+#include <stdbool.h>
 #include "util.h"
 #include "strbuf.h"
 #include "../perf.h"
@@ -69,6 +70,19 @@ extern const char *pager_program;
 extern int pager_in_use(void);
 extern int pager_use_color;
 
+extern bool use_browser;
+
+#ifdef NO_NEWT_SUPPORT
+static inline void setup_browser(void)
+{
+       setup_pager();
+}
+static inline void exit_browser(void) {}
+#else
+void setup_browser(void);
+void exit_browser(void);
+#endif
+
 extern const char *editor_program;
 extern const char *excludes_file;
 
index e88bca55a5993c82212c6307d35bab9824d9358f..9da01914e0af644461e303650c7021c6fc383013 100644 (file)
@@ -203,7 +203,10 @@ int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)
        int r;
 
        va_start(args, fmt);
-       r = color_vfprintf(fp, color, fmt, args);
+       if (use_browser)
+               r = vfprintf(fp, fmt, args);
+       else
+               r = color_vfprintf(fp, color, fmt, args);
        va_end(args);
        return r;
 }
index 0905600c3851b51ec6b52f716e33f797c1da1032..033d66db863a14990b308c66be3f62e1e863d0d6 100644 (file)
@@ -6,6 +6,7 @@
 #include <stdarg.h>
 #include <stdio.h>
 
+#include "cache.h"
 #include "color.h"
 #include "event.h"
 #include "debug.h"
@@ -21,7 +22,10 @@ int eprintf(int level, const char *fmt, ...)
 
        if (verbose >= level) {
                va_start(args, fmt);
-               ret = vfprintf(stderr, fmt, args);
+               if (use_browser)
+                       ret = browser__show_help(fmt, args);
+               else
+                       ret = vfprintf(stderr, fmt, args);
                va_end(args);
        }
 
index 58720a181591ba18740960b937e9446a244f7f9e..03accb867996a5523070f34fed2a1b609f903f01 100644 (file)
@@ -9,5 +9,6 @@ extern int dump_trace;
 
 int dump_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
 void trace_event(event_t *event);
+int browser__show_help(const char *format, va_list ap);
 
 #endif /* __PERF_DEBUG_H */
index 16f360cce5bfff0e56c7096e418bc38949652d58..fe366ce5db453ba2a153374b82de4a055d98ff64 100644 (file)
@@ -18,6 +18,11 @@ struct hist_entry *__perf_session__add_hist_entry(struct rb_root *hists,
                                                  u64 count, bool *hit);
 extern int64_t hist_entry__cmp(struct hist_entry *, struct hist_entry *);
 extern int64_t hist_entry__collapse(struct hist_entry *, struct hist_entry *);
+size_t hist_entry__fprintf(struct hist_entry *self,
+                          struct perf_session *pair_session,
+                          bool show_displacement,
+                          long displacement, FILE *fp,
+                          u64 session_total);
 void hist_entry__free(struct hist_entry *);
 
 void perf_session__output_resort(struct rb_root *hists, u64 total_samples);
diff --git a/tools/perf/util/newt.c b/tools/perf/util/newt.c
new file mode 100644 (file)
index 0000000..3d3a936
--- /dev/null
@@ -0,0 +1,194 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#undef _GNU_SOURCE
+
+#include <stdlib.h>
+#include <newt.h>
+
+#include "cache.h"
+#include "hist.h"
+#include "session.h"
+#include "sort.h"
+#include "symbol.h"
+
+static size_t hist_entry__append_browser(struct hist_entry *self,
+                                        newtComponent listbox, u64 total)
+{
+       char bf[1024];
+       size_t len;
+       FILE *fp;
+
+       if (symbol_conf.exclude_other && !self->parent)
+               return 0;
+
+       fp = fmemopen(bf, sizeof(bf), "w");
+       if (fp == NULL)
+               return 0;
+
+       len = hist_entry__fprintf(self, NULL, false, 0, fp, total);
+
+       fclose(fp);
+       newtListboxAppendEntry(listbox, bf, self);
+       return len;
+}
+
+static void hist_entry__annotate_browser(struct hist_entry *self)
+{
+       FILE *fp;
+       struct winsize ws;
+       newtComponent form, listbox;
+       struct newtExitStruct es;
+       char *str;
+       size_t line_len, max_line_len = 0;
+       size_t max_usable_width;
+       char *line = NULL;
+
+       if (self->sym == NULL)
+               return;
+
+       if (asprintf(&str, "perf annotate %s | expand", self->sym->name) < 0)
+               return;
+
+       fp = popen(str, "r");
+       if (fp == NULL)
+               goto out_free_str;
+
+       newtPushHelpLine("Press ESC to exit");
+       get_term_dimensions(&ws);
+       listbox = newtListbox(0, 0, ws.ws_row - 5, NEWT_FLAG_SCROLL);
+
+       while (!feof(fp)) {
+               if (getline(&line, &line_len, fp) < 0 || !line_len)
+                       break;
+               while (line_len != 0 && isspace(line[line_len - 1]))
+                       line[--line_len] = '\0';
+
+               if (line_len > max_line_len)
+                       max_line_len = line_len;
+               newtListboxAppendEntry(listbox, line, NULL);
+       }
+       fclose(fp);
+       free(line);
+
+       max_usable_width = ws.ws_col - 22;
+       if (max_line_len > max_usable_width)
+               max_line_len = max_usable_width;
+
+       newtListboxSetWidth(listbox, max_line_len);
+
+       newtCenteredWindow(max_line_len + 2, ws.ws_row - 5, self->sym->name);
+       form = newtForm(NULL, NULL, 0);
+       newtFormAddHotKey(form, NEWT_KEY_ESCAPE);
+       newtFormAddComponents(form, listbox, NULL);
+
+       newtFormRun(form, &es);
+       newtFormDestroy(form);
+       newtPopWindow();
+       newtPopHelpLine();
+out_free_str:
+       free(str);
+}
+
+void perf_session__browse_hists(struct rb_root *hists, u64 session_total,
+                               const char *helpline)
+{
+       struct sort_entry *se;
+       struct rb_node *nd;
+       unsigned int width;
+       char *col_width = symbol_conf.col_width_list_str;
+       struct winsize ws;
+       size_t max_len = 0;
+       char str[1024];
+       newtComponent form, listbox;
+       struct newtExitStruct es;
+
+       snprintf(str, sizeof(str), "Samples: %Ld", session_total);
+       newtDrawRootText(0, 0, str);
+       newtPushHelpLine(helpline);
+
+       get_term_dimensions(&ws);
+
+       form = newtForm(NULL, NULL, 0);
+       newtFormAddHotKey(form, NEWT_KEY_ESCAPE);
+
+       listbox = newtListbox(1, 1, ws.ws_row - 2, (NEWT_FLAG_SCROLL |
+                                                   NEWT_FLAG_BORDER |
+                                                   NEWT_FLAG_RETURNEXIT));
+
+       list_for_each_entry(se, &hist_entry__sort_list, list) {
+               if (se->elide)
+                       continue;
+               width = strlen(se->header);
+               if (se->width) {
+                       if (symbol_conf.col_width_list_str) {
+                               if (col_width) {
+                                       *se->width = atoi(col_width);
+                                       col_width = strchr(col_width, ',');
+                                       if (col_width)
+                                               ++col_width;
+                               }
+                       }
+                       *se->width = max(*se->width, width);
+               }
+       }
+
+       for (nd = rb_first(hists); nd; nd = rb_next(nd)) {
+               struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+               size_t len = hist_entry__append_browser(h, listbox, session_total);
+               if (len > max_len)
+                       max_len = len;
+       }
+
+       newtListboxSetWidth(listbox, max_len);
+       newtFormAddComponents(form, listbox, NULL);
+
+       while (1) {
+               struct hist_entry *selection;
+
+               newtFormRun(form, &es);
+               if (es.reason == NEWT_EXIT_HOTKEY)
+                       break;
+               selection = newtListboxGetCurrent(listbox);
+               hist_entry__annotate_browser(selection);
+       }
+
+       newtFormDestroy(form);
+}
+
+int browser__show_help(const char *format, va_list ap)
+{
+       int ret;
+       static int backlog;
+       static char msg[1024];
+
+        ret = vsnprintf(msg + backlog, sizeof(msg) - backlog, format, ap);
+       backlog += ret;
+
+       if (msg[backlog - 1] == '\n') {
+               newtPopHelpLine();
+               newtPushHelpLine(msg);
+               newtRefresh();
+               backlog = 0;
+       }
+
+       return ret;
+}
+
+bool use_browser;
+
+void setup_browser(void)
+{
+       if (!isatty(1))
+               return;
+
+       use_browser = true;
+       newtInit();
+       newtCls();
+       newtPushHelpLine(" ");
+}
+
+void exit_browser(void)
+{
+       if (use_browser)
+               newtFinished();
+}
index 5c33417eebb396599d77caa15798e5bce5293063..34d73395baacacbb48d0cfe73218c91f84f57dfa 100644 (file)
@@ -86,4 +86,13 @@ static inline struct map *
 {
        return map_groups__new_module(&self->kmaps, start, filename);
 }
+
+#ifdef NO_NEWT_SUPPORT
+static inline void perf_session__browse_hists(struct rb_root *hists __used,
+                                             u64 session_total __used,
+                                             const char *helpline __used) {}
+#else
+void perf_session__browse_hists(struct rb_root *hists, u64 session_total,
+                               const char *helpline);
+#endif
 #endif /* __PERF_SESSION_H */