perf report: Implement browsing of individual samples
authorAndi Kleen <ak@linux.intel.com>
Mon, 11 Mar 2019 14:44:58 +0000 (07:44 -0700)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Mon, 11 Mar 2019 19:33:19 +0000 (16:33 -0300)
Now 'perf report' can show whole time periods with 'perf script', but
the user still has to find individual samples of interest manually.

It would be expensive and complicated to search for the right samples in
the whole perf file. Typically users only need to look at a small number
of samples for useful analysis.

Also the full scripts tend to show samples of all CPUs and all threads
mixed up, which can be very confusing on larger systems.

Add a new --samples option to save a small random number of samples per
hist entry.

Use a reservoir sample technique to select a representatve number of
samples.

Then allow browsing the samples using 'perf script' as part of the hist
entry context menu. This automatically adds the right filters, so only
the thread or cpu of the sample is displayed. Then we use less' search
functionality to directly jump the to the time stamp of the selected
sample.

It uses different menus for assembler and source display.  Assembler
needs xed installed and source needs debuginfo.

Currently it only supports as many samples as fit on the screen due to
some limitations in the slang ui code.

Signed-off-by: Andi Kleen <ak@linux.intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Link: http://lkml.kernel.org/r/20190311174605.GA29294@tassilo.jf.intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
12 files changed:
tools/perf/Documentation/perf-config.txt
tools/perf/Documentation/perf-report.txt
tools/perf/builtin-report.c
tools/perf/ui/browsers/Build
tools/perf/ui/browsers/hists.c
tools/perf/ui/browsers/res_sample.c [new file with mode: 0644]
tools/perf/ui/browsers/scripts.c
tools/perf/util/hist.c
tools/perf/util/hist.h
tools/perf/util/sort.h
tools/perf/util/symbol.c
tools/perf/util/symbol_conf.h

index 86f3dcc15f8375726f0ed90d333e79e3cfadb669..2d0fb7613134e2b342069f3c804c5e8efbabd4c1 100644 (file)
@@ -584,6 +584,12 @@ llvm.*::
        llvm.opts::
                Options passed to llc.
 
+samples.*::
+
+       samples.context::
+               Define how many ns worth of time to show
+               around samples in perf report sample context browser.
+
 SEE ALSO
 --------
 linkperf:perf[1]
index 546d87221ad82e5f965861a516f55c2ff13643c9..f441baa794ce826eff34b24d07796847c4a65127 100644 (file)
@@ -461,6 +461,10 @@ include::itrace.txt[]
 --socket-filter::
        Only report the samples on the processor socket that match with this filter
 
+--samples=N::
+       Save N individual samples for each histogram entry to show context in perf
+       report tui browser.
+
 --raw-trace::
        When displaying traceevent output, do not use print fmt or plugins.
 
index 05c8dd41106cd132560dd35d62e0411d53baf176..1921aaa9cece896ca9265eeb6b8f36e097a83f8f 100644 (file)
@@ -1159,6 +1159,8 @@ int cmd_report(int argc, const char **argv)
        OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel,
                    "Enable kernel symbol demangling"),
        OPT_BOOLEAN(0, "mem-mode", &report.mem_mode, "mem access profile"),
+       OPT_INTEGER(0, "samples", &symbol_conf.res_sample,
+                   "Number of samples to save per histogram entry for individual browsing"),
        OPT_CALLBACK(0, "percent-limit", &report, "percent",
                     "Don't show entries under that percent", parse_percent_limit),
        OPT_CALLBACK(0, "percentage", NULL, "relative|absolute",
index 8fee56b465027a4776918a538c1d29fa540fa42c..fdf86f7981cab2ca62388733c3d36ac3b64494b8 100644 (file)
@@ -3,6 +3,7 @@ perf-y += hists.o
 perf-y += map.o
 perf-y += scripts.o
 perf-y += header.o
+perf-y += res_sample.o
 
 CFLAGS_annotate.o += -DENABLE_SLFUTURE_CONST
 CFLAGS_hists.o    += -DENABLE_SLFUTURE_CONST
index fb4430f8982c82c99f419e73cf12568094110ef5..3421ecbdd3f046c42e4b5ef578abf11bfb591b64 100644 (file)
@@ -1226,6 +1226,8 @@ void hist_browser__init_hpp(void)
                                hist_browser__hpp_color_overhead_guest_us;
        perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
                                hist_browser__hpp_color_overhead_acc;
+
+       res_sample_init();
 }
 
 static int hist_browser__show_entry(struct hist_browser *browser,
@@ -2345,6 +2347,7 @@ struct popup_action {
        struct map_symbol       ms;
        int                     socket;
        struct perf_evsel       *evsel;
+       enum rstype             rstype;
 
        int (*fn)(struct hist_browser *browser, struct popup_action *act);
 };
@@ -2572,6 +2575,17 @@ do_run_script(struct hist_browser *browser __maybe_unused,
        return 0;
 }
 
+static int
+do_res_sample_script(struct hist_browser *browser __maybe_unused,
+                    struct popup_action *act)
+{
+       struct hist_entry *he;
+
+       he = hist_browser__selected_entry(browser);
+       res_sample_browse(he->res_samples, he->num_res, act->evsel, act->rstype);
+       return 0;
+}
+
 static int
 add_script_opt_2(struct hist_browser *browser __maybe_unused,
               struct popup_action *act, char **optstr,
@@ -2629,6 +2643,27 @@ add_script_opt(struct hist_browser *browser,
        return n;
 }
 
+static int
+add_res_sample_opt(struct hist_browser *browser __maybe_unused,
+                  struct popup_action *act, char **optstr,
+                  struct res_sample *res_sample,
+                  struct perf_evsel *evsel,
+                  enum rstype type)
+{
+       if (!res_sample)
+               return 0;
+
+       if (asprintf(optstr, "Show context for individual samples %s",
+               type == A_ASM ? "with assembler" :
+               type == A_SOURCE ? "with source" : "") < 0)
+               return 0;
+
+       act->fn = do_res_sample_script;
+       act->evsel = evsel;
+       act->rstype = type;
+       return 1;
+}
+
 static int
 do_switch_data(struct hist_browser *browser __maybe_unused,
               struct popup_action *act __maybe_unused)
@@ -3115,6 +3150,18 @@ skip_annotation:
                }
                nr_options += add_script_opt(browser, &actions[nr_options],
                                             &options[nr_options], NULL, NULL, evsel);
+               nr_options += add_res_sample_opt(browser, &actions[nr_options],
+                                                &options[nr_options],
+                                hist_browser__selected_entry(browser)->res_samples,
+                                evsel, A_NORMAL);
+               nr_options += add_res_sample_opt(browser, &actions[nr_options],
+                                                &options[nr_options],
+                                hist_browser__selected_entry(browser)->res_samples,
+                                evsel, A_ASM);
+               nr_options += add_res_sample_opt(browser, &actions[nr_options],
+                                                &options[nr_options],
+                                hist_browser__selected_entry(browser)->res_samples,
+                                evsel, A_SOURCE);
                nr_options += add_switch_opt(browser, &actions[nr_options],
                                             &options[nr_options]);
 skip_scripting:
diff --git a/tools/perf/ui/browsers/res_sample.c b/tools/perf/ui/browsers/res_sample.c
new file mode 100644 (file)
index 0000000..884ef2a
--- /dev/null
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Display a menu with individual samples to browse with perf script */
+#include "util.h"
+#include "hist.h"
+#include "evsel.h"
+#include "hists.h"
+#include "sort.h"
+#include "config.h"
+#include "time-utils.h"
+#include <linux/time64.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 perf_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];
+
+       /* For now since ui__popup_menu doesn't like lists that don't fit */
+       num_res = max(min(SLtt_Screen_Rows - 4, num_res), 0);
+
+       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)
+                               free(names[i]);
+                       free(names);
+                       return -1;
+               }
+       }
+       choice = ui__popup_menu(num_res, names);
+       for (i = 0; i < num_res; i++)
+               free(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->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 +insn --xed" :
+                    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;
+}
index 9e5f87558af65bf792ae5e3774bdaec38c5df24b..cdba58447b855e0332267232b5a2ad34e76a266b 100644 (file)
@@ -125,7 +125,7 @@ out:
        return ret;
 }
 
-static void run_script(char *cmd)
+void run_script(char *cmd)
 {
        pr_debug("Running %s\n", cmd);
        SLang_reset_tty();
index 34c0f00c68d1aec8c8f4379ce544270cd6864033..1f230285d78aeeafbb776713b634c2ea00f60bee 100644 (file)
@@ -436,6 +436,13 @@ static int hist_entry__init(struct hist_entry *he,
                        goto err_rawdata;
        }
 
+       if (symbol_conf.res_sample) {
+               he->res_samples = calloc(sizeof(struct res_sample),
+                                       symbol_conf.res_sample);
+               if (!he->res_samples)
+                       goto err_srcline;
+       }
+
        INIT_LIST_HEAD(&he->pairs.node);
        thread__get(he->thread);
        he->hroot_in  = RB_ROOT_CACHED;
@@ -446,6 +453,9 @@ static int hist_entry__init(struct hist_entry *he,
 
        return 0;
 
+err_srcline:
+       free(he->srcline);
+
 err_rawdata:
        free(he->raw_data);
 
@@ -603,6 +613,32 @@ out:
        return he;
 }
 
+static unsigned random_max(unsigned high)
+{
+       unsigned thresh = -high % high;
+       for (;;) {
+               unsigned r = random();
+               if (r >= thresh)
+                       return r % high;
+       }
+}
+
+static void hists__res_sample(struct hist_entry *he, struct perf_sample *sample)
+{
+       struct res_sample *r;
+       int j;
+
+       if (he->num_res < symbol_conf.res_sample) {
+               j = he->num_res++;
+       } else {
+               j = random_max(symbol_conf.res_sample);
+       }
+       r = &he->res_samples[j];
+       r->time = sample->time;
+       r->cpu = sample->cpu;
+       r->tid = sample->tid;
+}
+
 static struct hist_entry*
 __hists__add_entry(struct hists *hists,
                   struct addr_location *al,
@@ -650,6 +686,8 @@ __hists__add_entry(struct hists *hists,
 
        if (!hists->has_callchains && he && he->callchain_size != 0)
                hists->has_callchains = true;
+       if (he && symbol_conf.res_sample)
+               hists__res_sample(he, sample);
        return he;
 }
 
@@ -1173,6 +1211,7 @@ void hist_entry__delete(struct hist_entry *he)
                mem_info__zput(he->mem_info);
        }
 
+       zfree(&he->res_samples);
        zfree(&he->stat_acc);
        free_srcline(he->srcline);
        if (he->srcfile && he->srcfile[0])
index 2113a6639cead16ac5453243d97f47d8291760db..76ff6c6d03b82f334ea8aa3528d8d817f164a066 100644 (file)
@@ -433,6 +433,13 @@ struct hist_browser_timer {
 };
 
 struct annotation_options;
+struct res_sample;
+
+enum rstype {
+       A_NORMAL,
+       A_ASM,
+       A_SOURCE
+};
 
 #ifdef HAVE_SLANG_SUPPORT
 #include "../ui/keysyms.h"
@@ -454,6 +461,11 @@ int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
                                  struct annotation_options *annotation_options);
 
 int script_browse(const char *script_opt, struct perf_evsel *evsel);
+
+void run_script(char *cmd);
+int res_sample_browse(struct res_sample *res_samples, int num_res,
+                     struct perf_evsel *evsel, enum rstype rstype);
+void res_sample_init(void);
 #else
 static inline
 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist __maybe_unused,
@@ -488,6 +500,16 @@ static inline int script_browse(const char *script_opt __maybe_unused,
        return 0;
 }
 
+static inline int res_sample_browse(struct res_sample *res_samples __maybe_unused,
+                                   int num_res __maybe_unused,
+                                   struct perf_evsel *evsel __maybe_unused,
+                                   enum rstype rstype __maybe_unused)
+{
+       return 0;
+}
+
+static inline void res_sample_init(void) {}
+
 #define K_LEFT  -1000
 #define K_RIGHT -2000
 #define K_SWITCH_INPUT_DATA -3000
index 19dceb7f61450ed8367707bf3357dbead19095dc..bb9442ab7a0c3084bc23b8634cc8ac735e27c338 100644 (file)
@@ -47,6 +47,12 @@ extern struct sort_entry sort_srcline;
 extern enum sort_type sort__first_dimension;
 extern const char default_mem_sort_order[];
 
+struct res_sample {
+       u64 time;
+       int cpu;
+       int tid;
+};
+
 struct he_stat {
        u64                     period;
        u64                     period_sys;
@@ -140,6 +146,8 @@ struct hist_entry {
        struct mem_info         *mem_info;
        void                    *raw_data;
        u32                     raw_size;
+       int                     num_res;
+       struct res_sample       *res_samples;
        void                    *trace_output;
        struct perf_hpp_list    *hpp_list;
        struct hist_entry       *parent_he;
index 6b73a0eeb6a1aaa00d7d1ed67e0dc066979c1694..58442ca5e3c4bac153d130b915e76f82695abb7c 100644 (file)
@@ -51,6 +51,7 @@ struct symbol_conf symbol_conf = {
        .symfs                  = "",
        .event_group            = true,
        .inline_name            = true,
+       .res_sample             = 0,
 };
 
 static enum dso_binary_type binary_type_symtab[] = {
index a5684a71b78e42da2c827c8125d7534ebb172ca9..6c55fa6fccec151fce220bf0d0489a6e3dfca606 100644 (file)
@@ -68,6 +68,7 @@ struct symbol_conf {
        struct intlist  *pid_list,
                        *tid_list;
        const char      *symfs;
+       int             res_sample;
 };
 
 extern struct symbol_conf symbol_conf;