aboutsummaryrefslogtreecommitdiff
path: root/tools/perf/builtin-top.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/builtin-top.c')
-rw-r--r--tools/perf/builtin-top.c1504
1 files changed, 1004 insertions, 500 deletions
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index cf0d21f1ae1..377971dc89a 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -5,6 +5,7 @@
* any workload, CPU or specific PID.
*
* Copyright (C) 2008, Red Hat Inc, Ingo Molnar <mingo@redhat.com>
+ * 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
*
* Improvements and fixes by:
*
@@ -20,22 +21,41 @@
#include "perf.h"
-#include "util/symbol.h"
+#include "util/annotate.h"
+#include "util/cache.h"
#include "util/color.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/machine.h"
+#include "util/session.h"
+#include "util/symbol.h"
+#include "util/thread.h"
+#include "util/thread_map.h"
+#include "util/top.h"
#include "util/util.h"
-#include "util/rbtree.h"
+#include <linux/rbtree.h>
#include "util/parse-options.h"
#include "util/parse-events.h"
+#include "util/cpumap.h"
+#include "util/xyarray.h"
+#include "util/sort.h"
+#include "util/intlist.h"
+#include "arch/common.h"
+
+#include "util/debug.h"
#include <assert.h>
+#include <elf.h>
#include <fcntl.h>
#include <stdio.h>
+#include <termios.h>
+#include <unistd.h>
+#include <inttypes.h>
#include <errno.h>
#include <time.h>
#include <sched.h>
-#include <pthread.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
@@ -43,695 +63,1179 @@
#include <sys/prctl.h>
#include <sys/wait.h>
#include <sys/uio.h>
+#include <sys/utsname.h>
#include <sys/mman.h>
#include <linux/unistd.h>
#include <linux/types.h>
-static int fd[MAX_NR_CPUS][MAX_COUNTERS];
-
-static int system_wide = 0;
+static volatile int done;
-static int default_interval = 100000;
+#define HEADER_LINE_NR 5
-static u64 count_filter = 5;
-static int print_entries = 15;
-
-static int target_pid = -1;
-static int profile_cpu = -1;
-static int nr_cpus = 0;
-static unsigned int realtime_prio = 0;
-static int group = 0;
-static unsigned int page_size;
-static unsigned int mmap_pages = 16;
-static int freq = 0;
-static int verbose = 0;
-
-static char *sym_filter;
-static unsigned long filter_start;
-static unsigned long filter_end;
+static void perf_top__update_print_entries(struct perf_top *top)
+{
+ top->print_entries = top->winsize.ws_row - HEADER_LINE_NR;
+}
-static int delay_secs = 2;
-static int zero;
-static int dump_symtab;
+static void perf_top__sig_winch(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused, void *arg)
+{
+ struct perf_top *top = arg;
-/*
- * Symbols
- */
+ get_term_dimensions(&top->winsize);
+ perf_top__update_print_entries(top);
+}
-static u64 min_ip;
-static u64 max_ip = -1ll;
+static int perf_top__parse_source(struct perf_top *top, struct hist_entry *he)
+{
+ struct symbol *sym;
+ struct annotation *notes;
+ struct map *map;
+ int err = -1;
-struct sym_entry {
- struct rb_node rb_node;
- struct list_head node;
- unsigned long count[MAX_COUNTERS];
- unsigned long snap_count;
- double weight;
- int skip;
-};
+ if (!he || !he->ms.sym)
+ return -1;
-struct sym_entry *sym_filter_entry;
+ sym = he->ms.sym;
+ map = he->ms.map;
-struct dso *kernel_dso;
+ /*
+ * We can't annotate with just /proc/kallsyms
+ */
+ if (map->dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS &&
+ !dso__is_kcore(map->dso)) {
+ pr_err("Can't annotate %s: No vmlinux file was found in the "
+ "path\n", sym->name);
+ sleep(1);
+ return -1;
+ }
-/*
- * Symbols will be added here in record_ip and will get out
- * after decayed.
- */
-static LIST_HEAD(active_symbols);
-static pthread_mutex_t active_symbols_lock = PTHREAD_MUTEX_INITIALIZER;
+ notes = symbol__annotation(sym);
+ if (notes->src != NULL) {
+ pthread_mutex_lock(&notes->lock);
+ goto out_assign;
+ }
-/*
- * Ordering weight: count-1 * count-2 * ... / count-n
- */
-static double sym_weight(const struct sym_entry *sym)
-{
- double weight = sym->snap_count;
- int counter;
+ pthread_mutex_lock(&notes->lock);
- for (counter = 1; counter < nr_counters-1; counter++)
- weight *= sym->count[counter];
+ if (symbol__alloc_hist(sym) < 0) {
+ pthread_mutex_unlock(&notes->lock);
+ pr_err("Not enough memory for annotating '%s' symbol!\n",
+ sym->name);
+ sleep(1);
+ return err;
+ }
- weight /= (sym->count[counter] + 1);
+ err = symbol__annotate(sym, map, 0);
+ if (err == 0) {
+out_assign:
+ top->sym_filter_entry = he;
+ }
- return weight;
+ pthread_mutex_unlock(&notes->lock);
+ return err;
}
-static long samples;
-static long userspace_samples;
-static const char CONSOLE_CLEAR[] = "";
-
-static void __list_insert_active_sym(struct sym_entry *syme)
+static void __zero_source_counters(struct hist_entry *he)
{
- list_add(&syme->node, &active_symbols);
+ struct symbol *sym = he->ms.sym;
+ symbol__annotate_zero_histograms(sym);
}
-static void list_remove_active_sym(struct sym_entry *syme)
+static void ui__warn_map_erange(struct map *map, struct symbol *sym, u64 ip)
{
- pthread_mutex_lock(&active_symbols_lock);
- list_del_init(&syme->node);
- pthread_mutex_unlock(&active_symbols_lock);
+ struct utsname uts;
+ int err = uname(&uts);
+
+ ui__warning("Out of bounds address found:\n\n"
+ "Addr: %" PRIx64 "\n"
+ "DSO: %s %c\n"
+ "Map: %" PRIx64 "-%" PRIx64 "\n"
+ "Symbol: %" PRIx64 "-%" PRIx64 " %c %s\n"
+ "Arch: %s\n"
+ "Kernel: %s\n"
+ "Tools: %s\n\n"
+ "Not all samples will be on the annotation output.\n\n"
+ "Please report to linux-kernel@vger.kernel.org\n",
+ ip, map->dso->long_name, dso__symtab_origin(map->dso),
+ map->start, map->end, sym->start, sym->end,
+ sym->binding == STB_GLOBAL ? 'g' :
+ sym->binding == STB_LOCAL ? 'l' : 'w', sym->name,
+ err ? "[unknown]" : uts.machine,
+ err ? "[unknown]" : uts.release, perf_version_string);
+ if (use_browser <= 0)
+ sleep(5);
+
+ map->erange_warned = true;
}
-static void rb_insert_active_sym(struct rb_root *tree, struct sym_entry *se)
+static void perf_top__record_precise_ip(struct perf_top *top,
+ struct hist_entry *he,
+ int counter, u64 ip)
{
- struct rb_node **p = &tree->rb_node;
- struct rb_node *parent = NULL;
- struct sym_entry *iter;
+ struct annotation *notes;
+ struct symbol *sym;
+ int err = 0;
+
+ if (he == NULL || he->ms.sym == NULL ||
+ ((top->sym_filter_entry == NULL ||
+ top->sym_filter_entry->ms.sym != he->ms.sym) && use_browser != 1))
+ return;
- while (*p != NULL) {
- parent = *p;
- iter = rb_entry(parent, struct sym_entry, rb_node);
+ sym = he->ms.sym;
+ notes = symbol__annotation(sym);
- if (se->weight > iter->weight)
- p = &(*p)->rb_left;
- else
- p = &(*p)->rb_right;
- }
+ if (pthread_mutex_trylock(&notes->lock))
+ return;
- rb_link_node(&se->rb_node, parent, p);
- rb_insert_color(&se->rb_node, tree);
-}
+ ip = he->ms.map->map_ip(he->ms.map, ip);
-static void print_sym_table(void)
-{
- int printed = 0, j;
- int counter;
- float samples_per_sec = samples/delay_secs;
- float ksamples_per_sec = (samples-userspace_samples)/delay_secs;
- float sum_ksamples = 0.0;
- struct sym_entry *syme, *n;
- struct rb_root tmp = RB_ROOT;
- struct rb_node *nd;
-
- samples = userspace_samples = 0;
-
- /* Sort the active symbols */
- pthread_mutex_lock(&active_symbols_lock);
- syme = list_entry(active_symbols.next, struct sym_entry, node);
- pthread_mutex_unlock(&active_symbols_lock);
-
- list_for_each_entry_safe_from(syme, n, &active_symbols, node) {
- syme->snap_count = syme->count[0];
- if (syme->snap_count != 0) {
- syme->weight = sym_weight(syme);
- rb_insert_active_sym(&tmp, syme);
- sum_ksamples += syme->snap_count;
-
- for (j = 0; j < nr_counters; j++)
- syme->count[j] = zero ? 0 : syme->count[j] * 7 / 8;
- } else
- list_remove_active_sym(syme);
- }
+ if (ui__has_annotation())
+ err = hist_entry__inc_addr_samples(he, counter, ip);
- puts(CONSOLE_CLEAR);
+ pthread_mutex_unlock(&notes->lock);
- printf(
-"------------------------------------------------------------------------------\n");
- printf( " PerfTop:%8.0f irqs/sec kernel:%4.1f%% [",
- samples_per_sec,
- 100.0 - (100.0*((samples_per_sec-ksamples_per_sec)/samples_per_sec)));
+ /*
+ * This function is now called with he->hists->lock held.
+ * Release it before going to sleep.
+ */
+ pthread_mutex_unlock(&he->hists->lock);
- if (nr_counters == 1) {
- printf("%Ld", (u64)attrs[0].sample_period);
- if (freq)
- printf("Hz ");
- else
- printf(" ");
+ if (err == -ERANGE && !he->ms.map->erange_warned)
+ ui__warn_map_erange(he->ms.map, sym, ip);
+ else if (err == -ENOMEM) {
+ pr_err("Not enough memory for annotating '%s' symbol!\n",
+ sym->name);
+ sleep(1);
}
- for (counter = 0; counter < nr_counters; counter++) {
- if (counter)
- printf("/");
+ pthread_mutex_lock(&he->hists->lock);
+}
- printf("%s", event_name(counter));
- }
+static void perf_top__show_details(struct perf_top *top)
+{
+ struct hist_entry *he = top->sym_filter_entry;
+ struct annotation *notes;
+ struct symbol *symbol;
+ int more;
- printf( "], ");
+ if (!he)
+ return;
- if (target_pid != -1)
- printf(" (target_pid: %d", target_pid);
- else
- printf(" (all");
+ symbol = he->ms.sym;
+ notes = symbol__annotation(symbol);
- if (profile_cpu != -1)
- printf(", cpu: %d)\n", profile_cpu);
- else {
- if (target_pid != -1)
- printf(")\n");
- else
- printf(", %d CPUs)\n", nr_cpus);
- }
+ pthread_mutex_lock(&notes->lock);
+
+ if (notes->src == NULL)
+ goto out_unlock;
- printf("------------------------------------------------------------------------------\n\n");
+ printf("Showing %s for %s\n", perf_evsel__name(top->sym_evsel), symbol->name);
+ printf(" Events Pcnt (>=%d%%)\n", top->sym_pcnt_filter);
- if (nr_counters == 1)
- printf(" samples pcnt");
+ more = symbol__annotate_printf(symbol, he->ms.map, top->sym_evsel,
+ 0, top->sym_pcnt_filter, top->print_entries, 4);
+ if (top->zero)
+ symbol__annotate_zero_histogram(symbol, top->sym_evsel->idx);
else
- printf(" weight samples pcnt");
+ symbol__annotate_decay_histogram(symbol, top->sym_evsel->idx);
+ if (more != 0)
+ printf("%d lines not displayed, maybe increase display entries [e]\n", more);
+out_unlock:
+ pthread_mutex_unlock(&notes->lock);
+}
- printf(" RIP kernel function\n"
- " ______ _______ _____ ________________ _______________\n\n"
- );
+static void perf_top__print_sym_table(struct perf_top *top)
+{
+ char bf[160];
+ int printed = 0;
+ const int win_width = top->winsize.ws_col - 1;
- for (nd = rb_first(&tmp); nd; nd = rb_next(nd)) {
- struct sym_entry *syme = rb_entry(nd, struct sym_entry, rb_node);
- struct symbol *sym = (struct symbol *)(syme + 1);
- char *color = PERF_COLOR_NORMAL;
- double pcnt;
+ puts(CONSOLE_CLEAR);
- if (++printed > print_entries || syme->snap_count < count_filter)
- continue;
+ perf_top__header_snprintf(top, bf, sizeof(bf));
+ printf("%s\n", bf);
- pcnt = 100.0 - (100.0 * ((sum_ksamples - syme->snap_count) /
- sum_ksamples));
+ perf_top__reset_sample_counters(top);
- /*
- * We color high-overhead entries in red, mid-overhead
- * entries in green - and keep the low overhead places
- * normal:
- */
- if (pcnt >= 5.0) {
- color = PERF_COLOR_RED;
- } else {
- if (pcnt >= 0.5)
- color = PERF_COLOR_GREEN;
- }
+ printf("%-*.*s\n", win_width, win_width, graph_dotted_line);
- if (nr_counters == 1)
- printf("%20.2f - ", syme->weight);
- else
- printf("%9.1f %10ld - ", syme->weight, syme->snap_count);
+ if (top->sym_evsel->hists.stats.nr_lost_warned !=
+ top->sym_evsel->hists.stats.nr_events[PERF_RECORD_LOST]) {
+ top->sym_evsel->hists.stats.nr_lost_warned =
+ top->sym_evsel->hists.stats.nr_events[PERF_RECORD_LOST];
+ color_fprintf(stdout, PERF_COLOR_RED,
+ "WARNING: LOST %d chunks, Check IO/CPU overload",
+ top->sym_evsel->hists.stats.nr_lost_warned);
+ ++printed;
+ }
- color_fprintf(stdout, color, "%4.1f%%", pcnt);
- printf(" - %016llx : %s\n", sym->start, sym->name);
+ if (top->sym_filter_entry) {
+ perf_top__show_details(top);
+ return;
}
+
+ hists__collapse_resort(&top->sym_evsel->hists, NULL);
+ hists__output_resort(&top->sym_evsel->hists);
+ hists__decay_entries(&top->sym_evsel->hists,
+ top->hide_user_symbols,
+ top->hide_kernel_symbols);
+ hists__output_recalc_col_len(&top->sym_evsel->hists,
+ top->print_entries - printed);
+ putchar('\n');
+ hists__fprintf(&top->sym_evsel->hists, false,
+ top->print_entries - printed, win_width,
+ top->min_percent, stdout);
}
-static void *display_thread(void *arg)
+static void prompt_integer(int *target, const char *msg)
{
- struct pollfd stdin_poll = { .fd = 0, .events = POLLIN };
- int delay_msecs = delay_secs * 1000;
+ char *buf = malloc(0), *p;
+ size_t dummy = 0;
+ int tmp;
- printf("PerfTop refresh period: %d seconds\n", delay_secs);
-
- do {
- print_sym_table();
- } while (!poll(&stdin_poll, 1, delay_msecs) == 1);
+ fprintf(stdout, "\n%s: ", msg);
+ if (getline(&buf, &dummy, stdin) < 0)
+ return;
- printf("key pressed - exiting.\n");
- exit(0);
+ p = strchr(buf, '\n');
+ if (p)
+ *p = 0;
- return NULL;
+ p = buf;
+ while(*p) {
+ if (!isdigit(*p))
+ goto out_free;
+ p++;
+ }
+ tmp = strtoul(buf, NULL, 10);
+ *target = tmp;
+out_free:
+ free(buf);
}
-static int symbol_filter(struct dso *self, struct symbol *sym)
+static void prompt_percent(int *target, const char *msg)
{
- static int filter_match;
- struct sym_entry *syme;
- const char *name = sym->name;
+ int tmp = 0;
- if (!strcmp(name, "_text") ||
- !strcmp(name, "_etext") ||
- !strcmp(name, "_sinittext") ||
- !strncmp("init_module", name, 11) ||
- !strncmp("cleanup_module", name, 14) ||
- strstr(name, "_text_start") ||
- strstr(name, "_text_end"))
- return 1;
+ prompt_integer(&tmp, msg);
+ if (tmp >= 0 && tmp <= 100)
+ *target = tmp;
+}
- syme = dso__sym_priv(self, sym);
- /* Tag samples to be skipped. */
- if (!strcmp("default_idle", name) ||
- !strcmp("cpu_idle", name) ||
- !strcmp("enter_idle", name) ||
- !strcmp("exit_idle", name) ||
- !strcmp("mwait_idle", name))
- syme->skip = 1;
-
- if (filter_match == 1) {
- filter_end = sym->start;
- filter_match = -1;
- if (filter_end - filter_start > 10000) {
- fprintf(stderr,
- "hm, too large filter symbol <%s> - skipping.\n",
- sym_filter);
- fprintf(stderr, "symbol filter start: %016lx\n",
- filter_start);
- fprintf(stderr, " end: %016lx\n",
- filter_end);
- filter_end = filter_start = 0;
- sym_filter = NULL;
- sleep(1);
- }
+static void perf_top__prompt_symbol(struct perf_top *top, const char *msg)
+{
+ char *buf = malloc(0), *p;
+ struct hist_entry *syme = top->sym_filter_entry, *n, *found = NULL;
+ struct rb_node *next;
+ size_t dummy = 0;
+
+ /* zero counters of active symbol */
+ if (syme) {
+ __zero_source_counters(syme);
+ top->sym_filter_entry = NULL;
}
- if (filter_match == 0 && sym_filter && !strcmp(name, sym_filter)) {
- filter_match = 1;
- filter_start = sym->start;
+ fprintf(stdout, "\n%s: ", msg);
+ if (getline(&buf, &dummy, stdin) < 0)
+ goto out_free;
+
+ p = strchr(buf, '\n');
+ if (p)
+ *p = 0;
+
+ next = rb_first(&top->sym_evsel->hists.entries);
+ while (next) {
+ n = rb_entry(next, struct hist_entry, rb_node);
+ if (n->ms.sym && !strcmp(buf, n->ms.sym->name)) {
+ found = n;
+ break;
+ }
+ next = rb_next(&n->rb_node);
}
+ if (!found) {
+ fprintf(stderr, "Sorry, %s is not active.\n", buf);
+ sleep(1);
+ } else
+ perf_top__parse_source(top, found);
- return 0;
+out_free:
+ free(buf);
}
-static int parse_symbols(void)
+static void perf_top__print_mapped_keys(struct perf_top *top)
{
- struct rb_node *node;
- struct symbol *sym;
-
- kernel_dso = dso__new("[kernel]", sizeof(struct sym_entry));
- if (kernel_dso == NULL)
- return -1;
+ char *name = NULL;
- if (dso__load_kernel(kernel_dso, NULL, symbol_filter, 1) != 0)
- goto out_delete_dso;
+ if (top->sym_filter_entry) {
+ struct symbol *sym = top->sym_filter_entry->ms.sym;
+ name = sym->name;
+ }
- node = rb_first(&kernel_dso->syms);
- sym = rb_entry(node, struct symbol, rb_node);
- min_ip = sym->start;
+ fprintf(stdout, "\nMapped keys:\n");
+ fprintf(stdout, "\t[d] display refresh delay. \t(%d)\n", top->delay_secs);
+ fprintf(stdout, "\t[e] display entries (lines). \t(%d)\n", top->print_entries);
- node = rb_last(&kernel_dso->syms);
- sym = rb_entry(node, struct symbol, rb_node);
- max_ip = sym->end;
+ if (top->evlist->nr_entries > 1)
+ fprintf(stdout, "\t[E] active event counter. \t(%s)\n", perf_evsel__name(top->sym_evsel));
- if (dump_symtab)
- dso__fprintf(kernel_dso, stderr);
+ fprintf(stdout, "\t[f] profile display filter (count). \t(%d)\n", top->count_filter);
- return 0;
+ fprintf(stdout, "\t[F] annotate display filter (percent). \t(%d%%)\n", top->sym_pcnt_filter);
+ fprintf(stdout, "\t[s] annotate symbol. \t(%s)\n", name?: "NULL");
+ fprintf(stdout, "\t[S] stop annotation.\n");
-out_delete_dso:
- dso__delete(kernel_dso);
- kernel_dso = NULL;
- return -1;
+ fprintf(stdout,
+ "\t[K] hide kernel_symbols symbols. \t(%s)\n",
+ top->hide_kernel_symbols ? "yes" : "no");
+ fprintf(stdout,
+ "\t[U] hide user symbols. \t(%s)\n",
+ top->hide_user_symbols ? "yes" : "no");
+ fprintf(stdout, "\t[z] toggle sample zeroing. \t(%d)\n", top->zero ? 1 : 0);
+ fprintf(stdout, "\t[qQ] quit.\n");
}
-#define TRACE_COUNT 3
-
-/*
- * Binary search in the histogram table and record the hit:
- */
-static void record_ip(u64 ip, int counter)
+static int perf_top__key_mapped(struct perf_top *top, int c)
{
- struct symbol *sym = dso__find_symbol(kernel_dso, ip);
-
- if (sym != NULL) {
- struct sym_entry *syme = dso__sym_priv(kernel_dso, sym);
-
- if (!syme->skip) {
- syme->count[counter]++;
- pthread_mutex_lock(&active_symbols_lock);
- if (list_empty(&syme->node) || !syme->node.next)
- __list_insert_active_sym(syme);
- pthread_mutex_unlock(&active_symbols_lock);
- return;
- }
+ switch (c) {
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'z':
+ case 'q':
+ case 'Q':
+ case 'K':
+ case 'U':
+ case 'F':
+ case 's':
+ case 'S':
+ return 1;
+ case 'E':
+ return top->evlist->nr_entries > 1 ? 1 : 0;
+ default:
+ break;
}
- samples--;
+ return 0;
}
-static void process_event(u64 ip, int counter, int user)
+static bool perf_top__handle_keypress(struct perf_top *top, int c)
{
- samples++;
+ bool ret = true;
- if (user) {
- userspace_samples++;
- return;
+ if (!perf_top__key_mapped(top, c)) {
+ struct pollfd stdin_poll = { .fd = 0, .events = POLLIN };
+ struct termios tc, save;
+
+ perf_top__print_mapped_keys(top);
+ fprintf(stdout, "\nEnter selection, or unmapped key to continue: ");
+ fflush(stdout);
+
+ tcgetattr(0, &save);
+ tc = save;
+ tc.c_lflag &= ~(ICANON | ECHO);
+ tc.c_cc[VMIN] = 0;
+ tc.c_cc[VTIME] = 0;
+ tcsetattr(0, TCSANOW, &tc);
+
+ poll(&stdin_poll, 1, -1);
+ c = getc(stdin);
+
+ tcsetattr(0, TCSAFLUSH, &save);
+ if (!perf_top__key_mapped(top, c))
+ return ret;
}
- record_ip(ip, counter);
-}
+ switch (c) {
+ case 'd':
+ prompt_integer(&top->delay_secs, "Enter display delay");
+ if (top->delay_secs < 1)
+ top->delay_secs = 1;
+ break;
+ case 'e':
+ prompt_integer(&top->print_entries, "Enter display entries (lines)");
+ if (top->print_entries == 0) {
+ struct sigaction act = {
+ .sa_sigaction = perf_top__sig_winch,
+ .sa_flags = SA_SIGINFO,
+ };
+ perf_top__sig_winch(SIGWINCH, NULL, top);
+ sigaction(SIGWINCH, &act, NULL);
+ } else {
+ signal(SIGWINCH, SIG_DFL);
+ }
+ break;
+ case 'E':
+ if (top->evlist->nr_entries > 1) {
+ /* Select 0 as the default event: */
+ int counter = 0;
+
+ fprintf(stderr, "\nAvailable events:");
+
+ evlist__for_each(top->evlist, top->sym_evsel)
+ fprintf(stderr, "\n\t%d %s", top->sym_evsel->idx, perf_evsel__name(top->sym_evsel));
+
+ prompt_integer(&counter, "Enter details event counter");
+
+ if (counter >= top->evlist->nr_entries) {
+ top->sym_evsel = perf_evlist__first(top->evlist);
+ fprintf(stderr, "Sorry, no such event, using %s.\n", perf_evsel__name(top->sym_evsel));
+ sleep(1);
+ break;
+ }
+ evlist__for_each(top->evlist, top->sym_evsel)
+ if (top->sym_evsel->idx == counter)
+ break;
+ } else
+ top->sym_evsel = perf_evlist__first(top->evlist);
+ break;
+ case 'f':
+ prompt_integer(&top->count_filter, "Enter display event count filter");
+ break;
+ case 'F':
+ prompt_percent(&top->sym_pcnt_filter,
+ "Enter details display event filter (percent)");
+ break;
+ case 'K':
+ top->hide_kernel_symbols = !top->hide_kernel_symbols;
+ break;
+ case 'q':
+ case 'Q':
+ printf("exiting.\n");
+ if (top->dump_symtab)
+ perf_session__fprintf_dsos(top->session, stderr);
+ ret = false;
+ break;
+ case 's':
+ perf_top__prompt_symbol(top, "Enter details symbol");
+ break;
+ case 'S':
+ if (!top->sym_filter_entry)
+ break;
+ else {
+ struct hist_entry *syme = top->sym_filter_entry;
+
+ top->sym_filter_entry = NULL;
+ __zero_source_counters(syme);
+ }
+ break;
+ case 'U':
+ top->hide_user_symbols = !top->hide_user_symbols;
+ break;
+ case 'z':
+ top->zero = !top->zero;
+ break;
+ default:
+ break;
+ }
-struct mmap_data {
- int counter;
- void *base;
- unsigned int mask;
- unsigned int prev;
-};
+ return ret;
+}
-static unsigned int mmap_read_head(struct mmap_data *md)
+static void perf_top__sort_new_samples(void *arg)
{
- struct perf_counter_mmap_page *pc = md->base;
- int head;
+ struct perf_top *t = arg;
+ perf_top__reset_sample_counters(t);
- head = pc->data_head;
- rmb();
+ if (t->evlist->selected != NULL)
+ t->sym_evsel = t->evlist->selected;
- return head;
+ hists__collapse_resort(&t->sym_evsel->hists, NULL);
+ hists__output_resort(&t->sym_evsel->hists);
+ hists__decay_entries(&t->sym_evsel->hists,
+ t->hide_user_symbols,
+ t->hide_kernel_symbols);
}
-struct timeval last_read, this_read;
-
-static void mmap_read_counter(struct mmap_data *md)
+static void *display_thread_tui(void *arg)
{
- unsigned int head = mmap_read_head(md);
- unsigned int old = md->prev;
- unsigned char *data = md->base + page_size;
- int diff;
+ struct perf_evsel *pos;
+ struct perf_top *top = arg;
+ const char *help = "For a higher level overview, try: perf top --sort comm,dso";
+ struct hist_browser_timer hbt = {
+ .timer = perf_top__sort_new_samples,
+ .arg = top,
+ .refresh = top->delay_secs,
+ };
- gettimeofday(&this_read, NULL);
+ perf_top__sort_new_samples(top);
/*
- * If we're further behind than half the buffer, there's a chance
- * the writer will bite our tail and mess up the samples under us.
- *
- * If we somehow ended up ahead of the head, we got messed up.
- *
- * In either case, truncate and restart at head.
+ * Initialize the uid_filter_str, in the future the TUI will allow
+ * Zooming in/out UIDs. For now juse use whatever the user passed
+ * via --uid.
*/
- diff = head - old;
- if (diff > md->mask / 2 || diff < 0) {
- struct timeval iv;
- unsigned long msecs;
+ evlist__for_each(top->evlist, pos)
+ pos->hists.uid_filter_str = top->record_opts.target.uid_str;
- timersub(&this_read, &last_read, &iv);
- msecs = iv.tv_sec*1000 + iv.tv_usec/1000;
+ perf_evlist__tui_browse_hists(top->evlist, help, &hbt, top->min_percent,
+ &top->session->header.env);
- fprintf(stderr, "WARNING: failed to keep up with mmap data."
- " Last read %lu msecs ago.\n", msecs);
+ done = 1;
+ return NULL;
+}
+static void *display_thread(void *arg)
+{
+ struct pollfd stdin_poll = { .fd = 0, .events = POLLIN };
+ struct termios tc, save;
+ struct perf_top *top = arg;
+ int delay_msecs, c;
+
+ tcgetattr(0, &save);
+ tc = save;
+ tc.c_lflag &= ~(ICANON | ECHO);
+ tc.c_cc[VMIN] = 0;
+ tc.c_cc[VTIME] = 0;
+
+ pthread__unblock_sigwinch();
+repeat:
+ delay_msecs = top->delay_secs * 1000;
+ tcsetattr(0, TCSANOW, &tc);
+ /* trash return*/
+ getc(stdin);
+
+ while (!done) {
+ perf_top__print_sym_table(top);
/*
- * head points to a known good entry, start there.
+ * Either timeout expired or we got an EINTR due to SIGWINCH,
+ * refresh screen in both cases.
*/
- old = head;
+ switch (poll(&stdin_poll, 1, delay_msecs)) {
+ case 0:
+ continue;
+ case -1:
+ if (errno == EINTR)
+ continue;
+ /* Fall trhu */
+ default:
+ c = getc(stdin);
+ tcsetattr(0, TCSAFLUSH, &save);
+
+ if (perf_top__handle_keypress(top, c))
+ goto repeat;
+ done = 1;
+ }
}
- last_read = this_read;
+ return NULL;
+}
- for (; old != head;) {
- struct ip_event {
- struct perf_event_header header;
- u64 ip;
- u32 pid, target_pid;
- };
- struct mmap_event {
- struct perf_event_header header;
- u32 pid, target_pid;
- u64 start;
- u64 len;
- u64 pgoff;
- char filename[PATH_MAX];
- };
+static int symbol_filter(struct map *map __maybe_unused, struct symbol *sym)
+{
+ const char *name = sym->name;
- typedef union event_union {
- struct perf_event_header header;
- struct ip_event ip;
- struct mmap_event mmap;
- } event_t;
+ /*
+ * ppc64 uses function descriptors and appends a '.' to the
+ * start of every instruction address. Remove it.
+ */
+ if (name[0] == '.')
+ name++;
+
+ if (!strcmp(name, "_text") ||
+ !strcmp(name, "_etext") ||
+ !strcmp(name, "_sinittext") ||
+ !strncmp("init_module", name, 11) ||
+ !strncmp("cleanup_module", name, 14) ||
+ strstr(name, "_text_start") ||
+ strstr(name, "_text_end"))
+ return 1;
+
+ if (symbol__is_idle(sym))
+ sym->ignore = true;
+
+ return 0;
+}
+
+static int hist_iter__top_callback(struct hist_entry_iter *iter,
+ struct addr_location *al, bool single,
+ void *arg)
+{
+ struct perf_top *top = arg;
+ struct hist_entry *he = iter->he;
+ struct perf_evsel *evsel = iter->evsel;
+
+ if (sort__has_sym && single) {
+ u64 ip = al->addr;
+
+ if (al->map)
+ ip = al->map->unmap_ip(al->map, ip);
+
+ perf_top__record_precise_ip(top, he, evsel->idx, ip);
+ }
+
+ return 0;
+}
+
+static void perf_event__process_sample(struct perf_tool *tool,
+ const union perf_event *event,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct perf_top *top = container_of(tool, struct perf_top, tool);
+ struct addr_location al;
+ int err;
+
+ if (!machine && perf_guest) {
+ static struct intlist *seen;
+
+ if (!seen)
+ seen = intlist__new(NULL);
+
+ if (!intlist__has_entry(seen, sample->pid)) {
+ pr_err("Can't find guest [%d]'s kernel information\n",
+ sample->pid);
+ intlist__add(seen, sample->pid);
+ }
+ return;
+ }
+
+ if (!machine) {
+ pr_err("%u unprocessable samples recorded.\r",
+ top->session->stats.nr_unprocessable_samples++);
+ return;
+ }
- event_t *event = (event_t *)&data[old & md->mask];
+ if (event->header.misc & PERF_RECORD_MISC_EXACT_IP)
+ top->exact_samples++;
- event_t event_copy;
+ if (perf_event__preprocess_sample(event, machine, &al, sample) < 0)
+ return;
- size_t size = event->header.size;
+ if (!top->kptr_restrict_warned &&
+ symbol_conf.kptr_restrict &&
+ al.cpumode == PERF_RECORD_MISC_KERNEL) {
+ ui__warning(
+"Kernel address maps (/proc/{kallsyms,modules}) are restricted.\n\n"
+"Check /proc/sys/kernel/kptr_restrict.\n\n"
+"Kernel%s samples will not be resolved.\n",
+ !RB_EMPTY_ROOT(&al.map->dso->symbols[MAP__FUNCTION]) ?
+ " modules" : "");
+ if (use_browser <= 0)
+ sleep(5);
+ top->kptr_restrict_warned = true;
+ }
+ if (al.sym == NULL) {
+ const char *msg = "Kernel samples will not be resolved.\n";
/*
- * Event straddles the mmap boundary -- header should always
- * be inside due to u64 alignment of output.
+ * As we do lazy loading of symtabs we only will know if the
+ * specified vmlinux file is invalid when we actually have a
+ * hit in kernel space and then try to load it. So if we get
+ * here and there are _no_ symbols in the DSO backing the
+ * kernel map, bail out.
+ *
+ * We may never get here, for instance, if we use -K/
+ * --hide-kernel-symbols, even if the user specifies an
+ * invalid --vmlinux ;-)
*/
- if ((old & md->mask) + size != ((old + size) & md->mask)) {
- unsigned int offset = old;
- unsigned int len = min(sizeof(*event), size), cpy;
- void *dst = &event_copy;
-
- do {
- cpy = min(md->mask + 1 - (offset & md->mask), len);
- memcpy(dst, &data[offset & md->mask], cpy);
- offset += cpy;
- dst += cpy;
- len -= cpy;
- } while (len);
-
- event = &event_copy;
+ if (!top->kptr_restrict_warned && !top->vmlinux_warned &&
+ al.map == machine->vmlinux_maps[MAP__FUNCTION] &&
+ RB_EMPTY_ROOT(&al.map->dso->symbols[MAP__FUNCTION])) {
+ if (symbol_conf.vmlinux_name) {
+ ui__warning("The %s file can't be used.\n%s",
+ symbol_conf.vmlinux_name, msg);
+ } else {
+ ui__warning("A vmlinux file was not found.\n%s",
+ msg);
+ }
+
+ if (use_browser <= 0)
+ sleep(5);
+ top->vmlinux_warned = true;
}
+ }
- old += size;
+ if (al.sym == NULL || !al.sym->ignore) {
+ struct hist_entry_iter iter = {
+ .add_entry_cb = hist_iter__top_callback,
+ };
- if (event->header.type == PERF_EVENT_SAMPLE) {
- int user =
- (event->header.misc & PERF_EVENT_MISC_CPUMODE_MASK) == PERF_EVENT_MISC_USER;
- process_event(event->ip.ip, md->counter, user);
- }
+ if (symbol_conf.cumulate_callchain)
+ iter.ops = &hist_iter_cumulative;
+ else
+ iter.ops = &hist_iter_normal;
+
+ pthread_mutex_lock(&evsel->hists.lock);
+
+ err = hist_entry_iter__add(&iter, &al, evsel, sample,
+ top->max_stack, top);
+ if (err < 0)
+ pr_err("Problem incrementing symbol period, skipping event\n");
+
+ pthread_mutex_unlock(&evsel->hists.lock);
}
- md->prev = old;
+ return;
}
-static struct pollfd event_array[MAX_NR_CPUS * MAX_COUNTERS];
-static struct mmap_data mmap_array[MAX_NR_CPUS][MAX_COUNTERS];
-
-static void mmap_read(void)
+static void perf_top__mmap_read_idx(struct perf_top *top, int idx)
{
- int i, counter;
+ struct perf_sample sample;
+ struct perf_evsel *evsel;
+ struct perf_session *session = top->session;
+ union perf_event *event;
+ struct machine *machine;
+ u8 origin;
+ int ret;
- for (i = 0; i < nr_cpus; i++) {
- for (counter = 0; counter < nr_counters; counter++)
- mmap_read_counter(&mmap_array[i][counter]);
+ while ((event = perf_evlist__mmap_read(top->evlist, idx)) != NULL) {
+ ret = perf_evlist__parse_sample(top->evlist, event, &sample);
+ if (ret) {
+ pr_err("Can't parse sample, err = %d\n", ret);
+ goto next_event;
+ }
+
+ evsel = perf_evlist__id2evsel(session->evlist, sample.id);
+ assert(evsel != NULL);
+
+ origin = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ if (event->header.type == PERF_RECORD_SAMPLE)
+ ++top->samples;
+
+ switch (origin) {
+ case PERF_RECORD_MISC_USER:
+ ++top->us_samples;
+ if (top->hide_user_symbols)
+ goto next_event;
+ machine = &session->machines.host;
+ break;
+ case PERF_RECORD_MISC_KERNEL:
+ ++top->kernel_samples;
+ if (top->hide_kernel_symbols)
+ goto next_event;
+ machine = &session->machines.host;
+ break;
+ case PERF_RECORD_MISC_GUEST_KERNEL:
+ ++top->guest_kernel_samples;
+ machine = perf_session__find_machine(session,
+ sample.pid);
+ break;
+ case PERF_RECORD_MISC_GUEST_USER:
+ ++top->guest_us_samples;
+ /*
+ * TODO: we don't process guest user from host side
+ * except simple counting.
+ */
+ /* Fall thru */
+ default:
+ goto next_event;
+ }
+
+
+ if (event->header.type == PERF_RECORD_SAMPLE) {
+ perf_event__process_sample(&top->tool, event, evsel,
+ &sample, machine);
+ } else if (event->header.type < PERF_RECORD_MAX) {
+ hists__inc_nr_events(&evsel->hists, event->header.type);
+ machine__process_event(machine, event, &sample);
+ } else
+ ++session->stats.nr_unknown_events;
+next_event:
+ perf_evlist__mmap_consume(top->evlist, idx);
}
}
-int nr_poll;
-int group_fd;
-
-static void start_counter(int i, int counter)
+static void perf_top__mmap_read(struct perf_top *top)
{
- struct perf_counter_attr *attr;
- unsigned int cpu;
+ int i;
- cpu = profile_cpu;
- if (target_pid == -1 && profile_cpu == -1)
- cpu = i;
+ for (i = 0; i < top->evlist->nr_mmaps; i++)
+ perf_top__mmap_read_idx(top, i);
+}
- attr = attrs + counter;
+static int perf_top__start_counters(struct perf_top *top)
+{
+ char msg[512];
+ struct perf_evsel *counter;
+ struct perf_evlist *evlist = top->evlist;
+ struct record_opts *opts = &top->record_opts;
- attr->sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID;
- attr->freq = freq;
+ perf_evlist__config(evlist, opts);
+ evlist__for_each(evlist, counter) {
try_again:
- fd[i][counter] = sys_perf_counter_open(attr, target_pid, cpu, group_fd, 0);
+ if (perf_evsel__open(counter, top->evlist->cpus,
+ top->evlist->threads) < 0) {
+ if (perf_evsel__fallback(counter, errno, msg, sizeof(msg))) {
+ if (verbose)
+ ui__warning("%s\n", msg);
+ goto try_again;
+ }
+
+ perf_evsel__open_strerror(counter, &opts->target,
+ errno, msg, sizeof(msg));
+ ui__error("%s\n", msg);
+ goto out_err;
+ }
+ }
- if (fd[i][counter] < 0) {
- int err = errno;
+ if (perf_evlist__mmap(evlist, opts->mmap_pages, false) < 0) {
+ ui__error("Failed to mmap with %d (%s)\n",
+ errno, strerror(errno));
+ goto out_err;
+ }
- if (err == EPERM)
- die("No permission - are you root?\n");
- /*
- * If it's cycles then fall back to hrtimer
- * based cpu-clock-tick sw counter, which
- * is always available even if no PMU support:
- */
- if (attr->type == PERF_TYPE_HARDWARE
- && attr->config == PERF_COUNT_HW_CPU_CYCLES) {
+ return 0;
- if (verbose)
- warning(" ... trying to fall back to cpu-clock-ticks\n");
+out_err:
+ return -1;
+}
- attr->type = PERF_TYPE_SOFTWARE;
- attr->config = PERF_COUNT_SW_CPU_CLOCK;
- goto try_again;
+static int perf_top__setup_sample_type(struct perf_top *top __maybe_unused)
+{
+ if (!sort__has_sym) {
+ if (symbol_conf.use_callchain) {
+ ui__error("Selected -g but \"sym\" not present in --sort/-s.");
+ return -EINVAL;
+ }
+ } else if (callchain_param.mode != CHAIN_NONE) {
+ if (callchain_register_param(&callchain_param) < 0) {
+ ui__error("Can't register callchain params.\n");
+ return -EINVAL;
}
- printf("\n");
- error("perfcounter syscall returned with %d (%s)\n",
- fd[i][counter], strerror(err));
- die("No CONFIG_PERF_COUNTERS=y kernel support configured?\n");
- exit(-1);
}
- assert(fd[i][counter] >= 0);
- fcntl(fd[i][counter], F_SETFL, O_NONBLOCK);
- /*
- * First counter acts as the group leader:
- */
- if (group && group_fd == -1)
- group_fd = fd[i][counter];
-
- event_array[nr_poll].fd = fd[i][counter];
- event_array[nr_poll].events = POLLIN;
- nr_poll++;
-
- mmap_array[i][counter].counter = counter;
- mmap_array[i][counter].prev = 0;
- mmap_array[i][counter].mask = mmap_pages*page_size - 1;
- mmap_array[i][counter].base = mmap(NULL, (mmap_pages+1)*page_size,
- PROT_READ, MAP_SHARED, fd[i][counter], 0);
- if (mmap_array[i][counter].base == MAP_FAILED)
- die("failed to mmap with %d (%s)\n", errno, strerror(errno));
+ return 0;
}
-static int __cmd_top(void)
+static int __cmd_top(struct perf_top *top)
{
+ struct record_opts *opts = &top->record_opts;
pthread_t thread;
- int i, counter;
int ret;
- for (i = 0; i < nr_cpus; i++) {
- group_fd = -1;
- for (counter = 0; counter < nr_counters; counter++)
- start_counter(i, counter);
+ top->session = perf_session__new(NULL, false, NULL);
+ if (top->session == NULL)
+ return -ENOMEM;
+
+ machines__set_symbol_filter(&top->session->machines, symbol_filter);
+
+ if (!objdump_path) {
+ ret = perf_session_env__lookup_objdump(&top->session->header.env);
+ if (ret)
+ goto out_delete;
}
+ ret = perf_top__setup_sample_type(top);
+ if (ret)
+ goto out_delete;
+
+ machine__synthesize_threads(&top->session->machines.host, &opts->target,
+ top->evlist->threads, false);
+ ret = perf_top__start_counters(top);
+ if (ret)
+ goto out_delete;
+
+ top->session->evlist = top->evlist;
+ perf_session__set_id_hdr_size(top->session);
+
+ /*
+ * When perf is starting the traced process, all the events (apart from
+ * group members) have enable_on_exec=1 set, so don't spoil it by
+ * prematurely enabling them.
+ *
+ * XXX 'top' still doesn't start workloads like record, trace, but should,
+ * so leave the check here.
+ */
+ if (!target__none(&opts->target))
+ perf_evlist__enable(top->evlist);
+
/* Wait for a minimal set of events before starting the snapshot */
- poll(event_array, nr_poll, 100);
+ poll(top->evlist->pollfd, top->evlist->nr_fds, 100);
- mmap_read();
+ perf_top__mmap_read(top);
- if (pthread_create(&thread, NULL, display_thread, NULL)) {
- printf("Could not create display thread.\n");
- exit(-1);
+ ret = -1;
+ if (pthread_create(&thread, NULL, (use_browser > 0 ? display_thread_tui :
+ display_thread), top)) {
+ ui__error("Could not create display thread.\n");
+ goto out_delete;
}
- if (realtime_prio) {
+ if (top->realtime_prio) {
struct sched_param param;
- param.sched_priority = realtime_prio;
+ param.sched_priority = top->realtime_prio;
if (sched_setscheduler(0, SCHED_FIFO, &param)) {
- printf("Could not set realtime priority.\n");
- exit(-1);
+ ui__error("Could not set realtime priority.\n");
+ goto out_delete;
}
}
- while (1) {
- int hits = samples;
+ while (!done) {
+ u64 hits = top->samples;
- mmap_read();
+ perf_top__mmap_read(top);
- if (hits == samples)
- ret = poll(event_array, nr_poll, 100);
+ if (hits == top->samples)
+ ret = poll(top->evlist->pollfd, top->evlist->nr_fds, 100);
}
- return 0;
+ ret = 0;
+out_delete:
+ perf_session__delete(top->session);
+ top->session = NULL;
+
+ return ret;
+}
+
+static int
+callchain_opt(const struct option *opt, const char *arg, int unset)
+{
+ symbol_conf.use_callchain = true;
+ return record_callchain_opt(opt, arg, unset);
}
-static const char * const top_usage[] = {
- "perf top [<options>]",
- NULL
-};
+static int
+parse_callchain_opt(const struct option *opt, const char *arg, int unset)
+{
+ symbol_conf.use_callchain = true;
+ return record_parse_callchain_opt(opt, arg, unset);
+}
-static const struct option options[] = {
- OPT_CALLBACK('e', "event", NULL, "event",
+static int perf_top_config(const char *var, const char *value, void *cb)
+{
+ struct perf_top *top = cb;
+
+ if (!strcmp(var, "top.call-graph"))
+ return record_parse_callchain(value, &top->record_opts);
+ if (!strcmp(var, "top.children")) {
+ symbol_conf.cumulate_callchain = perf_config_bool(var, value);
+ return 0;
+ }
+
+ return perf_default_config(var, value, cb);
+}
+
+static int
+parse_percent_limit(const struct option *opt, const char *arg,
+ int unset __maybe_unused)
+{
+ struct perf_top *top = opt->value;
+
+ top->min_percent = strtof(arg, NULL);
+ return 0;
+}
+
+int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ int status = -1;
+ char errbuf[BUFSIZ];
+ struct perf_top top = {
+ .count_filter = 5,
+ .delay_secs = 2,
+ .record_opts = {
+ .mmap_pages = UINT_MAX,
+ .user_freq = UINT_MAX,
+ .user_interval = ULLONG_MAX,
+ .freq = 4000, /* 4 KHz */
+ .target = {
+ .uses_mmap = true,
+ },
+ },
+ .max_stack = PERF_MAX_STACK_DEPTH,
+ .sym_pcnt_filter = 5,
+ };
+ struct record_opts *opts = &top.record_opts;
+ struct target *target = &opts->target;
+ const struct option options[] = {
+ OPT_CALLBACK('e', "event", &top.evlist, "event",
"event selector. use 'perf list' to list available events",
- parse_events),
- OPT_INTEGER('c', "count", &default_interval,
- "event period to sample"),
- OPT_INTEGER('p', "pid", &target_pid,
- "profile events on existing pid"),
- OPT_BOOLEAN('a', "all-cpus", &system_wide,
+ parse_events_option),
+ OPT_U64('c', "count", &opts->user_interval, "event period to sample"),
+ OPT_STRING('p', "pid", &target->pid, "pid",
+ "profile events on existing process id"),
+ OPT_STRING('t', "tid", &target->tid, "tid",
+ "profile events on existing thread id"),
+ OPT_BOOLEAN('a', "all-cpus", &target->system_wide,
"system-wide collection from all CPUs"),
- OPT_INTEGER('C', "CPU", &profile_cpu,
- "CPU to profile on"),
- OPT_INTEGER('m', "mmap-pages", &mmap_pages,
- "number of mmap data pages"),
- OPT_INTEGER('r', "realtime", &realtime_prio,
+ OPT_STRING('C', "cpu", &target->cpu_list, "cpu",
+ "list of cpus to monitor"),
+ OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
+ "file", "vmlinux pathname"),
+ OPT_BOOLEAN(0, "ignore-vmlinux", &symbol_conf.ignore_vmlinux,
+ "don't load vmlinux even if found"),
+ OPT_BOOLEAN('K', "hide_kernel_symbols", &top.hide_kernel_symbols,
+ "hide kernel symbols"),
+ OPT_CALLBACK('m', "mmap-pages", &opts->mmap_pages, "pages",
+ "number of mmap data pages",
+ perf_evlist__parse_mmap_pages),
+ OPT_INTEGER('r', "realtime", &top.realtime_prio,
"collect data with this RT SCHED_FIFO priority"),
- OPT_INTEGER('d', "delay", &delay_secs,
+ OPT_INTEGER('d', "delay", &top.delay_secs,
"number of seconds to delay between refreshes"),
- OPT_BOOLEAN('D', "dump-symtab", &dump_symtab,
+ OPT_BOOLEAN('D', "dump-symtab", &top.dump_symtab,
"dump the symbol table used for profiling"),
- OPT_INTEGER('f', "count-filter", &count_filter,
+ OPT_INTEGER('f', "count-filter", &top.count_filter,
"only display functions with more events than this"),
- OPT_BOOLEAN('g', "group", &group,
+ OPT_BOOLEAN(0, "group", &opts->group,
"put the counters into a counter group"),
- OPT_STRING('s', "sym-filter", &sym_filter, "pattern",
- "only display symbols matchig this pattern"),
- OPT_BOOLEAN('z', "zero", &group,
- "zero history across updates"),
- OPT_INTEGER('F', "freq", &freq,
- "profile at this frequency"),
- OPT_INTEGER('E', "entries", &print_entries,
+ OPT_BOOLEAN('i', "no-inherit", &opts->no_inherit,
+ "child tasks do not inherit counters"),
+ OPT_STRING(0, "sym-annotate", &top.sym_filter, "symbol name",
+ "symbol to annotate"),
+ OPT_BOOLEAN('z', "zero", &top.zero, "zero history across updates"),
+ OPT_UINTEGER('F', "freq", &opts->user_freq, "profile at this frequency"),
+ OPT_INTEGER('E', "entries", &top.print_entries,
"display this many functions"),
- OPT_BOOLEAN('v', "verbose", &verbose,
+ OPT_BOOLEAN('U', "hide_user_symbols", &top.hide_user_symbols,
+ "hide user symbols"),
+ OPT_BOOLEAN(0, "tui", &top.use_tui, "Use the TUI interface"),
+ OPT_BOOLEAN(0, "stdio", &top.use_stdio, "Use the stdio interface"),
+ OPT_INCR('v', "verbose", &verbose,
"be more verbose (show counter open errors, etc)"),
+ OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
+ "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..."
+ " Please refer the man page for the complete list."),
+ OPT_STRING(0, "fields", &field_order, "key[,keys...]",
+ "output field(s): overhead, period, sample plus all of sort keys"),
+ OPT_BOOLEAN('n', "show-nr-samples", &symbol_conf.show_nr_samples,
+ "Show a column with the number of samples"),
+ OPT_CALLBACK_NOOPT('g', NULL, &top.record_opts,
+ NULL, "enables call-graph recording",
+ &callchain_opt),
+ OPT_CALLBACK(0, "call-graph", &top.record_opts,
+ "mode[,dump_size]", record_callchain_help,
+ &parse_callchain_opt),
+ OPT_BOOLEAN(0, "children", &symbol_conf.cumulate_callchain,
+ "Accumulate callchains of children and show total overhead as well"),
+ OPT_INTEGER(0, "max-stack", &top.max_stack,
+ "Set the maximum stack depth when parsing the callchain. "
+ "Default: " __stringify(PERF_MAX_STACK_DEPTH)),
+ OPT_CALLBACK(0, "ignore-callees", NULL, "regex",
+ "ignore callees of these functions in call graphs",
+ report_parse_ignore_callees_opt),
+ OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period,
+ "Show a column with the sum of periods"),
+ OPT_STRING(0, "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
+ "only consider symbols in these dsos"),
+ OPT_STRING(0, "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
+ "only consider symbols in these comms"),
+ OPT_STRING(0, "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
+ "only consider these symbols"),
+ OPT_BOOLEAN(0, "source", &symbol_conf.annotate_src,
+ "Interleave source code with assembly code (default)"),
+ OPT_BOOLEAN(0, "asm-raw", &symbol_conf.annotate_asm_raw,
+ "Display raw encoding of assembly instructions (default)"),
+ OPT_STRING(0, "objdump", &objdump_path, "path",
+ "objdump binary to use for disassembly and annotations"),
+ OPT_STRING('M', "disassembler-style", &disassembler_style, "disassembler style",
+ "Specify disassembler style (e.g. -M intel for intel syntax)"),
+ OPT_STRING('u', "uid", &target->uid_str, "user", "user to profile"),
+ OPT_CALLBACK(0, "percent-limit", &top, "percent",
+ "Don't show entries under that percent", parse_percent_limit),
+ OPT_CALLBACK(0, "percentage", NULL, "relative|absolute",
+ "How to display percentage of filtered entries", parse_filter_percentage),
OPT_END()
-};
+ };
+ const char * const top_usage[] = {
+ "perf top [<options>]",
+ NULL
+ };
-int cmd_top(int argc, const char **argv, const char *prefix)
-{
- int counter;
+ top.evlist = perf_evlist__new();
+ if (top.evlist == NULL)
+ return -ENOMEM;
- page_size = sysconf(_SC_PAGE_SIZE);
+ perf_config(perf_top_config, &top);
argc = parse_options(argc, argv, options, top_usage, 0);
if (argc)
usage_with_options(top_usage, options);
- if (freq) {
- default_interval = freq;
- freq = 1;
+ sort__mode = SORT_MODE__TOP;
+ /* display thread wants entries to be collapsed in a different tree */
+ sort__need_collapse = 1;
+
+ if (setup_sorting() < 0) {
+ if (sort_order)
+ parse_options_usage(top_usage, options, "s", 1);
+ if (field_order)
+ parse_options_usage(sort_order ? NULL : top_usage,
+ options, "fields", 0);
+ goto out_delete_evlist;
}
- /* CPU and PID are mutually exclusive */
- if (target_pid != -1 && profile_cpu != -1) {
- printf("WARNING: PID switch overriding CPU\n");
- sleep(1);
- profile_cpu = -1;
+ if (top.use_stdio)
+ use_browser = 0;
+ else if (top.use_tui)
+ use_browser = 1;
+
+ setup_browser(false);
+
+ status = target__validate(target);
+ if (status) {
+ target__strerror(target, status, errbuf, BUFSIZ);
+ ui__warning("%s\n", errbuf);
}
- if (!nr_counters)
- nr_counters = 1;
+ status = target__parse_uid(target);
+ if (status) {
+ int saved_errno = errno;
- if (delay_secs < 1)
- delay_secs = 1;
+ target__strerror(target, status, errbuf, BUFSIZ);
+ ui__error("%s\n", errbuf);
- parse_symbols();
+ status = -saved_errno;
+ goto out_delete_evlist;
+ }
- /*
- * Fill in the ones not specifically initialized via -c:
- */
- for (counter = 0; counter < nr_counters; counter++) {
- if (attrs[counter].sample_period)
- continue;
+ if (target__none(target))
+ target->system_wide = true;
+
+ if (perf_evlist__create_maps(top.evlist, target) < 0)
+ usage_with_options(top_usage, options);
- attrs[counter].sample_period = default_interval;
+ if (!top.evlist->nr_entries &&
+ perf_evlist__add_default(top.evlist) < 0) {
+ ui__error("Not enough memory for event selector list\n");
+ goto out_delete_evlist;
+ }
+
+ symbol_conf.nr_events = top.evlist->nr_entries;
+
+ if (top.delay_secs < 1)
+ top.delay_secs = 1;
+
+ if (record_opts__config(opts)) {
+ status = -EINVAL;
+ goto out_delete_evlist;
+ }
+
+ top.sym_evsel = perf_evlist__first(top.evlist);
+
+ if (!symbol_conf.use_callchain) {
+ symbol_conf.cumulate_callchain = false;
+ perf_hpp__cancel_cumulate();
+ }
+
+ symbol_conf.priv_size = sizeof(struct annotation);
+
+ symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL);
+ if (symbol__init() < 0)
+ return -1;
+
+ sort__setup_elide(stdout);
+
+ get_term_dimensions(&top.winsize);
+ if (top.print_entries == 0) {
+ struct sigaction act = {
+ .sa_sigaction = perf_top__sig_winch,
+ .sa_flags = SA_SIGINFO,
+ };
+ perf_top__update_print_entries(&top);
+ sigaction(SIGWINCH, &act, NULL);
}
- nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
- assert(nr_cpus <= MAX_NR_CPUS);
- assert(nr_cpus >= 0);
+ status = __cmd_top(&top);
- if (target_pid != -1 || profile_cpu != -1)
- nr_cpus = 1;
+out_delete_evlist:
+ perf_evlist__delete(top.evlist);
- return __cmd_top();
+ return status;
}