diff options
Diffstat (limited to 'tools/perf')
91 files changed, 6663 insertions, 2688 deletions
diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Documentation/perf-annotate.txt index 6f5a498608b2..fe6762ed56bd 100644 --- a/tools/perf/Documentation/perf-annotate.txt +++ b/tools/perf/Documentation/perf-annotate.txt @@ -66,6 +66,25 @@ OPTIONS used. This interfaces starts by centering on the line with more samples, TAB/UNTAB cycles through the lines with more samples. +-c:: +--cpu:: Only report samples for the list of CPUs provided. Multiple CPUs can + be provided as a comma-separated list with no space: 0,1. Ranges of + CPUs are specified with -: 0-2. Default is to report samples on all + CPUs. + +--asm-raw:: + Show raw instruction encoding of assembly instructions. + +--source:: + Interleave source code with assembly code. Enabled by default, + disable with --no-source. + +--symfs=<directory>:: + Look for files with symbols relative to this directory. + +-M:: +--disassembler-style=:: Set disassembler style for objdump. + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-report[1] diff --git a/tools/perf/Documentation/perf-buildid-list.txt b/tools/perf/Documentation/perf-buildid-list.txt index 5eaac6f26d51..cc22325ffd1b 100644 --- a/tools/perf/Documentation/perf-buildid-list.txt +++ b/tools/perf/Documentation/perf-buildid-list.txt @@ -16,6 +16,9 @@ This command displays the buildids found in a perf.data file, so that other tools can be used to fetch packages with matching symbol tables for use by perf report. +It can also be used to show the build id of the running kernel or in an ELF +file using -i/--input. + OPTIONS ------- -H:: @@ -27,6 +30,9 @@ OPTIONS -f:: --force:: Don't do ownership validation. +-k:: +--kernel:: + Show running kernel build id. -v:: --verbose:: Be more verbose. diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt index 02bafce4b341..2780d9ce48bf 100644 --- a/tools/perf/Documentation/perf-probe.txt +++ b/tools/perf/Documentation/perf-probe.txt @@ -34,9 +34,11 @@ OPTIONS Specify vmlinux path which has debuginfo (Dwarf binary). -m:: ---module=MODNAME:: +--module=MODNAME|PATH:: Specify module name in which perf-probe searches probe points - or lines. + or lines. If a path of module file is passed, perf-probe + treat it as an offline module (this means you can add a probe on + a module which has not been loaded yet). -s:: --source=PATH:: diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 8ba03d6e5398..212f24d672e1 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -80,15 +80,24 @@ OPTIONS --dump-raw-trace:: Dump raw trace in ASCII. --g [type,min]:: +-g [type,min,order]:: --call-graph:: - Display call chains using type and min percent threshold. + Display call chains using type, min percent threshold and order. type can be either: - flat: single column, linear exposure of call chains. - graph: use a graph tree, displaying absolute overhead rates. - fractal: like graph, but displays relative rates. Each branch of the tree is considered as a new profiled object. + - Default: fractal,0.5. + + order can be either: + - callee: callee based call graph. + - caller: inverted caller based call graph. + + Default: fractal,0.5,callee. + +-G:: +--inverted:: + alias for inverted caller based call graph. --pretty=<key>:: Pretty printing style. key: normal, raw @@ -119,6 +128,30 @@ OPTIONS --symfs=<directory>:: Look for files with symbols relative to this directory. +-c:: +--cpu:: Only report samples for the list of CPUs provided. Multiple CPUs can + be provided as a comma-separated list with no space: 0,1. Ranges of + CPUs are specified with -: 0-2. Default is to report samples on all + CPUs. + +-M:: +--disassembler-style=:: Set disassembler style for objdump. + +--source:: + Interleave source code with assembly code. Enabled by default, + disable with --no-source. + +--asm-raw:: + Show raw instruction encoding of assembly instructions. + +--show-total-period:: Show a column with the sum of periods. + +-I:: +--show-info:: + Display extended information about the perf.data file. This adds + information which may be very large and thus may clutter the display. + It currently includes: cpu and numa topology of the host system. + SEE ALSO -------- -linkperf:perf-stat[1] +linkperf:perf-stat[1], linkperf:perf-annotate[1] diff --git a/tools/perf/Documentation/perf-sched.txt b/tools/perf/Documentation/perf-sched.txt index 46822d5fde1c..5b212b57f70b 100644 --- a/tools/perf/Documentation/perf-sched.txt +++ b/tools/perf/Documentation/perf-sched.txt @@ -8,7 +8,7 @@ perf-sched - Tool to trace/measure scheduler properties (latencies) SYNOPSIS -------- [verse] -'perf sched' {record|latency|map|replay|trace} +'perf sched' {record|latency|map|replay|script} DESCRIPTION ----------- @@ -20,8 +20,8 @@ There are five variants of perf sched: 'perf sched latency' to report the per task scheduling latencies and other scheduling properties of the workload. - 'perf sched trace' to see a detailed trace of the workload that - was recorded. + 'perf sched script' to see a detailed trace of the workload that + was recorded (aliased to 'perf script' for now). 'perf sched replay' to simulate the workload that was recorded via perf sched record. (this is done by starting up mockup threads diff --git a/tools/perf/Documentation/perf-script.txt b/tools/perf/Documentation/perf-script.txt index 86c87e214b11..dec87ecb530e 100644 --- a/tools/perf/Documentation/perf-script.txt +++ b/tools/perf/Documentation/perf-script.txt @@ -115,10 +115,10 @@ OPTIONS -f:: --fields:: Comma separated list of fields to print. Options are: - comm, tid, pid, time, cpu, event, trace, sym. Field - list can be prepended with the type, trace, sw or hw, + comm, tid, pid, time, cpu, event, trace, ip, sym, dso, addr. + Field list can be prepended with the type, trace, sw or hw, to indicate to which event type the field list applies. - e.g., -f sw:comm,tid,time,sym and -f trace:time,cpu,trace + e.g., -f sw:comm,tid,time,ip,sym and -f trace:time,cpu,trace perf script -f <fields> @@ -132,17 +132,17 @@ OPTIONS The arguments are processed in the order received. A later usage can reset a prior request. e.g.: - -f trace: -f comm,tid,time,sym + -f trace: -f comm,tid,time,ip,sym The first -f suppresses trace events (field list is ""), but then the - second invocation sets the fields to comm,tid,time,sym. In this case a + second invocation sets the fields to comm,tid,time,ip,sym. In this case a warning is given to the user: "Overriding previous field request for all events." Alternativey, consider the order: - -f comm,tid,time,sym -f trace: + -f comm,tid,time,ip,sym -f trace: The first -f sets the fields for all events and the second -f suppresses trace events. The user is given a warning message about @@ -182,6 +182,19 @@ OPTIONS --hide-call-graph:: When printing symbols do not display call chain. +-c:: +--cpu:: Only report samples for the list of CPUs provided. Multiple CPUs can + be provided as a comma-separated list with no space: 0,1. Ranges of + CPUs are specified with -: 0-2. Default is to report samples on all + CPUs. + +-I:: +--show-info:: + Display extended information about the perf.data file. This adds + information which may be very large and thus may clutter the display. + It currently includes: cpu and numa topology of the host system. + It can only be used with the perf script report mode. + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-script-perl[1], diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt index 918cc38ee6d1..8966b9ab2014 100644 --- a/tools/perf/Documentation/perf-stat.txt +++ b/tools/perf/Documentation/perf-stat.txt @@ -94,6 +94,22 @@ an empty cgroup (monitor all the time) using, e.g., -G foo,,bar. Cgroups must ha corresponding events, i.e., they always refer to events defined earlier on the command line. +-o file:: +--output file:: +Print the output into the designated file. + +--append:: +Append to the output file designated with the -o option. Ignored if -o is not specified. + +--log-fd:: + +Log output to fd, instead of stderr. Complementary to --output, and mutually exclusive +with it. --append may be used here. Examples: + 3>results perf stat --log-fd 3 -- $cmd + 3>>results perf stat --log-fd 3 --append -- $cmd + + + EXAMPLES -------- diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt index f6eb1cdafb77..b1a5bbbfebef 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -106,6 +106,51 @@ Default is to monitor all CPUS. --zero:: Zero history across display updates. +-s:: +--sort:: + Sort by key(s): pid, comm, dso, symbol, parent + +-n:: +--show-nr-samples:: + Show a column with the number of samples. + +--show-total-period:: + Show a column with the sum of periods. + +--dsos:: + Only consider symbols in these dsos. + +--comms:: + Only consider symbols in these comms. + +--symbols:: + Only consider these symbols. + +-M:: +--disassembler-style=:: Set disassembler style for objdump. + +--source:: + Interleave source code with assembly code. Enabled by default, + disable with --no-source. + +--asm-raw:: + Show raw instruction encoding of assembly instructions. + +-G [type,min,order]:: +--call-graph:: + Display call chains using type, min percent threshold and order. + type can be either: + - flat: single column, linear exposure of call chains. + - graph: use a graph tree, displaying absolute overhead rates. + - fractal: like graph, but displays relative rates. Each branch of + the tree is considered as a new profiled object. + + order can be either: + - callee: callee based call graph. + - caller: inverted caller based call graph. + + Default: fractal,0.5,callee. + INTERACTIVE PROMPTING KEYS -------------------------- @@ -130,9 +175,6 @@ INTERACTIVE PROMPTING KEYS [S]:: Stop annotation, return to full profile display. -[w]:: - Toggle between weighted sum and individual count[E]r profile. - [z]:: Toggle event count zeroing across display updates. diff --git a/tools/perf/Documentation/perfconfig.example b/tools/perf/Documentation/perfconfig.example new file mode 100644 index 000000000000..d1448668f4d4 --- /dev/null +++ b/tools/perf/Documentation/perfconfig.example @@ -0,0 +1,20 @@ +[colors] + + # These were the old defaults + top = red, lightgray + medium = green, lightgray + normal = black, lightgray + selected = lightgray, magenta + code = blue, lightgray + +[tui] + + # Defaults if linked with libslang + report = on + annotate = on + top = on + +[buildid] + + # Default, disable using /dev/null + dir = /root/.debug diff --git a/tools/perf/Makefile b/tools/perf/Makefile index 940257b5774e..b98e3075646b 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -30,6 +30,8 @@ endif # Define EXTRA_CFLAGS=-m64 or EXTRA_CFLAGS=-m32 as appropriate for cross-builds. # # Define NO_DWARF if you do not want debug-info analysis feature at all. +# +# Define WERROR=0 to disable treating any warnings as errors. $(OUTPUT)PERF-VERSION-FILE: .FORCE-PERF-VERSION-FILE @$(SHELL_PATH) util/PERF-VERSION-GEN $(OUTPUT) @@ -52,7 +54,10 @@ ifeq ($(ARCH),i386) endif ifeq ($(ARCH),x86_64) ARCH := x86 - IS_X86_64 := $(shell echo __x86_64__ | ${CC} -E -xc - | tail -n 1) + IS_X86_64 := 0 + ifeq (, $(findstring m32,$(EXTRA_CFLAGS))) + IS_X86_64 := $(shell echo __x86_64__ | ${CC} -E -xc - | tail -n 1) + endif ifeq (${IS_X86_64}, 1) RAW_ARCH := x86_64 ARCH_CFLAGS := -DARCH_X86_64 @@ -60,6 +65,11 @@ ifeq ($(ARCH),x86_64) endif endif +# Treat warnings as errors unless directed not to +ifneq ($(WERROR),0) + CFLAGS_WERROR := -Werror +endif + # # Include saner warnings here, which can catch bugs: # @@ -92,7 +102,7 @@ ifndef PERF_DEBUG CFLAGS_OPTIMIZE = -O6 endif -CFLAGS = -fno-omit-frame-pointer -ggdb3 -Wall -Wextra -std=gnu99 -Werror $(CFLAGS_OPTIMIZE) -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) $(EXTRA_CFLAGS) +CFLAGS = -fno-omit-frame-pointer -ggdb3 -Wall -Wextra -std=gnu99 $(CFLAGS_WERROR) $(CFLAGS_OPTIMIZE) -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) $(EXTRA_CFLAGS) EXTLIBS = -lpthread -lrt -lelf -lm ALL_CFLAGS = $(CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 ALL_LDFLAGS = $(LDFLAGS) @@ -178,9 +188,9 @@ strip-libs = $(filter-out -l%,$(1)) $(OUTPUT)python/perf.so: $(PYRF_OBJS) $(QUIET_GEN)CFLAGS='$(BASIC_CFLAGS)' $(PYTHON_WORD) util/setup.py \ - --quiet build_ext \ - --build-lib='$(OUTPUT)python' \ - --build-temp='$(OUTPUT)python/temp' + --quiet build_ext; \ + mkdir -p $(OUTPUT)python && \ + cp $(PYTHON_EXTBUILD_LIB)perf.so $(OUTPUT)python/ # # No Perl scripts right now: # @@ -279,6 +289,7 @@ LIB_H += util/thread.h LIB_H += util/thread_map.h LIB_H += util/trace-event.h LIB_H += util/probe-finder.h +LIB_H += util/dwarf-aux.h LIB_H += util/probe-event.h LIB_H += util/pstack.h LIB_H += util/cpumap.h @@ -435,6 +446,7 @@ else BASIC_CFLAGS += -DDWARF_SUPPORT EXTLIBS += -lelf -ldw LIB_OBJS += $(OUTPUT)util/probe-finder.o + LIB_OBJS += $(OUTPUT)util/dwarf-aux.o endif # PERF_HAVE_DWARF_REGS endif # NO_DWARF @@ -454,13 +466,13 @@ else LIB_OBJS += $(OUTPUT)util/ui/browsers/annotate.o LIB_OBJS += $(OUTPUT)util/ui/browsers/hists.o LIB_OBJS += $(OUTPUT)util/ui/browsers/map.o - LIB_OBJS += $(OUTPUT)util/ui/browsers/top.o LIB_OBJS += $(OUTPUT)util/ui/helpline.o LIB_OBJS += $(OUTPUT)util/ui/progress.o LIB_OBJS += $(OUTPUT)util/ui/util.o LIB_H += util/ui/browser.h LIB_H += util/ui/browsers/map.h LIB_H += util/ui/helpline.h + LIB_H += util/ui/keysyms.h LIB_H += util/ui/libslang.h LIB_H += util/ui/progress.h LIB_H += util/ui/util.h @@ -504,9 +516,13 @@ else PYTHON_WORD := $(call shell-wordify,$(PYTHON)) - python-clean := $(PYTHON_WORD) util/setup.py clean \ - --build-lib='$(OUTPUT)python' \ - --build-temp='$(OUTPUT)python/temp' + # python extension build directories + PYTHON_EXTBUILD := $(OUTPUT)python_ext_build/ + PYTHON_EXTBUILD_LIB := $(PYTHON_EXTBUILD)lib/ + PYTHON_EXTBUILD_TMP := $(PYTHON_EXTBUILD)tmp/ + export PYTHON_EXTBUILD_LIB PYTHON_EXTBUILD_TMP + + python-clean := rm -rf $(PYTHON_EXTBUILD) $(OUTPUT)python/perf.so ifdef NO_LIBPYTHON $(call disable-python) @@ -713,9 +729,6 @@ $(OUTPUT)util/ui/browser.o: util/ui/browser.c $(OUTPUT)PERF-CFLAGS $(OUTPUT)util/ui/browsers/annotate.o: util/ui/browsers/annotate.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< -$(OUTPUT)util/ui/browsers/top.o: util/ui/browsers/top.c $(OUTPUT)PERF-CFLAGS - $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< - $(OUTPUT)util/ui/browsers/hists.o: util/ui/browsers/hists.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< @@ -863,6 +876,9 @@ install: all $(INSTALL) scripts/python/*.py -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python' $(INSTALL) scripts/python/bin/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python/bin' +install-python_ext: + $(PYTHON_WORD) util/setup.py --quiet install --root='/$(DESTDIR_SQ)' + install-doc: $(MAKE) -C Documentation install @@ -890,7 +906,7 @@ quick-install-html: ### Cleaning rules clean: - $(RM) $(OUTPUT){*.o,*/*.o,*/*/*.o,*/*/*/*.o,$(LIB_FILE),perf-archive} + $(RM) $(LIB_OBJS) $(BUILTIN_OBJS) $(LIB_FILE) $(OUTPUT)perf-archive $(OUTPUT)perf.o $(LANG_BINDINGS) $(RM) $(ALL_PROGRAMS) perf $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo $(OUTPUT)common-cmds.h TAGS tags cscope* $(MAKE) -C Documentation/ clean diff --git a/tools/perf/arch/arm/util/dwarf-regs.c b/tools/perf/arch/arm/util/dwarf-regs.c index fff6450c8c99..e8d5c551c69c 100644 --- a/tools/perf/arch/arm/util/dwarf-regs.c +++ b/tools/perf/arch/arm/util/dwarf-regs.c @@ -8,7 +8,10 @@ * published by the Free Software Foundation. */ +#include <stdlib.h> +#ifndef __UCLIBC__ #include <libio.h> +#endif #include <dwarf-regs.h> struct pt_regs_dwarfnum { diff --git a/tools/perf/arch/powerpc/Makefile b/tools/perf/arch/powerpc/Makefile index 15130b50dfe3..744e629797be 100644 --- a/tools/perf/arch/powerpc/Makefile +++ b/tools/perf/arch/powerpc/Makefile @@ -2,3 +2,4 @@ ifndef NO_DWARF PERF_HAVE_DWARF_REGS := 1 LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o endif +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o diff --git a/tools/perf/arch/powerpc/util/header.c b/tools/perf/arch/powerpc/util/header.c new file mode 100644 index 000000000000..eba80c292945 --- /dev/null +++ b/tools/perf/arch/powerpc/util/header.c @@ -0,0 +1,36 @@ +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../../util/header.h" + +#define __stringify_1(x) #x +#define __stringify(x) __stringify_1(x) + +#define mfspr(rn) ({unsigned long rval; \ + asm volatile("mfspr %0," __stringify(rn) \ + : "=r" (rval)); rval; }) + +#define SPRN_PVR 0x11F /* Processor Version Register */ +#define PVR_VER(pvr) (((pvr) >> 16) & 0xFFFF) /* Version field */ +#define PVR_REV(pvr) (((pvr) >> 0) & 0xFFFF) /* Revison field */ + +int +get_cpuid(char *buffer, size_t sz) +{ + unsigned long pvr; + int nb; + + pvr = mfspr(SPRN_PVR); + + nb = snprintf(buffer, sz, "%lu,%lu$", PVR_VER(pvr), PVR_REV(pvr)); + + /* look for end marker to ensure the entire data fit */ + if (strchr(buffer, '$')) { + buffer[nb-1] = '\0'; + return 0; + } + return -1; +} diff --git a/tools/perf/arch/x86/Makefile b/tools/perf/arch/x86/Makefile index 15130b50dfe3..744e629797be 100644 --- a/tools/perf/arch/x86/Makefile +++ b/tools/perf/arch/x86/Makefile @@ -2,3 +2,4 @@ ifndef NO_DWARF PERF_HAVE_DWARF_REGS := 1 LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o endif +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o diff --git a/tools/perf/arch/x86/util/header.c b/tools/perf/arch/x86/util/header.c new file mode 100644 index 000000000000..f94006068d2b --- /dev/null +++ b/tools/perf/arch/x86/util/header.c @@ -0,0 +1,59 @@ +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../../util/header.h" + +static inline void +cpuid(unsigned int op, unsigned int *a, unsigned int *b, unsigned int *c, + unsigned int *d) +{ + __asm__ __volatile__ (".byte 0x53\n\tcpuid\n\t" + "movl %%ebx, %%esi\n\t.byte 0x5b" + : "=a" (*a), + "=S" (*b), + "=c" (*c), + "=d" (*d) + : "a" (op)); +} + +int +get_cpuid(char *buffer, size_t sz) +{ + unsigned int a, b, c, d, lvl; + int family = -1, model = -1, step = -1; + int nb; + char vendor[16]; + + cpuid(0, &lvl, &b, &c, &d); + strncpy(&vendor[0], (char *)(&b), 4); + strncpy(&vendor[4], (char *)(&d), 4); + strncpy(&vendor[8], (char *)(&c), 4); + vendor[12] = '\0'; + + if (lvl >= 1) { + cpuid(1, &a, &b, &c, &d); + + family = (a >> 8) & 0xf; /* bits 11 - 8 */ + model = (a >> 4) & 0xf; /* Bits 7 - 4 */ + step = a & 0xf; + + /* extended family */ + if (family == 0xf) + family += (a >> 20) & 0xff; + + /* extended model */ + if (family >= 0x6) + model += ((a >> 16) & 0xf) << 4; + } + nb = snprintf(buffer, sz, "%s,%u,%u,%u$", vendor, family, model, step); + + /* look for end marker to ensure the entire data fit */ + if (strchr(buffer, '$')) { + buffer[nb-1] = '\0'; + return 0; + } + return -1; +} diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index 7b139e1e7e86..46b4c24f338e 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -28,6 +28,8 @@ #include "util/hist.h" #include "util/session.h" +#include <linux/bitmap.h> + static char const *input_name = "perf.data"; static bool force, use_tui, use_stdio; @@ -38,6 +40,9 @@ static bool print_line; static const char *sym_hist_filter; +static const char *cpu_list; +static DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); + static int perf_evlist__add_sample(struct perf_evlist *evlist, struct perf_sample *sample, struct perf_evsel *evsel, @@ -90,6 +95,9 @@ static int process_sample_event(union perf_event *event, return -1; } + if (cpu_list && !test_bit(sample->cpu, cpu_bitmap)) + return 0; + if (!al.filtered && perf_evlist__add_sample(session->evlist, sample, evsel, &al)) { pr_warning("problem incrementing symbol count, " @@ -106,10 +114,11 @@ static int hist_entry__tty_annotate(struct hist_entry *he, int evidx) print_line, full_paths, 0, 0); } -static void hists__find_annotations(struct hists *self, int evidx) +static void hists__find_annotations(struct hists *self, int evidx, + int nr_events) { struct rb_node *nd = rb_first(&self->entries), *next; - int key = KEY_RIGHT; + int key = K_RIGHT; while (nd) { struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); @@ -121,7 +130,7 @@ static void hists__find_annotations(struct hists *self, int evidx) notes = symbol__annotation(he->ms.sym); if (notes->src == NULL) { find_next: - if (key == KEY_LEFT) + if (key == K_LEFT) nd = rb_prev(nd); else nd = rb_next(nd); @@ -129,12 +138,13 @@ find_next: } if (use_browser > 0) { - key = hist_entry__tui_annotate(he, evidx); + key = hist_entry__tui_annotate(he, evidx, nr_events, + NULL, NULL, 0); switch (key) { - case KEY_RIGHT: + case K_RIGHT: next = rb_next(nd); break; - case KEY_LEFT: + case K_LEFT: next = rb_prev(nd); break; default: @@ -177,6 +187,12 @@ static int __cmd_annotate(void) if (session == NULL) return -ENOMEM; + if (cpu_list) { + ret = perf_session__cpu_bitmap(session, cpu_list, cpu_bitmap); + if (ret) + goto out_delete; + } + ret = perf_session__process_events(session, &event_ops); if (ret) goto out_delete; @@ -201,7 +217,8 @@ static int __cmd_annotate(void) total_nr_samples += nr_samples; hists__collapse_resort(hists); hists__output_resort(hists); - hists__find_annotations(hists, pos->idx); + hists__find_annotations(hists, pos->idx, + session->evlist->nr_entries); } } @@ -252,6 +269,15 @@ static const struct option options[] = { "print matching source lines (may be slow)"), OPT_BOOLEAN('P', "full-paths", &full_paths, "Don't shorten the displayed pathnames"), + OPT_STRING('c', "cpu", &cpu_list, "cpu", "list of cpus to profile"), + OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", + "Look for files with symbols relative to this directory"), + 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('M', "disassembler-style", &disassembler_style, "disassembler style", + "Specify disassembler style (e.g. -M intel for intel syntax)"), OPT_END() }; diff --git a/tools/perf/builtin-buildid-list.c b/tools/perf/builtin-buildid-list.c index 5af32ae9031e..cb690a65bf02 100644 --- a/tools/perf/builtin-buildid-list.c +++ b/tools/perf/builtin-buildid-list.c @@ -1,7 +1,8 @@ /* * builtin-buildid-list.c * - * Builtin buildid-list command: list buildids in perf.data + * Builtin buildid-list command: list buildids in perf.data, in the running + * kernel and in ELF files. * * Copyright (C) 2009, Red Hat Inc. * Copyright (C) 2009, Arnaldo Carvalho de Melo <acme@redhat.com> @@ -15,8 +16,11 @@ #include "util/session.h" #include "util/symbol.h" +#include <libelf.h> + static char const *input_name = "perf.data"; static bool force; +static bool show_kernel; static bool with_hits; static const char * const buildid_list_usage[] = { @@ -29,12 +33,13 @@ static const struct option options[] = { OPT_STRING('i', "input", &input_name, "file", "input file name"), OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), + OPT_BOOLEAN('k', "kernel", &show_kernel, "Show current kernel build id"), OPT_INCR('v', "verbose", &verbose, "be more verbose"), OPT_END() }; -static int __cmd_buildid_list(void) +static int perf_session__list_build_ids(void) { struct perf_session *session; @@ -52,6 +57,49 @@ static int __cmd_buildid_list(void) return 0; } +static int sysfs__fprintf_build_id(FILE *fp) +{ + u8 kallsyms_build_id[BUILD_ID_SIZE]; + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + + if (sysfs__read_build_id("/sys/kernel/notes", kallsyms_build_id, + sizeof(kallsyms_build_id)) != 0) + return -1; + + build_id__sprintf(kallsyms_build_id, sizeof(kallsyms_build_id), + sbuild_id); + fprintf(fp, "%s\n", sbuild_id); + return 0; +} + +static int filename__fprintf_build_id(const char *name, FILE *fp) +{ + u8 build_id[BUILD_ID_SIZE]; + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + + if (filename__read_build_id(name, build_id, + sizeof(build_id)) != sizeof(build_id)) + return 0; + + build_id__sprintf(build_id, sizeof(build_id), sbuild_id); + return fprintf(fp, "%s\n", sbuild_id); +} + +static int __cmd_buildid_list(void) +{ + if (show_kernel) + return sysfs__fprintf_build_id(stdout); + + elf_version(EV_CURRENT); + /* + * See if this is an ELF file first: + */ + if (filename__fprintf_build_id(input_name, stdout)) + return 0; + + return perf_session__list_build_ids(); +} + int cmd_buildid_list(int argc, const char **argv, const char *prefix __used) { argc = parse_options(argc, argv, options, buildid_list_usage, 0); diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index e8219990f8b8..b39f3a1ee7dc 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -162,7 +162,7 @@ static int __cmd_diff(void) hists__match(&session[0]->hists, &session[1]->hists); hists__fprintf(&session[1]->hists, &session[0]->hists, - show_displacement, stdout); + show_displacement, true, 0, 0, stdout); out_delete: for (i = 0; i < 2; ++i) perf_session__delete(session[i]); diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index 9ac05aafd9b2..899080ace267 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -942,10 +942,10 @@ static const char *record_args[] = { "-f", "-m", "1024", "-c", "1", - "-e", "lock:lock_acquire:r", - "-e", "lock:lock_acquired:r", - "-e", "lock:lock_contended:r", - "-e", "lock:lock_release:r", + "-e", "lock:lock_acquire", + "-e", "lock:lock_acquired", + "-e", "lock:lock_contended", + "-e", "lock:lock_release", }; static int __cmd_record(int argc, const char **argv) diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index 2c0e64d0b4aa..710ae3d0a489 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -134,10 +134,18 @@ static int opt_show_lines(const struct option *opt __used, { int ret = 0; - if (str) - ret = parse_line_range_desc(str, ¶ms.line_range); - INIT_LIST_HEAD(¶ms.line_range.line_list); + if (!str) + return 0; + + if (params.show_lines) { + pr_warning("Warning: more than one --line options are" + " detected. Only the first one is valid.\n"); + return 0; + } + params.show_lines = true; + ret = parse_line_range_desc(str, ¶ms.line_range); + INIT_LIST_HEAD(¶ms.line_range.line_list); return ret; } @@ -242,7 +250,8 @@ static const struct option options[] = { OPT_STRING('s', "source", &symbol_conf.source_prefix, "directory", "path to kernel source"), OPT_STRING('m', "module", ¶ms.target_module, - "modname", "target module name"), + "modname|path", + "target module name (for online) or path (for offline)"), #endif OPT__DRY_RUN(&probe_event_dry_run), OPT_INTEGER('\0', "max-probes", ¶ms.max_probe_points, diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 8e2c85798185..6ab58cc99d53 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -30,8 +30,6 @@ #include <sched.h> #include <sys/mman.h> -#define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y)) - enum write_mode_t { WRITE_FORCE, WRITE_APPEND @@ -47,7 +45,7 @@ static int freq = 1000; static int output; static int pipe_output = 0; static const char *output_name = NULL; -static int group = 0; +static bool group = false; static int realtime_prio = 0; static bool nodelay = false; static bool raw_samples = false; @@ -75,6 +73,7 @@ static off_t post_processing_offset; static struct perf_session *session; static const char *cpu_list; +static const char *progname; static void advance_output(size_t size) { @@ -139,17 +138,29 @@ static void mmap_read(struct perf_mmap *md) static volatile int done = 0; static volatile int signr = -1; +static volatile int child_finished = 0; static void sig_handler(int sig) { + if (sig == SIGCHLD) + child_finished = 1; + done = 1; signr = sig; } static void sig_atexit(void) { - if (child_pid > 0) - kill(child_pid, SIGTERM); + int status; + + if (child_pid > 0) { + if (!child_finished) + kill(child_pid, SIGTERM); + + wait(&status); + if (WIFSIGNALED(status)) + psignal(WTERMSIG(status), progname); + } if (signr == -1 || signr == SIGUSR1) return; @@ -163,6 +174,7 @@ static void config_attr(struct perf_evsel *evsel, struct perf_evlist *evlist) struct perf_event_attr *attr = &evsel->attr; int track = !evsel->idx; /* only the first counter needs these */ + attr->disabled = 1; attr->inherit = !no_inherit; attr->read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | @@ -250,13 +262,16 @@ static bool perf_evlist__equal(struct perf_evlist *evlist, static void open_counters(struct perf_evlist *evlist) { - struct perf_evsel *pos; + struct perf_evsel *pos, *first; if (evlist->cpus->map[0] < 0) no_inherit = true; + first = list_entry(evlist->entries.next, struct perf_evsel, node); + list_for_each_entry(pos, &evlist->entries, node) { struct perf_event_attr *attr = &pos->attr; + struct xyarray *group_fd = NULL; /* * Check if parse_single_tracepoint_event has already asked for * PERF_SAMPLE_TIME. @@ -271,15 +286,19 @@ static void open_counters(struct perf_evlist *evlist) */ bool time_needed = attr->sample_type & PERF_SAMPLE_TIME; + if (group && pos != first) + group_fd = first->fd; + config_attr(pos, evlist); retry_sample_id: attr->sample_id_all = sample_id_all_avail ? 1 : 0; try_again: - if (perf_evsel__open(pos, evlist->cpus, evlist->threads, group) < 0) { + if (perf_evsel__open(pos, evlist->cpus, evlist->threads, group, + group_fd) < 0) { int err = errno; if (err == EPERM || err == EACCES) { - ui__warning_paranoid(); + ui__error_paranoid(); exit(EXIT_FAILURE); } else if (err == ENODEV && cpu_list) { die("No such device - did you specify" @@ -438,7 +457,6 @@ static void mmap_read_all(void) static int __cmd_record(int argc, const char **argv) { - int i; struct stat st; int flags; int err; @@ -448,6 +466,8 @@ static int __cmd_record(int argc, const char **argv) char buf; struct machine *machine; + progname = argv[0]; + page_size = sysconf(_SC_PAGE_SIZE); atexit(sig_atexit); @@ -516,6 +536,19 @@ static int __cmd_record(int argc, const char **argv) if (have_tracepoints(&evsel_list->entries)) perf_header__set_feat(&session->header, HEADER_TRACE_INFO); + perf_header__set_feat(&session->header, HEADER_HOSTNAME); + perf_header__set_feat(&session->header, HEADER_OSRELEASE); + perf_header__set_feat(&session->header, HEADER_ARCH); + perf_header__set_feat(&session->header, HEADER_CPUDESC); + perf_header__set_feat(&session->header, HEADER_NRCPUS); + perf_header__set_feat(&session->header, HEADER_EVENT_DESC); + perf_header__set_feat(&session->header, HEADER_CMDLINE); + perf_header__set_feat(&session->header, HEADER_VERSION); + perf_header__set_feat(&session->header, HEADER_CPU_TOPOLOGY); + perf_header__set_feat(&session->header, HEADER_TOTAL_MEM); + perf_header__set_feat(&session->header, HEADER_NUMA_TOPOLOGY); + perf_header__set_feat(&session->header, HEADER_CPUID); + /* 512 kiB: default amount of unprivileged mlocked memory */ if (mmap_pages == UINT_MAX) mmap_pages = (512 * 1024) / page_size; @@ -674,6 +707,8 @@ static int __cmd_record(int argc, const char **argv) } } + perf_evlist__enable(evsel_list); + /* * Let the child rip */ @@ -682,7 +717,6 @@ static int __cmd_record(int argc, const char **argv) for (;;) { int hits = samples; - int thread; mmap_read_all(); @@ -693,19 +727,8 @@ static int __cmd_record(int argc, const char **argv) waking++; } - if (done) { - for (i = 0; i < evsel_list->cpus->nr; i++) { - struct perf_evsel *pos; - - list_for_each_entry(pos, &evsel_list->entries, node) { - for (thread = 0; - thread < evsel_list->threads->nr; - thread++) - ioctl(FD(pos, i, thread), - PERF_EVENT_IOC_DISABLE); - } - } - } + if (done) + perf_evlist__disable(evsel_list); } if (quiet || signr == SIGUSR1) @@ -740,7 +763,7 @@ static bool force, append_file; const struct option record_options[] = { OPT_CALLBACK('e', "event", &evsel_list, "event", "event selector. use 'perf list' to list available events", - parse_events), + parse_events_option), OPT_CALLBACK(0, "filter", &evsel_list, "filter", "event filter", parse_filter), OPT_INTEGER('p', "pid", &target_pid, @@ -768,6 +791,8 @@ const struct option record_options[] = { "child tasks do not inherit counters"), OPT_UINTEGER('F', "freq", &user_freq, "profile at this frequency"), OPT_UINTEGER('m', "mmap-pages", &mmap_pages, "number of mmap data pages"), + OPT_BOOLEAN(0, "group", &group, + "put the counters into a counter group"), OPT_BOOLEAN('g', "call-graph", &call_graph, "do call-graph (stack chain/backtrace) recording"), OPT_INCR('v', "verbose", &verbose, @@ -795,6 +820,8 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) int err = -ENOMEM; struct perf_evsel *pos; + perf_header__set_cmdline(argc, argv); + evsel_list = perf_evlist__new(NULL, NULL); if (evsel_list == NULL) return -ENOMEM; diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 287a173523a7..4d7c8340c326 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -33,11 +33,14 @@ #include "util/sort.h" #include "util/hist.h" +#include <linux/bitmap.h> + static char const *input_name = "perf.data"; static bool force, use_tui, use_stdio; static bool hide_unresolved; static bool dont_use_callchains; +static bool show_full_info; static bool show_threads; static struct perf_read_values show_threads_values; @@ -45,9 +48,13 @@ static struct perf_read_values show_threads_values; static const char default_pretty_printing_style[] = "normal"; static const char *pretty_printing_style = default_pretty_printing_style; -static char callchain_default_opt[] = "fractal,0.5"; +static char callchain_default_opt[] = "fractal,0.5,callee"; +static bool inverted_callchain; static symbol_filter_t annotate_init; +static const char *cpu_list; +static DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); + static int perf_session__add_hist_entry(struct perf_session *session, struct addr_location *al, struct perf_sample *sample, @@ -116,6 +123,9 @@ static int process_sample_event(union perf_event *event, if (al.filtered || (hide_unresolved && al.sym == NULL)) return 0; + if (cpu_list && !test_bit(sample->cpu, cpu_bitmap)) + return 0; + if (al.map != NULL) al.map->dso->hit = 1; @@ -153,23 +163,22 @@ static int perf_session__setup_sample_type(struct perf_session *self) { if (!(self->sample_type & PERF_SAMPLE_CALLCHAIN)) { if (sort__has_parent) { - fprintf(stderr, "selected --sort parent, but no" - " callchain data. Did you call" - " perf record without -g?\n"); + ui__warning("Selected --sort parent, but no " + "callchain data. Did you call " + "'perf record' without -g?\n"); return -EINVAL; } if (symbol_conf.use_callchain) { - fprintf(stderr, "selected -g but no callchain data." - " Did you call perf record without" - " -g?\n"); + ui__warning("Selected -g but no callchain data. Did " + "you call 'perf record' without -g?\n"); return -1; } } else if (!dont_use_callchains && callchain_param.mode != CHAIN_NONE && !symbol_conf.use_callchain) { symbol_conf.use_callchain = true; if (callchain_register_param(&callchain_param) < 0) { - fprintf(stderr, "Can't register callchain" - " params\n"); + ui__warning("Can't register callchain " + "params.\n"); return -EINVAL; } } @@ -221,13 +230,10 @@ static int perf_evlist__tty_browse_hists(struct perf_evlist *evlist, list_for_each_entry(pos, &evlist->entries, node) { struct hists *hists = &pos->hists; - const char *evname = NULL; - - if (rb_first(&hists->entries) != rb_last(&hists->entries)) - evname = event_name(pos); + const char *evname = event_name(pos); hists__fprintf_nr_sample_events(hists, evname, stdout); - hists__fprintf(hists, NULL, false, stdout); + hists__fprintf(hists, NULL, false, true, 0, 0, stdout); fprintf(stdout, "\n\n"); } @@ -262,6 +268,15 @@ static int __cmd_report(void) if (session == NULL) return -ENOMEM; + if (cpu_list) { + ret = perf_session__cpu_bitmap(session, cpu_list, cpu_bitmap); + if (ret) + goto out_delete; + } + + if (use_browser <= 0) + perf_session__fprintf_info(session, stdout, show_full_info); + if (show_threads) perf_read_values_init(&show_threads_values); @@ -316,9 +331,10 @@ static int __cmd_report(void) goto out_delete; } - if (use_browser > 0) - perf_evlist__tui_browse_hists(session->evlist, help); - else + if (use_browser > 0) { + perf_evlist__tui_browse_hists(session->evlist, help, + NULL, NULL, 0); + } else perf_evlist__tty_browse_hists(session->evlist, help); out_delete: @@ -386,13 +402,29 @@ parse_callchain_opt(const struct option *opt __used, const char *arg, if (!tok) goto setup; - tok2 = strtok(NULL, ","); callchain_param.min_percent = strtod(tok, &endptr); if (tok == endptr) return -1; - if (tok2) + /* get the print limit */ + tok2 = strtok(NULL, ","); + if (!tok2) + goto setup; + + if (tok2[0] != 'c') { callchain_param.print_limit = strtod(tok2, &endptr); + tok2 = strtok(NULL, ","); + if (!tok2) + goto setup; + } + + /* get the call chain order */ + if (!strcmp(tok2, "caller")) + callchain_param.order = ORDER_CALLER; + else if (!strcmp(tok2, "callee")) + callchain_param.order = ORDER_CALLEE; + else + return -1; setup: if (callchain_register_param(&callchain_param) < 0) { fprintf(stderr, "Can't register callchain params\n"); @@ -436,9 +468,10 @@ static const struct option options[] = { "regex filter to identify parent, see: '--sort parent'"), OPT_BOOLEAN('x', "exclude-other", &symbol_conf.exclude_other, "Only display entries with parent-match"), - OPT_CALLBACK_DEFAULT('g', "call-graph", NULL, "output_type,min_percent", - "Display callchains using output_type (graph, flat, fractal, or none) and min percent threshold. " - "Default: fractal,0.5", &parse_callchain_opt, callchain_default_opt), + OPT_CALLBACK_DEFAULT('g', "call-graph", NULL, "output_type,min_percent, call_order", + "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold and callchain order. " + "Default: fractal,0.5,callee", &parse_callchain_opt, callchain_default_opt), + OPT_BOOLEAN('G', "inverted", &inverted_callchain, "alias for inverted call graph"), OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]", "only consider symbols in these dsos"), OPT_STRING('C', "comms", &symbol_conf.comm_list_str, "comm[,comm...]", @@ -455,6 +488,17 @@ static const struct option options[] = { "Only display entries resolved to a symbol"), OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", "Look for files with symbols relative to this directory"), + OPT_STRING('c', "cpu", &cpu_list, "cpu", "list of cpus to profile"), + OPT_BOOLEAN('I', "show-info", &show_full_info, + "Display extended information about perf.data file"), + 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('M', "disassembler-style", &disassembler_style, "disassembler style", + "Specify disassembler style (e.g. -M intel for intel syntax)"), + OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period, + "Show a column with the sum of periods"), OPT_END() }; @@ -467,6 +511,9 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) else if (use_tui) use_browser = 1; + if (inverted_callchain) + callchain_param.order = ORDER_CALLER; + if (strcmp(input_name, "-") != 0) setup_browser(true); else @@ -504,7 +551,14 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) if (parent_pattern != default_parent_pattern) { if (sort_dimension__add("parent") < 0) return -1; - sort_parent.elide = 1; + + /* + * Only show the parent fields if we explicitly + * sort that way. If we only use parent machinery + * for filtering, we don't want it. + */ + if (!strstr(sort_order, "parent")) + sort_parent.elide = 1; } else symbol_conf.exclude_other = false; diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index dcfe8873c9a1..5177964943e7 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c @@ -1637,23 +1637,29 @@ static struct perf_event_ops event_ops = { .ordered_samples = true, }; -static int read_events(void) +static void read_events(bool destroy, struct perf_session **psession) { int err = -EINVAL; struct perf_session *session = perf_session__new(input_name, O_RDONLY, 0, false, &event_ops); if (session == NULL) - return -ENOMEM; + die("No Memory"); if (perf_session__has_traces(session, "record -R")) { err = perf_session__process_events(session, &event_ops); + if (err) + die("Failed to process events, error %d", err); + nr_events = session->hists.stats.nr_events[0]; nr_lost_events = session->hists.stats.total_lost; nr_lost_chunks = session->hists.stats.nr_events[PERF_RECORD_LOST]; } - perf_session__delete(session); - return err; + if (destroy) + perf_session__delete(session); + + if (psession) + *psession = session; } static void print_bad_events(void) @@ -1689,9 +1695,10 @@ static void print_bad_events(void) static void __cmd_lat(void) { struct rb_node *next; + struct perf_session *session; setup_pager(); - read_events(); + read_events(false, &session); sort_lat(); printf("\n ---------------------------------------------------------------------------------------------------------------\n"); @@ -1717,6 +1724,7 @@ static void __cmd_lat(void) print_bad_events(); printf("\n"); + perf_session__delete(session); } static struct trace_sched_handler map_ops = { @@ -1731,7 +1739,7 @@ static void __cmd_map(void) max_cpu = sysconf(_SC_NPROCESSORS_CONF); setup_pager(); - read_events(); + read_events(true, NULL); print_bad_events(); } @@ -1744,7 +1752,7 @@ static void __cmd_replay(void) test_calibrations(); - read_events(); + read_events(true, NULL); printf("nr_run_events: %ld\n", nr_run_events); printf("nr_sleep_events: %ld\n", nr_sleep_events); @@ -1769,7 +1777,7 @@ static void __cmd_replay(void) static const char * const sched_usage[] = { - "perf sched [<options>] {record|latency|map|replay|trace}", + "perf sched [<options>] {record|latency|map|replay|script}", NULL }; diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index 22747de7234b..2f62a2952269 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -13,6 +13,7 @@ #include "util/util.h" #include "util/evlist.h" #include "util/evsel.h" +#include <linux/bitmap.h> static char const *script_name; static char const *generate_script_lang; @@ -21,6 +22,9 @@ static u64 last_timestamp; static u64 nr_unordered; extern const struct option record_options[]; static bool no_callchain; +static bool show_full_info; +static const char *cpu_list; +static DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); enum perf_output_field { PERF_OUTPUT_COMM = 1U << 0, @@ -30,7 +34,10 @@ enum perf_output_field { PERF_OUTPUT_CPU = 1U << 4, PERF_OUTPUT_EVNAME = 1U << 5, PERF_OUTPUT_TRACE = 1U << 6, - PERF_OUTPUT_SYM = 1U << 7, + PERF_OUTPUT_IP = 1U << 7, + PERF_OUTPUT_SYM = 1U << 8, + PERF_OUTPUT_DSO = 1U << 9, + PERF_OUTPUT_ADDR = 1U << 10, }; struct output_option { @@ -44,7 +51,10 @@ struct output_option { {.str = "cpu", .field = PERF_OUTPUT_CPU}, {.str = "event", .field = PERF_OUTPUT_EVNAME}, {.str = "trace", .field = PERF_OUTPUT_TRACE}, + {.str = "ip", .field = PERF_OUTPUT_IP}, {.str = "sym", .field = PERF_OUTPUT_SYM}, + {.str = "dso", .field = PERF_OUTPUT_DSO}, + {.str = "addr", .field = PERF_OUTPUT_ADDR}, }; /* default set to maintain compatibility with current format */ @@ -60,7 +70,8 @@ static struct { .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | - PERF_OUTPUT_EVNAME | PERF_OUTPUT_SYM, + PERF_OUTPUT_EVNAME | PERF_OUTPUT_IP | + PERF_OUTPUT_SYM | PERF_OUTPUT_DSO, .invalid_fields = PERF_OUTPUT_TRACE, }, @@ -70,7 +81,8 @@ static struct { .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | - PERF_OUTPUT_EVNAME | PERF_OUTPUT_SYM, + PERF_OUTPUT_EVNAME | PERF_OUTPUT_IP | + PERF_OUTPUT_SYM | PERF_OUTPUT_DSO, .invalid_fields = PERF_OUTPUT_TRACE, }, @@ -88,7 +100,8 @@ static struct { .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | - PERF_OUTPUT_EVNAME | PERF_OUTPUT_SYM, + PERF_OUTPUT_EVNAME | PERF_OUTPUT_IP | + PERF_OUTPUT_SYM | PERF_OUTPUT_DSO, .invalid_fields = PERF_OUTPUT_TRACE, }, @@ -157,9 +170,9 @@ static int perf_evsel__check_attr(struct perf_evsel *evsel, !perf_session__has_traces(session, "record -R")) return -EINVAL; - if (PRINT_FIELD(SYM)) { + if (PRINT_FIELD(IP)) { if (perf_event_attr__check_stype(attr, PERF_SAMPLE_IP, "IP", - PERF_OUTPUT_SYM)) + PERF_OUTPUT_IP)) return -EINVAL; if (!no_callchain && @@ -167,6 +180,24 @@ static int perf_evsel__check_attr(struct perf_evsel *evsel, symbol_conf.use_callchain = false; } + if (PRINT_FIELD(ADDR) && + perf_event_attr__check_stype(attr, PERF_SAMPLE_ADDR, "ADDR", + PERF_OUTPUT_ADDR)) + return -EINVAL; + + if (PRINT_FIELD(SYM) && !PRINT_FIELD(IP) && !PRINT_FIELD(ADDR)) { + pr_err("Display of symbols requested but neither sample IP nor " + "sample address\nis selected. Hence, no addresses to convert " + "to symbols.\n"); + return -EINVAL; + } + if (PRINT_FIELD(DSO) && !PRINT_FIELD(IP) && !PRINT_FIELD(ADDR)) { + pr_err("Display of DSO requested but neither sample IP nor " + "sample address\nis selected. Hence, no addresses to convert " + "to DSO.\n"); + return -EINVAL; + } + if ((PRINT_FIELD(PID) || PRINT_FIELD(TID)) && perf_event_attr__check_stype(attr, PERF_SAMPLE_TID, "TID", PERF_OUTPUT_TID|PERF_OUTPUT_PID)) @@ -230,7 +261,7 @@ static void print_sample_start(struct perf_sample *sample, if (PRINT_FIELD(COMM)) { if (latency_format) printf("%8.8s ", thread->comm); - else if (PRINT_FIELD(SYM) && symbol_conf.use_callchain) + else if (PRINT_FIELD(IP) && symbol_conf.use_callchain) printf("%s ", thread->comm); else printf("%16s ", thread->comm); @@ -271,6 +302,63 @@ static void print_sample_start(struct perf_sample *sample, } } +static bool sample_addr_correlates_sym(struct perf_event_attr *attr) +{ + if ((attr->type == PERF_TYPE_SOFTWARE) && + ((attr->config == PERF_COUNT_SW_PAGE_FAULTS) || + (attr->config == PERF_COUNT_SW_PAGE_FAULTS_MIN) || + (attr->config == PERF_COUNT_SW_PAGE_FAULTS_MAJ))) + return true; + + return false; +} + +static void print_sample_addr(union perf_event *event, + struct perf_sample *sample, + struct perf_session *session, + struct thread *thread, + struct perf_event_attr *attr) +{ + struct addr_location al; + u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + const char *symname, *dsoname; + + printf("%16" PRIx64, sample->addr); + + if (!sample_addr_correlates_sym(attr)) + return; + + thread__find_addr_map(thread, session, cpumode, MAP__FUNCTION, + event->ip.pid, sample->addr, &al); + if (!al.map) + thread__find_addr_map(thread, session, cpumode, MAP__VARIABLE, + event->ip.pid, sample->addr, &al); + + al.cpu = sample->cpu; + al.sym = NULL; + + if (al.map) + al.sym = map__find_symbol(al.map, al.addr, NULL); + + if (PRINT_FIELD(SYM)) { + if (al.sym && al.sym->name) + symname = al.sym->name; + else + symname = ""; + + printf(" %16s", symname); + } + + if (PRINT_FIELD(DSO)) { + if (al.map && al.map->dso && al.map->dso->name) + dsoname = al.map->dso->name; + else + dsoname = ""; + + printf(" (%s)", dsoname); + } +} + static void process_event(union perf_event *event __unused, struct perf_sample *sample, struct perf_evsel *evsel, @@ -288,12 +376,16 @@ static void process_event(union perf_event *event __unused, print_trace_event(sample->cpu, sample->raw_data, sample->raw_size); - if (PRINT_FIELD(SYM)) { + if (PRINT_FIELD(ADDR)) + print_sample_addr(event, sample, session, thread, attr); + + if (PRINT_FIELD(IP)) { if (!symbol_conf.use_callchain) printf(" "); else printf("\n"); - perf_session__print_symbols(event, sample, session); + perf_session__print_ip(event, sample, session, + PRINT_FIELD(SYM), PRINT_FIELD(DSO)); } printf("\n"); @@ -365,6 +457,10 @@ static int process_sample_event(union perf_event *event, last_timestamp = sample->time; return 0; } + + if (cpu_list && !test_bit(sample->cpu, cpu_bitmap)) + return 0; + scripting_ops->process_event(event, sample, evsel, session, thread); session->hists.stats.total_period += sample->period; @@ -985,9 +1081,11 @@ static const struct option options[] = { OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", "Look for files with symbols relative to this directory"), OPT_CALLBACK('f', "fields", NULL, "str", - "comma separated output fields prepend with 'type:'. Valid types: hw,sw,trace,raw. Fields: comm,tid,pid,time,cpu,event,trace,sym", + "comma separated output fields prepend with 'type:'. Valid types: hw,sw,trace,raw. Fields: comm,tid,pid,time,cpu,event,trace,ip,sym,dso,addr", parse_output_fields), - + OPT_STRING('c', "cpu", &cpu_list, "cpu", "list of cpus to profile"), + OPT_BOOLEAN('I', "show-info", &show_full_info, + "display extended information from perf.data file"), OPT_END() }; @@ -1167,6 +1265,13 @@ int cmd_script(int argc, const char **argv, const char *prefix __used) if (session == NULL) return -ENOMEM; + if (cpu_list) { + if (perf_session__cpu_bitmap(session, cpu_list, cpu_bitmap)) + return -1; + } + + perf_session__fprintf_info(session, stdout, show_full_info); + if (!no_callchain) symbol_conf.use_callchain = true; else diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index a9f06715e44d..7d98676808d8 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -61,6 +61,8 @@ #include <locale.h> #define DEFAULT_SEPARATOR " " +#define CNTR_NOT_SUPPORTED "<not supported>" +#define CNTR_NOT_COUNTED "<not counted>" static struct perf_event_attr default_attrs[] = { @@ -191,6 +193,10 @@ static int big_num_opt = -1; static const char *cpu_list; static const char *csv_sep = NULL; static bool csv_output = false; +static bool group = false; +static const char *output_name = NULL; +static FILE *output = NULL; +static int output_fd; static volatile int done = 0; @@ -248,8 +254,13 @@ static double avg_stats(struct stats *stats) */ static double stddev_stats(struct stats *stats) { - double variance = stats->M2 / (stats->n - 1); - double variance_mean = variance / stats->n; + double variance, variance_mean; + + if (!stats->n) + return 0.0; + + variance = stats->M2 / (stats->n - 1); + variance_mean = variance / stats->n; return sqrt(variance_mean); } @@ -267,9 +278,14 @@ struct stats runtime_itlb_cache_stats[MAX_NR_CPUS]; struct stats runtime_dtlb_cache_stats[MAX_NR_CPUS]; struct stats walltime_nsecs_stats; -static int create_perf_stat_counter(struct perf_evsel *evsel) +static int create_perf_stat_counter(struct perf_evsel *evsel, + struct perf_evsel *first) { struct perf_event_attr *attr = &evsel->attr; + struct xyarray *group_fd = NULL; + + if (group && evsel != first) + group_fd = first->fd; if (scale) attr->read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | @@ -278,14 +294,15 @@ static int create_perf_stat_counter(struct perf_evsel *evsel) attr->inherit = !no_inherit; if (system_wide) - return perf_evsel__open_per_cpu(evsel, evsel_list->cpus, false); - + return perf_evsel__open_per_cpu(evsel, evsel_list->cpus, + group, group_fd); if (target_pid == -1 && target_tid == -1) { attr->disabled = 1; attr->enable_on_exec = 1; } - return perf_evsel__open_per_thread(evsel, evsel_list->threads, false); + return perf_evsel__open_per_thread(evsel, evsel_list->threads, + group, group_fd); } /* @@ -349,7 +366,7 @@ static int read_counter_aggr(struct perf_evsel *counter) update_stats(&ps->res_stats[i], count[i]); if (verbose) { - fprintf(stderr, "%s: %" PRIu64 " %" PRIu64 " %" PRIu64 "\n", + fprintf(output, "%s: %" PRIu64 " %" PRIu64 " %" PRIu64 "\n", event_name(counter), count[0], count[1], count[2]); } @@ -385,7 +402,7 @@ static int read_counter(struct perf_evsel *counter) static int run_perf_stat(int argc __used, const char **argv) { unsigned long long t0, t1; - struct perf_evsel *counter; + struct perf_evsel *counter, *first; int status = 0; int child_ready_pipe[2], go_pipe[2]; const bool forks = (argc > 0); @@ -442,12 +459,15 @@ static int run_perf_stat(int argc __used, const char **argv) close(child_ready_pipe[0]); } + first = list_entry(evsel_list->entries.next, struct perf_evsel, node); + list_for_each_entry(counter, &evsel_list->entries, node) { - if (create_perf_stat_counter(counter) < 0) { + if (create_perf_stat_counter(counter, first) < 0) { if (errno == EINVAL || errno == ENOSYS || errno == ENOENT) { if (verbose) ui__warning("%s event is not supported by the kernel.\n", event_name(counter)); + counter->supported = false; continue; } @@ -466,6 +486,7 @@ static int run_perf_stat(int argc __used, const char **argv) die("Not all events could be opened.\n"); return -1; } + counter->supported = true; } if (perf_evlist__set_filters(evsel_list)) { @@ -482,6 +503,8 @@ static int run_perf_stat(int argc __used, const char **argv) if (forks) { close(go_pipe[1]); wait(&status); + if (WIFSIGNALED(status)) + psignal(WTERMSIG(status), argv[0]); } else { while(!done) sleep(1); } @@ -513,7 +536,10 @@ static void print_noise_pct(double total, double avg) if (avg) pct = 100.0*total/avg; - fprintf(stderr, " ( +-%6.2f%% )", pct); + if (csv_output) + fprintf(output, "%s%.2f%%", csv_sep, pct); + else if (pct) + fprintf(output, " ( +-%6.2f%% )", pct); } static void print_noise(struct perf_evsel *evsel, double avg) @@ -538,16 +564,17 @@ static void nsec_printout(int cpu, struct perf_evsel *evsel, double avg) csv_output ? 0 : -4, evsel_list->cpus->map[cpu], csv_sep); - fprintf(stderr, fmt, cpustr, msecs, csv_sep, event_name(evsel)); + fprintf(output, fmt, cpustr, msecs, csv_sep, event_name(evsel)); if (evsel->cgrp) - fprintf(stderr, "%s%s", csv_sep, evsel->cgrp->name); + fprintf(output, "%s%s", csv_sep, evsel->cgrp->name); if (csv_output) return; if (perf_evsel__match(evsel, SOFTWARE, SW_TASK_CLOCK)) - fprintf(stderr, " # %8.3f CPUs utilized ", avg / avg_stats(&walltime_nsecs_stats)); + fprintf(output, " # %8.3f CPUs utilized ", + avg / avg_stats(&walltime_nsecs_stats)); } static void print_stalled_cycles_frontend(int cpu, struct perf_evsel *evsel __used, double avg) @@ -568,9 +595,9 @@ static void print_stalled_cycles_frontend(int cpu, struct perf_evsel *evsel __us else if (ratio > 10.0) color = PERF_COLOR_YELLOW; - fprintf(stderr, " # "); - color_fprintf(stderr, color, "%6.2f%%", ratio); - fprintf(stderr, " frontend cycles idle "); + fprintf(output, " # "); + color_fprintf(output, color, "%6.2f%%", ratio); + fprintf(output, " frontend cycles idle "); } static void print_stalled_cycles_backend(int cpu, struct perf_evsel *evsel __used, double avg) @@ -591,9 +618,9 @@ static void print_stalled_cycles_backend(int cpu, struct perf_evsel *evsel __use else if (ratio > 20.0) color = PERF_COLOR_YELLOW; - fprintf(stderr, " # "); - color_fprintf(stderr, color, "%6.2f%%", ratio); - fprintf(stderr, " backend cycles idle "); + fprintf(output, " # "); + color_fprintf(output, color, "%6.2f%%", ratio); + fprintf(output, " backend cycles idle "); } static void print_branch_misses(int cpu, struct perf_evsel *evsel __used, double avg) @@ -614,9 +641,9 @@ static void print_branch_misses(int cpu, struct perf_evsel *evsel __used, double else if (ratio > 5.0) color = PERF_COLOR_YELLOW; - fprintf(stderr, " # "); - color_fprintf(stderr, color, "%6.2f%%", ratio); - fprintf(stderr, " of all branches "); + fprintf(output, " # "); + color_fprintf(output, color, "%6.2f%%", ratio); + fprintf(output, " of all branches "); } static void print_l1_dcache_misses(int cpu, struct perf_evsel *evsel __used, double avg) @@ -637,9 +664,9 @@ static void print_l1_dcache_misses(int cpu, struct perf_evsel *evsel __used, dou else if (ratio > 5.0) color = PERF_COLOR_YELLOW; - fprintf(stderr, " # "); - color_fprintf(stderr, color, "%6.2f%%", ratio); - fprintf(stderr, " of all L1-dcache hits "); + fprintf(output, " # "); + color_fprintf(output, color, "%6.2f%%", ratio); + fprintf(output, " of all L1-dcache hits "); } static void print_l1_icache_misses(int cpu, struct perf_evsel *evsel __used, double avg) @@ -660,9 +687,9 @@ static void print_l1_icache_misses(int cpu, struct perf_evsel *evsel __used, dou else if (ratio > 5.0) color = PERF_COLOR_YELLOW; - fprintf(stderr, " # "); - color_fprintf(stderr, color, "%6.2f%%", ratio); - fprintf(stderr, " of all L1-icache hits "); + fprintf(output, " # "); + color_fprintf(output, color, "%6.2f%%", ratio); + fprintf(output, " of all L1-icache hits "); } static void print_dtlb_cache_misses(int cpu, struct perf_evsel *evsel __used, double avg) @@ -683,9 +710,9 @@ static void print_dtlb_cache_misses(int cpu, struct perf_evsel *evsel __used, do else if (ratio > 5.0) color = PERF_COLOR_YELLOW; - fprintf(stderr, " # "); - color_fprintf(stderr, color, "%6.2f%%", ratio); - fprintf(stderr, " of all dTLB cache hits "); + fprintf(output, " # "); + color_fprintf(output, color, "%6.2f%%", ratio); + fprintf(output, " of all dTLB cache hits "); } static void print_itlb_cache_misses(int cpu, struct perf_evsel *evsel __used, double avg) @@ -706,9 +733,9 @@ static void print_itlb_cache_misses(int cpu, struct perf_evsel *evsel __used, do else if (ratio > 5.0) color = PERF_COLOR_YELLOW; - fprintf(stderr, " # "); - color_fprintf(stderr, color, "%6.2f%%", ratio); - fprintf(stderr, " of all iTLB cache hits "); + fprintf(output, " # "); + color_fprintf(output, color, "%6.2f%%", ratio); + fprintf(output, " of all iTLB cache hits "); } static void print_ll_cache_misses(int cpu, struct perf_evsel *evsel __used, double avg) @@ -729,9 +756,9 @@ static void print_ll_cache_misses(int cpu, struct perf_evsel *evsel __used, doub else if (ratio > 5.0) color = PERF_COLOR_YELLOW; - fprintf(stderr, " # "); - color_fprintf(stderr, color, "%6.2f%%", ratio); - fprintf(stderr, " of all LL-cache hits "); + fprintf(output, " # "); + color_fprintf(output, color, "%6.2f%%", ratio); + fprintf(output, " of all LL-cache hits "); } static void abs_printout(int cpu, struct perf_evsel *evsel, double avg) @@ -754,10 +781,10 @@ static void abs_printout(int cpu, struct perf_evsel *evsel, double avg) else cpu = 0; - fprintf(stderr, fmt, cpustr, avg, csv_sep, event_name(evsel)); + fprintf(output, fmt, cpustr, avg, csv_sep, event_name(evsel)); if (evsel->cgrp) - fprintf(stderr, "%s%s", csv_sep, evsel->cgrp->name); + fprintf(output, "%s%s", csv_sep, evsel->cgrp->name); if (csv_output) return; @@ -768,14 +795,14 @@ static void abs_printout(int cpu, struct perf_evsel *evsel, double avg) if (total) ratio = avg / total; - fprintf(stderr, " # %5.2f insns per cycle ", ratio); + fprintf(output, " # %5.2f insns per cycle ", ratio); total = avg_stats(&runtime_stalled_cycles_front_stats[cpu]); total = max(total, avg_stats(&runtime_stalled_cycles_back_stats[cpu])); if (total && avg) { ratio = total / avg; - fprintf(stderr, "\n # %5.2f stalled cycles per insn", ratio); + fprintf(output, "\n # %5.2f stalled cycles per insn", ratio); } } else if (perf_evsel__match(evsel, HARDWARE, HW_BRANCH_MISSES) && @@ -823,7 +850,7 @@ static void abs_printout(int cpu, struct perf_evsel *evsel, double avg) if (total) ratio = avg * 100 / total; - fprintf(stderr, " # %8.3f %% of all cache refs ", ratio); + fprintf(output, " # %8.3f %% of all cache refs ", ratio); } else if (perf_evsel__match(evsel, HARDWARE, HW_STALLED_CYCLES_FRONTEND)) { print_stalled_cycles_frontend(cpu, evsel, avg); @@ -835,16 +862,16 @@ static void abs_printout(int cpu, struct perf_evsel *evsel, double avg) if (total) ratio = 1.0 * avg / total; - fprintf(stderr, " # %8.3f GHz ", ratio); + fprintf(output, " # %8.3f GHz ", ratio); } else if (runtime_nsecs_stats[cpu].n != 0) { total = avg_stats(&runtime_nsecs_stats[cpu]); if (total) ratio = 1000.0 * avg / total; - fprintf(stderr, " # %8.3f M/sec ", ratio); + fprintf(output, " # %8.3f M/sec ", ratio); } else { - fprintf(stderr, " "); + fprintf(output, " "); } } @@ -859,17 +886,17 @@ static void print_counter_aggr(struct perf_evsel *counter) int scaled = counter->counts->scaled; if (scaled == -1) { - fprintf(stderr, "%*s%s%*s", + fprintf(output, "%*s%s%*s", csv_output ? 0 : 18, - "<not counted>", + counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED, csv_sep, csv_output ? 0 : -24, event_name(counter)); if (counter->cgrp) - fprintf(stderr, "%s%s", csv_sep, counter->cgrp->name); + fprintf(output, "%s%s", csv_sep, counter->cgrp->name); - fputc('\n', stderr); + fputc('\n', output); return; } @@ -878,22 +905,22 @@ static void print_counter_aggr(struct perf_evsel *counter) else abs_printout(-1, counter, avg); + print_noise(counter, avg); + if (csv_output) { - fputc('\n', stderr); + fputc('\n', output); return; } - print_noise(counter, avg); - if (scaled) { double avg_enabled, avg_running; avg_enabled = avg_stats(&ps->res_stats[1]); avg_running = avg_stats(&ps->res_stats[2]); - fprintf(stderr, " [%5.2f%%]", 100 * avg_running / avg_enabled); + fprintf(output, " [%5.2f%%]", 100 * avg_running / avg_enabled); } - fprintf(stderr, "\n"); + fprintf(output, "\n"); } /* @@ -910,18 +937,20 @@ static void print_counter(struct perf_evsel *counter) ena = counter->counts->cpu[cpu].ena; run = counter->counts->cpu[cpu].run; if (run == 0 || ena == 0) { - fprintf(stderr, "CPU%*d%s%*s%s%*s", + fprintf(output, "CPU%*d%s%*s%s%*s", csv_output ? 0 : -4, evsel_list->cpus->map[cpu], csv_sep, csv_output ? 0 : 18, - "<not counted>", csv_sep, + counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED, + csv_sep, csv_output ? 0 : -24, event_name(counter)); if (counter->cgrp) - fprintf(stderr, "%s%s", csv_sep, counter->cgrp->name); + fprintf(output, "%s%s", + csv_sep, counter->cgrp->name); - fputc('\n', stderr); + fputc('\n', output); continue; } @@ -934,9 +963,10 @@ static void print_counter(struct perf_evsel *counter) print_noise(counter, 1.0); if (run != ena) - fprintf(stderr, " (%.2f%%)", 100.0 * run / ena); + fprintf(output, " (%.2f%%)", + 100.0 * run / ena); } - fputc('\n', stderr); + fputc('\n', output); } } @@ -948,21 +978,21 @@ static void print_stat(int argc, const char **argv) fflush(stdout); if (!csv_output) { - fprintf(stderr, "\n"); - fprintf(stderr, " Performance counter stats for "); + fprintf(output, "\n"); + fprintf(output, " Performance counter stats for "); if(target_pid == -1 && target_tid == -1) { - fprintf(stderr, "\'%s", argv[0]); + fprintf(output, "\'%s", argv[0]); for (i = 1; i < argc; i++) - fprintf(stderr, " %s", argv[i]); + fprintf(output, " %s", argv[i]); } else if (target_pid != -1) - fprintf(stderr, "process id \'%d", target_pid); + fprintf(output, "process id \'%d", target_pid); else - fprintf(stderr, "thread id \'%d", target_tid); + fprintf(output, "thread id \'%d", target_tid); - fprintf(stderr, "\'"); + fprintf(output, "\'"); if (run_count > 1) - fprintf(stderr, " (%d runs)", run_count); - fprintf(stderr, ":\n\n"); + fprintf(output, " (%d runs)", run_count); + fprintf(output, ":\n\n"); } if (no_aggr) { @@ -975,15 +1005,15 @@ static void print_stat(int argc, const char **argv) if (!csv_output) { if (!null_run) - fprintf(stderr, "\n"); - fprintf(stderr, " %17.9f seconds time elapsed", + fprintf(output, "\n"); + fprintf(output, " %17.9f seconds time elapsed", avg_stats(&walltime_nsecs_stats)/1e9); if (run_count > 1) { - fprintf(stderr, " "); + fprintf(output, " "); print_noise_pct(stddev_stats(&walltime_nsecs_stats), avg_stats(&walltime_nsecs_stats)); } - fprintf(stderr, "\n\n"); + fprintf(output, "\n\n"); } } @@ -1021,10 +1051,12 @@ static int stat__set_big_num(const struct option *opt __used, return 0; } +static bool append_file; + static const struct option options[] = { OPT_CALLBACK('e', "event", &evsel_list, "event", "event selector. use 'perf list' to list available events", - parse_events), + parse_events_option), OPT_CALLBACK(0, "filter", &evsel_list, "filter", "event filter", parse_filter), OPT_BOOLEAN('i', "no-inherit", &no_inherit, @@ -1035,6 +1067,8 @@ static const struct option options[] = { "stat events on existing thread id"), OPT_BOOLEAN('a', "all-cpus", &system_wide, "system-wide collection from all CPUs"), + OPT_BOOLEAN('g', "group", &group, + "put the counters into a counter group"), OPT_BOOLEAN('c', "scale", &scale, "scale/normalize counters"), OPT_INCR('v', "verbose", &verbose, @@ -1059,6 +1093,11 @@ static const struct option options[] = { OPT_CALLBACK('G', "cgroup", &evsel_list, "name", "monitor event in cgroup name only", parse_cgroups), + OPT_STRING('o', "output", &output_name, "file", + "output file name"), + OPT_BOOLEAN(0, "append", &append_file, "append to the output file"), + OPT_INTEGER(0, "log-fd", &output_fd, + "log output to fd, instead of stderr"), OPT_END() }; @@ -1130,6 +1169,7 @@ int cmd_stat(int argc, const char **argv, const char *prefix __used) { struct perf_evsel *pos; int status = -ENOMEM; + const char *mode; setlocale(LC_ALL, ""); @@ -1140,16 +1180,46 @@ int cmd_stat(int argc, const char **argv, const char *prefix __used) argc = parse_options(argc, argv, options, stat_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (csv_sep) + output = stderr; + if (output_name && strcmp(output_name, "-")) + output = NULL; + + if (output_name && output_fd) { + fprintf(stderr, "cannot use both --output and --log-fd\n"); + usage_with_options(stat_usage, options); + } + if (!output) { + struct timespec tm; + mode = append_file ? "a" : "w"; + + output = fopen(output_name, mode); + if (!output) { + perror("failed to create output file"); + exit(-1); + } + clock_gettime(CLOCK_REALTIME, &tm); + fprintf(output, "# started on %s\n", ctime(&tm.tv_sec)); + } else if (output_fd != 2) { + mode = append_file ? "a" : "w"; + output = fdopen(output_fd, mode); + if (!output) { + perror("Failed opening logfd"); + return -errno; + } + } + + if (csv_sep) { csv_output = true; - else + if (!strcmp(csv_sep, "\\t")) + csv_sep = "\t"; + } else csv_sep = DEFAULT_SEPARATOR; /* * let the spreadsheet do the pretty-printing */ if (csv_output) { - /* User explicitely passed -B? */ + /* User explicitly passed -B? */ if (big_num_opt == 1) { fprintf(stderr, "-B option not supported with -x\n"); usage_with_options(stat_usage, options); @@ -1215,7 +1285,8 @@ int cmd_stat(int argc, const char **argv, const char *prefix __used) status = 0; for (run_idx = 0; run_idx < run_count; run_idx++) { if (run_count != 1 && verbose) - fprintf(stderr, "[ perf stat: executing run #%d ... ]\n", run_idx + 1); + fprintf(output, "[ perf stat: executing run #%d ... ]\n", + run_idx + 1); if (sync_run) sync(); diff --git a/tools/perf/builtin-test.c b/tools/perf/builtin-test.c index 2da9162262b0..831d1baeac37 100644 --- a/tools/perf/builtin-test.c +++ b/tools/perf/builtin-test.c @@ -12,6 +12,7 @@ #include "util/parse-events.h" #include "util/symbol.h" #include "util/thread_map.h" +#include "../../include/linux/hw_breakpoint.h" static long page_size; @@ -245,8 +246,8 @@ static int trace_event__id(const char *evname) int err = -1, fd; if (asprintf(&filename, - "/sys/kernel/debug/tracing/events/syscalls/%s/id", - evname) < 0) + "%s/syscalls/%s/id", + debugfs_path, evname) < 0) return -1; fd = open(filename, O_RDONLY); @@ -290,7 +291,7 @@ static int test__open_syscall_event(void) goto out_thread_map_delete; } - if (perf_evsel__open_per_thread(evsel, threads, false) < 0) { + if (perf_evsel__open_per_thread(evsel, threads, false, NULL) < 0) { pr_debug("failed to open counter: %s, " "tweak /proc/sys/kernel/perf_event_paranoid?\n", strerror(errno)); @@ -365,7 +366,7 @@ static int test__open_syscall_event_on_all_cpus(void) goto out_thread_map_delete; } - if (perf_evsel__open(evsel, cpus, threads, false) < 0) { + if (perf_evsel__open(evsel, cpus, threads, false, NULL) < 0) { pr_debug("failed to open counter: %s, " "tweak /proc/sys/kernel/perf_event_paranoid?\n", strerror(errno)); @@ -530,7 +531,7 @@ static int test__basic_mmap(void) perf_evlist__add(evlist, evsels[i]); - if (perf_evsel__open(evsels[i], cpus, threads, false) < 0) { + if (perf_evsel__open(evsels[i], cpus, threads, false, NULL) < 0) { pr_debug("failed to open counter: %s, " "tweak /proc/sys/kernel/perf_event_paranoid?\n", strerror(errno)); @@ -560,7 +561,7 @@ static int test__basic_mmap(void) } err = perf_event__parse_sample(event, attr.sample_type, sample_size, - false, &sample); + false, &sample, false); if (err) { pr_err("Can't parse sample, err = %d\n", err); goto out_munmap; @@ -600,6 +601,246 @@ out_free_threads: #undef nsyscalls } +#define TEST_ASSERT_VAL(text, cond) \ +do { \ + if (!cond) { \ + pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \ + return -1; \ + } \ +} while (0) + +static int test__checkevent_tracepoint(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong sample_type", + (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | PERF_SAMPLE_CPU) == + evsel->attr.sample_type); + TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->attr.sample_period); + return 0; +} + +static int test__checkevent_tracepoint_multi(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel; + + TEST_ASSERT_VAL("wrong number of entries", evlist->nr_entries > 1); + + list_for_each_entry(evsel, &evlist->entries, node) { + TEST_ASSERT_VAL("wrong type", + PERF_TYPE_TRACEPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong sample_type", + (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | PERF_SAMPLE_CPU) + == evsel->attr.sample_type); + TEST_ASSERT_VAL("wrong sample_period", + 1 == evsel->attr.sample_period); + } + return 0; +} + +static int test__checkevent_raw(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); + return 0; +} + +static int test__checkevent_numeric(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", 1 == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); + return 0; +} + +static int test__checkevent_symbolic_name(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); + return 0; +} + +static int test__checkevent_symbolic_alias(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_SW_PAGE_FAULTS == evsel->attr.config); + return 0; +} + +static int test__checkevent_genhw(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HW_CACHE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", (1 << 16) == evsel->attr.config); + return 0; +} + +static int test__checkevent_breakpoint(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", (HW_BREAKPOINT_R | HW_BREAKPOINT_W) == + evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_4 == + evsel->attr.bp_len); + return 0; +} + +static int test__checkevent_breakpoint_x(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", + HW_BREAKPOINT_X == evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", sizeof(long) == evsel->attr.bp_len); + return 0; +} + +static int test__checkevent_breakpoint_r(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", + PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", + HW_BREAKPOINT_R == evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", + HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len); + return 0; +} + +static int test__checkevent_breakpoint_w(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", + PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", + HW_BREAKPOINT_W == evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", + HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len); + return 0; +} + +static struct test__event_st { + const char *name; + __u32 type; + int (*check)(struct perf_evlist *evlist); +} test__events[] = { + { + .name = "syscalls:sys_enter_open", + .check = test__checkevent_tracepoint, + }, + { + .name = "syscalls:*", + .check = test__checkevent_tracepoint_multi, + }, + { + .name = "r1", + .check = test__checkevent_raw, + }, + { + .name = "1:1", + .check = test__checkevent_numeric, + }, + { + .name = "instructions", + .check = test__checkevent_symbolic_name, + }, + { + .name = "faults", + .check = test__checkevent_symbolic_alias, + }, + { + .name = "L1-dcache-load-miss", + .check = test__checkevent_genhw, + }, + { + .name = "mem:0", + .check = test__checkevent_breakpoint, + }, + { + .name = "mem:0:x", + .check = test__checkevent_breakpoint_x, + }, + { + .name = "mem:0:r", + .check = test__checkevent_breakpoint_r, + }, + { + .name = "mem:0:w", + .check = test__checkevent_breakpoint_w, + }, +}; + +#define TEST__EVENTS_CNT (sizeof(test__events) / sizeof(struct test__event_st)) + +static int test__parse_events(void) +{ + struct perf_evlist *evlist; + u_int i; + int ret = 0; + + for (i = 0; i < TEST__EVENTS_CNT; i++) { + struct test__event_st *e = &test__events[i]; + + evlist = perf_evlist__new(NULL, NULL); + if (evlist == NULL) + break; + + ret = parse_events(evlist, e->name, 0); + if (ret) { + pr_debug("failed to parse event '%s', err %d\n", + e->name, ret); + break; + } + + ret = e->check(evlist); + if (ret) + break; + + perf_evlist__delete(evlist); + } + + return ret; +} static struct test { const char *desc; int (*func)(void); @@ -621,6 +862,10 @@ static struct test { .func = test__basic_mmap, }, { + .desc = "parse events tests", + .func = test__parse_events, + }, + { .func = NULL, }, }; diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index f2f3f4937aa2..c9cdedb58134 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: * @@ -36,6 +37,7 @@ #include "util/parse-events.h" #include "util/cpumap.h" #include "util/xyarray.h" +#include "util/sort.h" #include "util/debug.h" @@ -65,12 +67,8 @@ static struct perf_top top = { .count_filter = 5, .delay_secs = 2, - .display_weighted = -1, .target_pid = -1, .target_tid = -1, - .active_symbols = LIST_HEAD_INIT(top.active_symbols), - .active_symbols_lock = PTHREAD_MUTEX_INITIALIZER, - .active_symbols_cond = PTHREAD_COND_INITIALIZER, .freq = 1000, /* 1 KHz */ }; @@ -78,6 +76,12 @@ static bool system_wide = false; static bool use_tui, use_stdio; +static bool sort_has_symbols; + +static bool dont_use_callchains; +static char callchain_default_opt[] = "fractal,0.5,callee"; + + static int default_interval = 0; static bool kptr_restrict_warned; @@ -85,7 +89,7 @@ static bool vmlinux_warned; static bool inherit = false; static int realtime_prio = 0; static bool group = false; -static unsigned int page_size; +static bool sample_id_all_avail = true; static unsigned int mmap_pages = 128; static bool dump_symtab = false; @@ -93,7 +97,6 @@ static bool dump_symtab = false; static struct winsize winsize; static const char *sym_filter = NULL; -struct sym_entry *sym_filter_entry_sched = NULL; static int sym_pcnt_filter = 5; /* @@ -136,18 +139,18 @@ static void sig_winch_handler(int sig __used) update_print_entries(&winsize); } -static int parse_source(struct sym_entry *syme) +static int parse_source(struct hist_entry *he) { struct symbol *sym; struct annotation *notes; struct map *map; int err = -1; - if (!syme) + if (!he || !he->ms.sym) return -1; - sym = sym_entry__symbol(syme); - map = syme->map; + sym = he->ms.sym; + map = he->ms.map; /* * We can't annotate with just /proc/kallsyms @@ -175,52 +178,63 @@ static int parse_source(struct sym_entry *syme) return err; } - err = symbol__annotate(sym, syme->map, 0); + err = symbol__annotate(sym, map, 0); if (err == 0) { out_assign: - top.sym_filter_entry = syme; + top.sym_filter_entry = he; } pthread_mutex_unlock(¬es->lock); return err; } -static void __zero_source_counters(struct sym_entry *syme) +static void __zero_source_counters(struct hist_entry *he) { - struct symbol *sym = sym_entry__symbol(syme); + struct symbol *sym = he->ms.sym; symbol__annotate_zero_histograms(sym); } -static void record_precise_ip(struct sym_entry *syme, int counter, u64 ip) +static void record_precise_ip(struct hist_entry *he, int counter, u64 ip) { struct annotation *notes; struct symbol *sym; - if (syme != top.sym_filter_entry) + 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; - sym = sym_entry__symbol(syme); + sym = he->ms.sym; notes = symbol__annotation(sym); if (pthread_mutex_trylock(¬es->lock)) return; - ip = syme->map->map_ip(syme->map, ip); - symbol__inc_addr_samples(sym, syme->map, counter, ip); + if (notes->src == NULL && + symbol__alloc_hist(sym, top.evlist->nr_entries) < 0) { + pthread_mutex_unlock(¬es->lock); + pr_err("Not enough memory for annotating '%s' symbol!\n", + sym->name); + sleep(1); + return; + } + + ip = he->ms.map->map_ip(he->ms.map, ip); + symbol__inc_addr_samples(sym, he->ms.map, counter, ip); pthread_mutex_unlock(¬es->lock); } -static void show_details(struct sym_entry *syme) +static void show_details(struct hist_entry *he) { struct annotation *notes; struct symbol *symbol; int more; - if (!syme) + if (!he) return; - symbol = sym_entry__symbol(syme); + symbol = he->ms.sym; notes = symbol__annotation(symbol); pthread_mutex_lock(¬es->lock); @@ -231,7 +245,7 @@ static void show_details(struct sym_entry *syme) printf("Showing %s for %s\n", event_name(top.sym_evsel), symbol->name); printf(" Events Pcnt (>=%d%%)\n", sym_pcnt_filter); - more = symbol__annotate_printf(symbol, syme->map, top.sym_evsel->idx, + more = symbol__annotate_printf(symbol, he->ms.map, top.sym_evsel->idx, 0, sym_pcnt_filter, top.print_entries, 4); if (top.zero) symbol__annotate_zero_histogram(symbol, top.sym_evsel->idx); @@ -245,21 +259,28 @@ out_unlock: static const char CONSOLE_CLEAR[] = "[H[2J"; -static void __list_insert_active_sym(struct sym_entry *syme) +static struct hist_entry * + perf_session__add_hist_entry(struct perf_session *session, + struct addr_location *al, + struct perf_sample *sample, + struct perf_evsel *evsel) { - list_add(&syme->node, &top.active_symbols); + struct hist_entry *he; + + he = __hists__add_entry(&evsel->hists, al, NULL, sample->period); + if (he == NULL) + return NULL; + + session->hists.stats.total_period += sample->period; + hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); + return he; } -static void print_sym_table(struct perf_session *session) +static void print_sym_table(void) { char bf[160]; int printed = 0; - struct rb_node *nd; - struct sym_entry *syme; - struct rb_root tmp = RB_ROOT; const int win_width = winsize.ws_col - 1; - int sym_width, dso_width, dso_short_width; - float sum_ksamples = perf_top__decay_samples(&top, &tmp); puts(CONSOLE_CLEAR); @@ -270,10 +291,14 @@ static void print_sym_table(struct perf_session *session) printf("%-*.*s\n", win_width, win_width, graph_dotted_line); - if (session->hists.stats.total_lost != 0) { - color_fprintf(stdout, PERF_COLOR_RED, "WARNING:"); - printf(" LOST %" PRIu64 " events, Check IO/CPU overload\n", - session->hists.stats.total_lost); + 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; } if (top.sym_filter_entry) { @@ -281,58 +306,15 @@ static void print_sym_table(struct perf_session *session) return; } - perf_top__find_widths(&top, &tmp, &dso_width, &dso_short_width, - &sym_width); - - if (sym_width + dso_width > winsize.ws_col - 29) { - dso_width = dso_short_width; - if (sym_width + dso_width > winsize.ws_col - 29) - sym_width = winsize.ws_col - dso_width - 29; - } + hists__collapse_resort_threaded(&top.sym_evsel->hists); + hists__output_resort_threaded(&top.sym_evsel->hists); + hists__decay_entries_threaded(&top.sym_evsel->hists, + top.hide_user_symbols, + top.hide_kernel_symbols); + hists__output_recalc_col_len(&top.sym_evsel->hists, winsize.ws_row - 3); putchar('\n'); - if (top.evlist->nr_entries == 1) - printf(" samples pcnt"); - else - printf(" weight samples pcnt"); - - if (verbose) - printf(" RIP "); - printf(" %-*.*s DSO\n", sym_width, sym_width, "function"); - printf(" %s _______ _____", - top.evlist->nr_entries == 1 ? " " : "______"); - if (verbose) - printf(" ________________"); - printf(" %-*.*s", sym_width, sym_width, graph_line); - printf(" %-*.*s", dso_width, dso_width, graph_line); - puts("\n"); - - for (nd = rb_first(&tmp); nd; nd = rb_next(nd)) { - struct symbol *sym; - double pcnt; - - syme = rb_entry(nd, struct sym_entry, rb_node); - sym = sym_entry__symbol(syme); - if (++printed > top.print_entries || - (int)syme->snap_count < top.count_filter) - continue; - - pcnt = 100.0 - (100.0 * ((sum_ksamples - syme->snap_count) / - sum_ksamples)); - - if (top.evlist->nr_entries == 1 || !top.display_weighted) - printf("%20.2f ", syme->weight); - else - printf("%9.1f %10ld ", syme->weight, syme->snap_count); - - percent_color_fprintf(stdout, "%4.1f%%", pcnt); - if (verbose) - printf(" %016" PRIx64, sym->start); - printf(" %-*.*s", sym_width, sym_width, sym->name); - printf(" %-*.*s\n", dso_width, dso_width, - dso_width >= syme->map->dso->long_name_len ? - syme->map->dso->long_name : - syme->map->dso->short_name); - } + hists__fprintf(&top.sym_evsel->hists, NULL, false, false, + winsize.ws_row - 4 - printed, win_width, stdout); } static void prompt_integer(int *target, const char *msg) @@ -370,10 +352,11 @@ static void prompt_percent(int *target, const char *msg) *target = tmp; } -static void prompt_symbol(struct sym_entry **target, const char *msg) +static void prompt_symbol(struct hist_entry **target, const char *msg) { char *buf = malloc(0), *p; - struct sym_entry *syme = *target, *n, *found = NULL; + struct hist_entry *syme = *target, *n, *found = NULL; + struct rb_node *next; size_t dummy = 0; /* zero counters of active symbol */ @@ -390,17 +373,14 @@ static void prompt_symbol(struct sym_entry **target, const char *msg) if (p) *p = 0; - pthread_mutex_lock(&top.active_symbols_lock); - syme = list_entry(top.active_symbols.next, struct sym_entry, node); - pthread_mutex_unlock(&top.active_symbols_lock); - - list_for_each_entry_safe_from(syme, n, &top.active_symbols, node) { - struct symbol *sym = sym_entry__symbol(syme); - - if (!strcmp(buf, sym->name)) { - found = syme; + 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) { @@ -419,7 +399,7 @@ static void print_mapped_keys(void) char *name = NULL; if (top.sym_filter_entry) { - struct symbol *sym = sym_entry__symbol(top.sym_filter_entry); + struct symbol *sym = top.sym_filter_entry->ms.sym; name = sym->name; } @@ -436,9 +416,6 @@ static void print_mapped_keys(void) fprintf(stdout, "\t[s] annotate symbol. \t(%s)\n", name?: "NULL"); fprintf(stdout, "\t[S] stop annotation.\n"); - if (top.evlist->nr_entries > 1) - fprintf(stdout, "\t[w] toggle display weighted/count[E]r. \t(%d)\n", top.display_weighted ? 1 : 0); - fprintf(stdout, "\t[K] hide kernel_symbols symbols. \t(%s)\n", top.hide_kernel_symbols ? "yes" : "no"); @@ -465,7 +442,6 @@ static int key_mapped(int c) case 'S': return 1; case 'E': - case 'w': return top.evlist->nr_entries > 1 ? 1 : 0; default: break; @@ -474,7 +450,7 @@ static int key_mapped(int c) return 0; } -static void handle_keypress(struct perf_session *session, int c) +static void handle_keypress(int c) { if (!key_mapped(c)) { struct pollfd stdin_poll = { .fd = 0, .events = POLLIN }; @@ -550,7 +526,7 @@ static void handle_keypress(struct perf_session *session, int c) case 'Q': printf("exiting.\n"); if (dump_symtab) - perf_session__fprintf_dsos(session, stderr); + perf_session__fprintf_dsos(top.session, stderr); exit(0); case 's': prompt_symbol(&top.sym_filter_entry, "Enter details symbol"); @@ -559,7 +535,7 @@ static void handle_keypress(struct perf_session *session, int c) if (!top.sym_filter_entry) break; else { - struct sym_entry *syme = top.sym_filter_entry; + struct hist_entry *syme = top.sym_filter_entry; top.sym_filter_entry = NULL; __zero_source_counters(syme); @@ -568,9 +544,6 @@ static void handle_keypress(struct perf_session *session, int c) case 'U': top.hide_user_symbols = !top.hide_user_symbols; break; - case 'w': - top.display_weighted = ~top.display_weighted; - break; case 'z': top.zero = !top.zero; break; @@ -579,19 +552,30 @@ static void handle_keypress(struct perf_session *session, int c) } } +static void perf_top__sort_new_samples(void *arg) +{ + struct perf_top *t = arg; + perf_top__reset_sample_counters(t); + + if (t->evlist->selected != NULL) + t->sym_evsel = t->evlist->selected; + + hists__collapse_resort_threaded(&t->sym_evsel->hists); + hists__output_resort_threaded(&t->sym_evsel->hists); + hists__decay_entries_threaded(&t->sym_evsel->hists, + top.hide_user_symbols, + top.hide_kernel_symbols); +} + static void *display_thread_tui(void *arg __used) { - int err = 0; - pthread_mutex_lock(&top.active_symbols_lock); - while (list_empty(&top.active_symbols)) { - err = pthread_cond_wait(&top.active_symbols_cond, - &top.active_symbols_lock); - if (err) - break; - } - pthread_mutex_unlock(&top.active_symbols_lock); - if (!err) - perf_top__tui_browser(&top); + const char *help = "For a higher level overview, try: perf top --sort comm,dso"; + + perf_top__sort_new_samples(&top); + perf_evlist__tui_browse_hists(top.evlist, help, + perf_top__sort_new_samples, + &top, top.delay_secs); + exit_browser(0); exit(0); return NULL; @@ -602,7 +586,6 @@ static void *display_thread(void *arg __used) struct pollfd stdin_poll = { .fd = 0, .events = POLLIN }; struct termios tc, save; int delay_msecs, c; - struct perf_session *session = (struct perf_session *) arg; tcgetattr(0, &save); tc = save; @@ -610,20 +593,35 @@ static void *display_thread(void *arg __used) 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); - do { - print_sym_table(session); - } while (!poll(&stdin_poll, 1, delay_msecs) == 1); - + while (1) { + print_sym_table(); + /* + * Either timeout expired or we got an EINTR due to SIGWINCH, + * refresh screen in both cases. + */ + switch (poll(&stdin_poll, 1, delay_msecs)) { + case 0: + continue; + case -1: + if (errno == EINTR) + continue; + /* Fall trhu */ + default: + goto process_hotkey; + } + } +process_hotkey: c = getc(stdin); tcsetattr(0, TCSAFLUSH, &save); - handle_keypress(session, c); + handle_keypress(c); goto repeat; return NULL; @@ -644,9 +642,8 @@ static const char *skip_symbols[] = { NULL }; -static int symbol_filter(struct map *map, struct symbol *sym) +static int symbol_filter(struct map *map __used, struct symbol *sym) { - struct sym_entry *syme; const char *name = sym->name; int i; @@ -666,16 +663,6 @@ static int symbol_filter(struct map *map, struct symbol *sym) strstr(name, "_text_end")) return 1; - syme = symbol__priv(sym); - syme->map = map; - symbol__annotate_init(map, sym); - - if (!top.sym_filter_entry && sym_filter && !strcmp(name, sym_filter)) { - /* schedule initial sym_filter_entry setup */ - sym_filter_entry_sched = syme; - sym_filter = NULL; - } - for (i = 0; skip_symbols[i]; i++) { if (!strcmp(skip_symbols[i], name)) { sym->ignore = true; @@ -687,13 +674,15 @@ static int symbol_filter(struct map *map, struct symbol *sym) } static void perf_event__process_sample(const union perf_event *event, + struct perf_evsel *evsel, struct perf_sample *sample, struct perf_session *session) { + struct symbol *parent = NULL; u64 ip = event->ip.ip; - struct sym_entry *syme; struct addr_location al; struct machine *machine; + int err; u8 origin = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; ++top.samples; @@ -782,51 +771,43 @@ static void perf_event__process_sample(const union perf_event *event, sleep(5); vmlinux_warned = true; } - - return; } - /* let's see, whether we need to install initial sym_filter_entry */ - if (sym_filter_entry_sched) { - top.sym_filter_entry = sym_filter_entry_sched; - sym_filter_entry_sched = NULL; - if (parse_source(top.sym_filter_entry) < 0) { - struct symbol *sym = sym_entry__symbol(top.sym_filter_entry); - - pr_err("Can't annotate %s", sym->name); - if (top.sym_filter_entry->map->dso->symtab_type == SYMTAB__KALLSYMS) { - pr_err(": No vmlinux file was found in the path:\n"); - machine__fprintf_vmlinux_path(machine, stderr); - } else - pr_err(".\n"); - exit(1); + if (al.sym == NULL || !al.sym->ignore) { + struct hist_entry *he; + + if ((sort__has_parent || symbol_conf.use_callchain) && + sample->callchain) { + err = perf_session__resolve_callchain(session, al.thread, + sample->callchain, &parent); + if (err) + return; } - } - syme = symbol__priv(al.sym); - if (!al.sym->ignore) { - struct perf_evsel *evsel; + he = perf_session__add_hist_entry(session, &al, sample, evsel); + if (he == NULL) { + pr_err("Problem incrementing symbol period, skipping event\n"); + return; + } - evsel = perf_evlist__id2evsel(top.evlist, sample->id); - assert(evsel != NULL); - syme->count[evsel->idx]++; - record_precise_ip(syme, evsel->idx, ip); - pthread_mutex_lock(&top.active_symbols_lock); - if (list_empty(&syme->node) || !syme->node.next) { - static bool first = true; - __list_insert_active_sym(syme); - if (first) { - pthread_cond_broadcast(&top.active_symbols_cond); - first = false; - } + if (symbol_conf.use_callchain) { + err = callchain_append(he->callchain, &session->callchain_cursor, + sample->period); + if (err) + return; } - pthread_mutex_unlock(&top.active_symbols_lock); + + if (sort_has_symbols) + record_precise_ip(he, evsel->idx, ip); } + + return; } static void perf_session__mmap_read_idx(struct perf_session *self, int idx) { struct perf_sample sample; + struct perf_evsel *evsel; union perf_event *event; int ret; @@ -837,10 +818,16 @@ static void perf_session__mmap_read_idx(struct perf_session *self, int idx) continue; } + evsel = perf_evlist__id2evsel(self->evlist, sample.id); + assert(evsel != NULL); + if (event->header.type == PERF_RECORD_SAMPLE) - perf_event__process_sample(event, &sample, self); - else + perf_event__process_sample(event, evsel, &sample, self); + else if (event->header.type < PERF_RECORD_MAX) { + hists__inc_nr_events(&evsel->hists, event->header.type); perf_event__process(event, &sample, self); + } else + ++self->hists.stats.nr_unknown_events; } } @@ -854,10 +841,16 @@ static void perf_session__mmap_read(struct perf_session *self) static void start_counters(struct perf_evlist *evlist) { - struct perf_evsel *counter; + struct perf_evsel *counter, *first; + + first = list_entry(evlist->entries.next, struct perf_evsel, node); list_for_each_entry(counter, &evlist->entries, node) { struct perf_event_attr *attr = &counter->attr; + struct xyarray *group_fd = NULL; + + if (group && counter != first) + group_fd = first->fd; attr->sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID; @@ -872,16 +865,29 @@ static void start_counters(struct perf_evlist *evlist) attr->read_format |= PERF_FORMAT_ID; } + if (symbol_conf.use_callchain) + attr->sample_type |= PERF_SAMPLE_CALLCHAIN; + attr->mmap = 1; + attr->comm = 1; attr->inherit = inherit; +retry_sample_id: + attr->sample_id_all = sample_id_all_avail ? 1 : 0; try_again: if (perf_evsel__open(counter, top.evlist->cpus, - top.evlist->threads, group) < 0) { + top.evlist->threads, group, + group_fd) < 0) { int err = errno; if (err == EPERM || err == EACCES) { - ui__warning_paranoid(); + ui__error_paranoid(); goto out_err; + } else if (err == EINVAL && sample_id_all_avail) { + /* + * Old kernel, no attr->sample_id_type_all field + */ + sample_id_all_avail = false; + goto retry_sample_id; } /* * If it's cycles then fall back to hrtimer @@ -927,35 +933,56 @@ out_err: exit(0); } +static int setup_sample_type(void) +{ + if (!sort_has_symbols) { + if (symbol_conf.use_callchain) { + ui__warning("Selected -g but \"sym\" not present in --sort/-s."); + return -EINVAL; + } + } else if (!dont_use_callchains && callchain_param.mode != CHAIN_NONE) { + if (callchain_register_param(&callchain_param) < 0) { + ui__warning("Can't register callchain params.\n"); + return -EINVAL; + } + } + + return 0; +} + static int __cmd_top(void) { pthread_t thread; - int ret __used; + int ret; /* * FIXME: perf_session__new should allow passing a O_MMAP, so that all this * mmap reading, etc is encapsulated in it. Use O_WRONLY for now. */ - struct perf_session *session = perf_session__new(NULL, O_WRONLY, false, false, NULL); - if (session == NULL) + top.session = perf_session__new(NULL, O_WRONLY, false, false, NULL); + if (top.session == NULL) return -ENOMEM; + ret = setup_sample_type(); + if (ret) + goto out_delete; + if (top.target_tid != -1) perf_event__synthesize_thread_map(top.evlist->threads, - perf_event__process, session); + perf_event__process, top.session); else - perf_event__synthesize_threads(perf_event__process, session); + perf_event__synthesize_threads(perf_event__process, top.session); start_counters(top.evlist); - session->evlist = top.evlist; - perf_session__update_sample_type(session); + top.session->evlist = top.evlist; + perf_session__update_sample_type(top.session); /* Wait for a minimal set of events before starting the snapshot */ poll(top.evlist->pollfd, top.evlist->nr_fds, 100); - perf_session__mmap_read(session); + perf_session__mmap_read(top.session); if (pthread_create(&thread, NULL, (use_browser > 0 ? display_thread_tui : - display_thread), session)) { + display_thread), NULL)) { printf("Could not create display thread.\n"); exit(-1); } @@ -973,12 +1000,96 @@ static int __cmd_top(void) while (1) { u64 hits = top.samples; - perf_session__mmap_read(session); + perf_session__mmap_read(top.session); if (hits == top.samples) ret = poll(top.evlist->pollfd, top.evlist->nr_fds, 100); } +out_delete: + perf_session__delete(top.session); + top.session = NULL; + + return 0; +} + +static int +parse_callchain_opt(const struct option *opt __used, const char *arg, + int unset) +{ + char *tok, *tok2; + char *endptr; + + /* + * --no-call-graph + */ + if (unset) { + dont_use_callchains = true; + return 0; + } + + symbol_conf.use_callchain = true; + + if (!arg) + return 0; + + tok = strtok((char *)arg, ","); + if (!tok) + return -1; + + /* get the output mode */ + if (!strncmp(tok, "graph", strlen(arg))) + callchain_param.mode = CHAIN_GRAPH_ABS; + + else if (!strncmp(tok, "flat", strlen(arg))) + callchain_param.mode = CHAIN_FLAT; + + else if (!strncmp(tok, "fractal", strlen(arg))) + callchain_param.mode = CHAIN_GRAPH_REL; + + else if (!strncmp(tok, "none", strlen(arg))) { + callchain_param.mode = CHAIN_NONE; + symbol_conf.use_callchain = false; + + return 0; + } + + else + return -1; + + /* get the min percentage */ + tok = strtok(NULL, ","); + if (!tok) + goto setup; + + callchain_param.min_percent = strtod(tok, &endptr); + if (tok == endptr) + return -1; + + /* get the print limit */ + tok2 = strtok(NULL, ","); + if (!tok2) + goto setup; + + if (tok2[0] != 'c') { + callchain_param.print_limit = strtod(tok2, &endptr); + tok2 = strtok(NULL, ","); + if (!tok2) + goto setup; + } + + /* get the call chain order */ + if (!strcmp(tok2, "caller")) + callchain_param.order = ORDER_CALLER; + else if (!strcmp(tok2, "callee")) + callchain_param.order = ORDER_CALLEE; + else + return -1; +setup: + if (callchain_register_param(&callchain_param) < 0) { + fprintf(stderr, "Can't register callchain params\n"); + return -1; + } return 0; } @@ -990,7 +1101,7 @@ static const char * const top_usage[] = { static const struct option options[] = { OPT_CALLBACK('e', "event", &top.evlist, "event", "event selector. use 'perf list' to list available events", - parse_events), + parse_events_option), OPT_INTEGER('c', "count", &default_interval, "event period to sample"), OPT_INTEGER('p', "pid", &top.target_pid, @@ -1018,7 +1129,7 @@ static const struct option options[] = { "put the counters into a counter group"), OPT_BOOLEAN('i', "inherit", &inherit, "child tasks inherit counters"), - OPT_STRING('s', "sym-annotate", &sym_filter, "symbol name", + OPT_STRING(0, "sym-annotate", &sym_filter, "symbol name", "symbol to annotate"), OPT_BOOLEAN('z', "zero", &top.zero, "zero history across updates"), @@ -1032,6 +1143,28 @@ static const struct option options[] = { OPT_BOOLEAN(0, "stdio", &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"), + OPT_BOOLEAN('n', "show-nr-samples", &symbol_conf.show_nr_samples, + "Show a column with the number of samples"), + OPT_CALLBACK_DEFAULT('G', "call-graph", NULL, "output_type,min_percent, call_order", + "Display callchains using output_type (graph, flat, fractal, or none), min percent threshold and callchain order. " + "Default: fractal,0.5,callee", &parse_callchain_opt, + callchain_default_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('M', "disassembler-style", &disassembler_style, "disassembler style", + "Specify disassembler style (e.g. -M intel for intel syntax)"), OPT_END() }; @@ -1044,18 +1177,16 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) if (top.evlist == NULL) return -ENOMEM; - page_size = sysconf(_SC_PAGE_SIZE); + symbol_conf.exclude_other = false; argc = parse_options(argc, argv, options, top_usage, 0); if (argc) usage_with_options(top_usage, options); - /* - * XXX For now start disabled, only using TUI if explicitely asked for. - * Change that when handle_keys equivalent gets written, live annotation - * done, etc. - */ - use_browser = 0; + if (sort_order == default_sort_order) + sort_order = "dso,symbol"; + + setup_sorting(top_usage, options); if (use_stdio) use_browser = 0; @@ -1118,13 +1249,22 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) top.sym_evsel = list_entry(top.evlist->entries.next, struct perf_evsel, node); - symbol_conf.priv_size = (sizeof(struct sym_entry) + sizeof(struct annotation) + - (top.evlist->nr_entries + 1) * sizeof(unsigned long)); + symbol_conf.priv_size = sizeof(struct annotation); symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL); if (symbol__init() < 0) return -1; + sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", stdout); + sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, "comm", stdout); + sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", stdout); + + /* + * Avoid annotation data structures overhead when symbols aren't on the + * sort list. + */ + sort_has_symbols = sort_sym.list.next != NULL; + get_term_dimensions(&winsize); if (top.print_entries == 0) { update_print_entries(&winsize); diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h index 4702e2443a8e..b382bd551aac 100644 --- a/tools/perf/builtin.h +++ b/tools/perf/builtin.h @@ -4,7 +4,6 @@ #include "util/util.h" #include "util/strbuf.h" -extern const char perf_version_string[]; extern const char perf_usage_string[]; extern const char perf_more_info_string[]; diff --git a/tools/perf/perf.c b/tools/perf/perf.c index ec635b7cc8ea..73d0cac8b67e 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -427,6 +427,24 @@ static void get_debugfs_mntpt(void) debugfs_mntpt[0] = '\0'; } +static void pthread__block_sigwinch(void) +{ + sigset_t set; + + sigemptyset(&set); + sigaddset(&set, SIGWINCH); + pthread_sigmask(SIG_BLOCK, &set, NULL); +} + +void pthread__unblock_sigwinch(void) +{ + sigset_t set; + + sigemptyset(&set); + sigaddset(&set, SIGWINCH); + pthread_sigmask(SIG_UNBLOCK, &set, NULL); +} + int main(int argc, const char **argv) { const char *cmd; @@ -480,6 +498,12 @@ int main(int argc, const char **argv) * time. */ setup_path(); + /* + * Block SIGWINCH notifications so that the thread that wants it can + * unblock and get syscalls like select interrupted instead of waiting + * forever while the signal goes to some other non interested thread. + */ + pthread__block_sigwinch(); while (1) { static int done_help; diff --git a/tools/perf/perf.h b/tools/perf/perf.h index a5fc660c1f12..914c895510f7 100644 --- a/tools/perf/perf.h +++ b/tools/perf/perf.h @@ -9,18 +9,21 @@ void get_term_dimensions(struct winsize *ws); #include "../../arch/x86/include/asm/unistd.h" #define rmb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory") #define cpu_relax() asm volatile("rep; nop" ::: "memory"); +#define CPUINFO_PROC "model name" #endif #if defined(__x86_64__) #include "../../arch/x86/include/asm/unistd.h" #define rmb() asm volatile("lfence" ::: "memory") #define cpu_relax() asm volatile("rep; nop" ::: "memory"); +#define CPUINFO_PROC "model name" #endif #ifdef __powerpc__ #include "../../arch/powerpc/include/asm/unistd.h" #define rmb() asm volatile ("sync" ::: "memory") #define cpu_relax() asm volatile ("" ::: "memory"); +#define CPUINFO_PROC "cpu" #endif #ifdef __s390__ @@ -37,30 +40,35 @@ void get_term_dimensions(struct winsize *ws); # define rmb() asm volatile("" ::: "memory") #endif #define cpu_relax() asm volatile("" ::: "memory") +#define CPUINFO_PROC "cpu type" #endif #ifdef __hppa__ #include "../../arch/parisc/include/asm/unistd.h" #define rmb() asm volatile("" ::: "memory") #define cpu_relax() asm volatile("" ::: "memory"); +#define CPUINFO_PROC "cpu" #endif #ifdef __sparc__ #include "../../arch/sparc/include/asm/unistd.h" #define rmb() asm volatile("":::"memory") #define cpu_relax() asm volatile("":::"memory") +#define CPUINFO_PROC "cpu" #endif #ifdef __alpha__ #include "../../arch/alpha/include/asm/unistd.h" #define rmb() asm volatile("mb" ::: "memory") #define cpu_relax() asm volatile("" ::: "memory") +#define CPUINFO_PROC "cpu model" #endif #ifdef __ia64__ #include "../../arch/ia64/include/asm/unistd.h" #define rmb() asm volatile ("mf" ::: "memory") #define cpu_relax() asm volatile ("hint @pause" ::: "memory") +#define CPUINFO_PROC "model name" #endif #ifdef __arm__ @@ -71,6 +79,7 @@ void get_term_dimensions(struct winsize *ws); */ #define rmb() ((void(*)(void))0xffff0fa0)() #define cpu_relax() asm volatile("":::"memory") +#define CPUINFO_PROC "Processor" #endif #ifdef __mips__ @@ -83,6 +92,7 @@ void get_term_dimensions(struct winsize *ws); : /* no input */ \ : "memory") #define cpu_relax() asm volatile("" ::: "memory") +#define CPUINFO_PROC "cpu model" #endif #include <time.h> @@ -171,5 +181,8 @@ struct ip_callchain { }; extern bool perf_host, perf_guest; +extern const char perf_version_string[]; + +void pthread__unblock_sigwinch(void); #endif diff --git a/tools/perf/scripts/python/bin/net_dropmonitor-record b/tools/perf/scripts/python/bin/net_dropmonitor-record new file mode 100755 index 000000000000..423fb81dadae --- /dev/null +++ b/tools/perf/scripts/python/bin/net_dropmonitor-record @@ -0,0 +1,2 @@ +#!/bin/bash +perf record -e skb:kfree_skb $@ diff --git a/tools/perf/scripts/python/bin/net_dropmonitor-report b/tools/perf/scripts/python/bin/net_dropmonitor-report new file mode 100755 index 000000000000..8d698f5a06aa --- /dev/null +++ b/tools/perf/scripts/python/bin/net_dropmonitor-report @@ -0,0 +1,4 @@ +#!/bin/bash +# description: display a table of dropped frames + +perf script -s "$PERF_EXEC_PATH"/scripts/python/net_dropmonitor.py $@ diff --git a/tools/perf/scripts/python/net_dropmonitor.py b/tools/perf/scripts/python/net_dropmonitor.py new file mode 100755 index 000000000000..a4ffc9500023 --- /dev/null +++ b/tools/perf/scripts/python/net_dropmonitor.py @@ -0,0 +1,72 @@ +# Monitor the system for dropped packets and proudce a report of drop locations and counts + +import os +import sys + +sys.path.append(os.environ['PERF_EXEC_PATH'] + \ + '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') + +from perf_trace_context import * +from Core import * +from Util import * + +drop_log = {} +kallsyms = [] + +def get_kallsyms_table(): + global kallsyms + try: + f = open("/proc/kallsyms", "r") + linecount = 0 + for line in f: + linecount = linecount+1 + f.seek(0) + except: + return + + + j = 0 + for line in f: + loc = int(line.split()[0], 16) + name = line.split()[2] + j = j +1 + if ((j % 100) == 0): + print "\r" + str(j) + "/" + str(linecount), + kallsyms.append({ 'loc': loc, 'name' : name}) + + print "\r" + str(j) + "/" + str(linecount) + kallsyms.sort() + return + +def get_sym(sloc): + loc = int(sloc) + for i in kallsyms: + if (i['loc'] >= loc): + return (i['name'], i['loc']-loc) + return (None, 0) + +def print_drop_table(): + print "%25s %25s %25s" % ("LOCATION", "OFFSET", "COUNT") + for i in drop_log.keys(): + (sym, off) = get_sym(i) + if sym == None: + sym = i + print "%25s %25s %25s" % (sym, off, drop_log[i]) + + +def trace_begin(): + print "Starting trace (Ctrl-C to dump results)" + +def trace_end(): + print "Gathering kallsyms data" + get_kallsyms_table() + print_drop_table() + +# called from perf, when it finds a correspoinding event +def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, + skbaddr, protocol, location): + slocation = str(location) + try: + drop_log[slocation] = drop_log[slocation] + 1 + except: + drop_log[slocation] = 1 diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index e01af2b1a469..119e996035c8 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -16,6 +16,8 @@ #include "annotate.h" #include <pthread.h> +const char *disassembler_style; + int symbol__annotate_init(struct map *map __used, struct symbol *sym) { struct annotation *notes = symbol__annotation(sym); @@ -308,9 +310,12 @@ fallback: } err = -ENOENT; dso->annotate_warned = 1; - pr_err("Can't annotate %s: No vmlinux file%s was found in the " - "path.\nPlease use 'perf buildid-cache -av vmlinux' or " - "--vmlinux vmlinux.\n", + pr_err("Can't annotate %s:\n\n" + "No vmlinux file%s\nwas found in the path.\n\n" + "Please use:\n\n" + " perf buildid-cache -av vmlinux\n\n" + "or:\n\n" + " --vmlinux vmlinux", sym->name, build_id_msg ?: ""); goto out_free_filename; } @@ -323,10 +328,15 @@ fallback: dso, dso->long_name, sym, sym->name); snprintf(command, sizeof(command), - "objdump --start-address=0x%016" PRIx64 - " --stop-address=0x%016" PRIx64 " -dS -C %s|grep -v %s|expand", + "objdump %s%s --start-address=0x%016" PRIx64 + " --stop-address=0x%016" PRIx64 + " -d %s %s -C %s|grep -v %s|expand", + disassembler_style ? "-M " : "", + disassembler_style ? disassembler_style : "", map__rip_2objdump(map, sym->start), map__rip_2objdump(map, sym->end), + symbol_conf.annotate_asm_raw ? "" : "--no-show-raw", + symbol_conf.annotate_src ? "-S" : "", symfs_filename, filename); pr_debug("Executing: %s\n", command); diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h index c2c286896801..d9072523d342 100644 --- a/tools/perf/util/annotate.h +++ b/tools/perf/util/annotate.h @@ -91,13 +91,18 @@ int symbol__tty_annotate(struct symbol *sym, struct map *map, int evidx, #ifdef NO_NEWT_SUPPORT static inline int symbol__tui_annotate(struct symbol *sym __used, struct map *map __used, - int evidx __used, int refresh __used) + int evidx __used, + void(*timer)(void *arg) __used, + void *arg __used, int delay_secs __used) { return 0; } #else int symbol__tui_annotate(struct symbol *sym, struct map *map, int evidx, - int refresh); + int nr_events, void(*timer)(void *arg), void *arg, + int delay_secs); #endif +extern const char *disassembler_style; + #endif /* __PERF_ANNOTATE_H */ diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index 1a79df9f739f..9b4ff16cac96 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -14,6 +14,11 @@ enum chain_mode { CHAIN_GRAPH_REL }; +enum chain_order { + ORDER_CALLER, + ORDER_CALLEE +}; + struct callchain_node { struct callchain_node *parent; struct list_head siblings; @@ -41,6 +46,7 @@ struct callchain_param { u32 print_limit; double min_percent; sort_chain_func_t sort; + enum chain_order order; }; struct callchain_list { diff --git a/tools/perf/util/color.c b/tools/perf/util/color.c index e191eb9a667f..521c38a79190 100644 --- a/tools/perf/util/color.c +++ b/tools/perf/util/color.c @@ -200,7 +200,7 @@ static int __color_vfprintf(FILE *fp, const char *color, const char *fmt, * Auto-detect: */ if (perf_use_color_default < 0) { - if (isatty(1) || pager_in_use()) + if (isatty(fileno(fp)) || pager_in_use()) perf_use_color_default = 1; else perf_use_color_default = 0; diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index e02d78cae70f..80d9598db31a 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -341,7 +341,7 @@ const char *perf_config_dirname(const char *name, const char *value) static int perf_default_core_config(const char *var __used, const char *value __used) { - /* Add other config variables here and to Documentation/config.txt. */ + /* Add other config variables here. */ return 0; } @@ -350,7 +350,7 @@ int perf_default_config(const char *var, const char *value, void *dummy __used) if (!prefixcmp(var, "core.")) return perf_default_core_config(var, value); - /* Add other config variables here and to Documentation/config.txt. */ + /* Add other config variables here. */ return 0; } @@ -399,7 +399,6 @@ static int perf_config_global(void) int perf_config(config_fn_t fn, void *data) { int ret = 0, found = 0; - char *repo_config = NULL; const char *home = NULL; /* Setting $PERF_CONFIG makes perf read _only_ the given config file. */ @@ -414,19 +413,32 @@ int perf_config(config_fn_t fn, void *data) home = getenv("HOME"); if (perf_config_global() && home) { char *user_config = strdup(mkpath("%s/.perfconfig", home)); - if (!access(user_config, R_OK)) { - ret += perf_config_from_file(fn, user_config, data); - found += 1; + struct stat st; + + if (user_config == NULL) { + warning("Not enough memory to process %s/.perfconfig, " + "ignoring it.", home); + goto out; } - free(user_config); - } - repo_config = perf_pathdup("config"); - if (!access(repo_config, R_OK)) { - ret += perf_config_from_file(fn, repo_config, data); + if (stat(user_config, &st) < 0) + goto out_free; + + if (st.st_uid && (st.st_uid != geteuid())) { + warning("File %s not owned by current user or root, " + "ignoring it.", user_config); + goto out_free; + } + + if (!st.st_size) + goto out_free; + + ret += perf_config_from_file(fn, user_config, data); found += 1; +out_free: + free(user_config); } - free(repo_config); +out: if (found == 0) return -1; return ret; diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 155749d74350..26817daa2961 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -47,19 +47,20 @@ int dump_printf(const char *fmt, ...) } #ifdef NO_NEWT_SUPPORT -void ui__warning(const char *format, ...) +int ui__warning(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); + return 0; } #endif -void ui__warning_paranoid(void) +int ui__error_paranoid(void) { - ui__warning("Permission error - are you root?\n" + return ui__error("Permission error - are you root?\n" "Consider tweaking /proc/sys/kernel/perf_event_paranoid:\n" " -1 - Not paranoid at all\n" " 0 - Disallow raw tracepoint access for unpriv\n" diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index fd53db47e3de..f2ce88d04f54 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -19,23 +19,18 @@ static inline int ui_helpline__show_help(const char *format __used, va_list ap _ return 0; } -static inline struct ui_progress *ui_progress__new(const char *title __used, - u64 total __used) -{ - return (struct ui_progress *)1; -} - -static inline void ui_progress__update(struct ui_progress *self __used, - u64 curr __used) {} +static inline void ui_progress__update(u64 curr __used, u64 total __used, + const char *title __used) {} -static inline void ui_progress__delete(struct ui_progress *self __used) {} +#define ui__error(format, arg...) ui__warning(format, ##arg) #else extern char ui_helpline__last_msg[]; int ui_helpline__show_help(const char *format, va_list ap); #include "ui/progress.h" +int ui__error(const char *format, ...) __attribute__((format(printf, 1, 2))); #endif -void ui__warning(const char *format, ...) __attribute__((format(printf, 1, 2))); -void ui__warning_paranoid(void); +int ui__warning(const char *format, ...) __attribute__((format(printf, 1, 2))); +int ui__error_paranoid(void); #endif /* __PERF_DEBUG_H */ diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c new file mode 100644 index 000000000000..ee51e9b4dc09 --- /dev/null +++ b/tools/perf/util/dwarf-aux.c @@ -0,0 +1,843 @@ +/* + * dwarf-aux.c : libdw auxiliary interfaces + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <stdbool.h> +#include "util.h" +#include "debug.h" +#include "dwarf-aux.h" + +/** + * cu_find_realpath - Find the realpath of the target file + * @cu_die: A DIE(dwarf information entry) of CU(compilation Unit) + * @fname: The tail filename of the target file + * + * Find the real(long) path of @fname in @cu_die. + */ +const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname) +{ + Dwarf_Files *files; + size_t nfiles, i; + const char *src = NULL; + int ret; + + if (!fname) + return NULL; + + ret = dwarf_getsrcfiles(cu_die, &files, &nfiles); + if (ret != 0) + return NULL; + + for (i = 0; i < nfiles; i++) { + src = dwarf_filesrc(files, i, NULL, NULL); + if (strtailcmp(src, fname) == 0) + break; + } + if (i == nfiles) + return NULL; + return src; +} + +/** + * cu_get_comp_dir - Get the path of compilation directory + * @cu_die: a CU DIE + * + * Get the path of compilation directory of given @cu_die. + * Since this depends on DW_AT_comp_dir, older gcc will not + * embedded it. In that case, this returns NULL. + */ +const char *cu_get_comp_dir(Dwarf_Die *cu_die) +{ + Dwarf_Attribute attr; + if (dwarf_attr(cu_die, DW_AT_comp_dir, &attr) == NULL) + return NULL; + return dwarf_formstring(&attr); +} + +/** + * cu_find_lineinfo - Get a line number and file name for given address + * @cu_die: a CU DIE + * @addr: An address + * @fname: a pointer which returns the file name string + * @lineno: a pointer which returns the line number + * + * Find a line number and file name for @addr in @cu_die. + */ +int cu_find_lineinfo(Dwarf_Die *cu_die, unsigned long addr, + const char **fname, int *lineno) +{ + Dwarf_Line *line; + Dwarf_Addr laddr; + + line = dwarf_getsrc_die(cu_die, (Dwarf_Addr)addr); + if (line && dwarf_lineaddr(line, &laddr) == 0 && + addr == (unsigned long)laddr && dwarf_lineno(line, lineno) == 0) { + *fname = dwarf_linesrc(line, NULL, NULL); + if (!*fname) + /* line number is useless without filename */ + *lineno = 0; + } + + return *lineno ?: -ENOENT; +} + +static int __die_find_inline_cb(Dwarf_Die *die_mem, void *data); + +/** + * cu_walk_functions_at - Walk on function DIEs at given address + * @cu_die: A CU DIE + * @addr: An address + * @callback: A callback which called with found DIEs + * @data: A user data + * + * Walk on function DIEs at given @addr in @cu_die. Passed DIEs + * should be subprogram or inlined-subroutines. + */ +int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr, + int (*callback)(Dwarf_Die *, void *), void *data) +{ + Dwarf_Die die_mem; + Dwarf_Die *sc_die; + int ret = -ENOENT; + + /* Inlined function could be recursive. Trace it until fail */ + for (sc_die = die_find_realfunc(cu_die, addr, &die_mem); + sc_die != NULL; + sc_die = die_find_child(sc_die, __die_find_inline_cb, &addr, + &die_mem)) { + ret = callback(sc_die, data); + if (ret) + break; + } + + return ret; + +} + +/** + * die_compare_name - Compare diename and tname + * @dw_die: a DIE + * @tname: a string of target name + * + * Compare the name of @dw_die and @tname. Return false if @dw_die has no name. + */ +bool die_compare_name(Dwarf_Die *dw_die, const char *tname) +{ + const char *name; + name = dwarf_diename(dw_die); + return name ? (strcmp(tname, name) == 0) : false; +} + +/** + * die_get_call_lineno - Get callsite line number of inline-function instance + * @in_die: a DIE of an inlined function instance + * + * Get call-site line number of @in_die. This means from where the inline + * function is called. + */ +int die_get_call_lineno(Dwarf_Die *in_die) +{ + Dwarf_Attribute attr; + Dwarf_Word ret; + + if (!dwarf_attr(in_die, DW_AT_call_line, &attr)) + return -ENOENT; + + dwarf_formudata(&attr, &ret); + return (int)ret; +} + +/** + * die_get_type - Get type DIE + * @vr_die: a DIE of a variable + * @die_mem: where to store a type DIE + * + * Get a DIE of the type of given variable (@vr_die), and store + * it to die_mem. Return NULL if fails to get a type DIE. + */ +Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) +{ + Dwarf_Attribute attr; + + if (dwarf_attr_integrate(vr_die, DW_AT_type, &attr) && + dwarf_formref_die(&attr, die_mem)) + return die_mem; + else + return NULL; +} + +/* Get a type die, but skip qualifiers */ +static Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) +{ + int tag; + + do { + vr_die = die_get_type(vr_die, die_mem); + if (!vr_die) + break; + tag = dwarf_tag(vr_die); + } while (tag == DW_TAG_const_type || + tag == DW_TAG_restrict_type || + tag == DW_TAG_volatile_type || + tag == DW_TAG_shared_type); + + return vr_die; +} + +/** + * die_get_real_type - Get a type die, but skip qualifiers and typedef + * @vr_die: a DIE of a variable + * @die_mem: where to store a type DIE + * + * Get a DIE of the type of given variable (@vr_die), and store + * it to die_mem. Return NULL if fails to get a type DIE. + * If the type is qualifiers (e.g. const) or typedef, this skips it + * and tries to find real type (structure or basic types, e.g. int). + */ +Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) +{ + do { + vr_die = __die_get_real_type(vr_die, die_mem); + } while (vr_die && dwarf_tag(vr_die) == DW_TAG_typedef); + + return vr_die; +} + +/* Get attribute and translate it as a udata */ +static int die_get_attr_udata(Dwarf_Die *tp_die, unsigned int attr_name, + Dwarf_Word *result) +{ + Dwarf_Attribute attr; + + if (dwarf_attr(tp_die, attr_name, &attr) == NULL || + dwarf_formudata(&attr, result) != 0) + return -ENOENT; + + return 0; +} + +/* Get attribute and translate it as a sdata */ +static int die_get_attr_sdata(Dwarf_Die *tp_die, unsigned int attr_name, + Dwarf_Sword *result) +{ + Dwarf_Attribute attr; + + if (dwarf_attr(tp_die, attr_name, &attr) == NULL || + dwarf_formsdata(&attr, result) != 0) + return -ENOENT; + + return 0; +} + +/** + * die_is_signed_type - Check whether a type DIE is signed or not + * @tp_die: a DIE of a type + * + * Get the encoding of @tp_die and return true if the encoding + * is signed. + */ +bool die_is_signed_type(Dwarf_Die *tp_die) +{ + Dwarf_Word ret; + + if (die_get_attr_udata(tp_die, DW_AT_encoding, &ret)) + return false; + + return (ret == DW_ATE_signed_char || ret == DW_ATE_signed || + ret == DW_ATE_signed_fixed); +} + +/** + * die_get_data_member_location - Get the data-member offset + * @mb_die: a DIE of a member of a data structure + * @offs: The offset of the member in the data structure + * + * Get the offset of @mb_die in the data structure including @mb_die, and + * stores result offset to @offs. If any error occurs this returns errno. + */ +int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs) +{ + Dwarf_Attribute attr; + Dwarf_Op *expr; + size_t nexpr; + int ret; + + if (dwarf_attr(mb_die, DW_AT_data_member_location, &attr) == NULL) + return -ENOENT; + + if (dwarf_formudata(&attr, offs) != 0) { + /* DW_AT_data_member_location should be DW_OP_plus_uconst */ + ret = dwarf_getlocation(&attr, &expr, &nexpr); + if (ret < 0 || nexpr == 0) + return -ENOENT; + + if (expr[0].atom != DW_OP_plus_uconst || nexpr != 1) { + pr_debug("Unable to get offset:Unexpected OP %x (%zd)\n", + expr[0].atom, nexpr); + return -ENOTSUP; + } + *offs = (Dwarf_Word)expr[0].number; + } + return 0; +} + +/* Get the call file index number in CU DIE */ +static int die_get_call_fileno(Dwarf_Die *in_die) +{ + Dwarf_Sword idx; + + if (die_get_attr_sdata(in_die, DW_AT_call_file, &idx) == 0) + return (int)idx; + else + return -ENOENT; +} + +/* Get the declared file index number in CU DIE */ +static int die_get_decl_fileno(Dwarf_Die *pdie) +{ + Dwarf_Sword idx; + + if (die_get_attr_sdata(pdie, DW_AT_decl_file, &idx) == 0) + return (int)idx; + else + return -ENOENT; +} + +/** + * die_get_call_file - Get callsite file name of inlined function instance + * @in_die: a DIE of an inlined function instance + * + * Get call-site file name of @in_die. This means from which file the inline + * function is called. + */ +const char *die_get_call_file(Dwarf_Die *in_die) +{ + Dwarf_Die cu_die; + Dwarf_Files *files; + int idx; + + idx = die_get_call_fileno(in_die); + if (idx < 0 || !dwarf_diecu(in_die, &cu_die, NULL, NULL) || + dwarf_getsrcfiles(&cu_die, &files, NULL) != 0) + return NULL; + + return dwarf_filesrc(files, idx, NULL, NULL); +} + + +/** + * die_find_child - Generic DIE search function in DIE tree + * @rt_die: a root DIE + * @callback: a callback function + * @data: a user data passed to the callback function + * @die_mem: a buffer for result DIE + * + * Trace DIE tree from @rt_die and call @callback for each child DIE. + * If @callback returns DIE_FIND_CB_END, this stores the DIE into + * @die_mem and returns it. If @callback returns DIE_FIND_CB_CONTINUE, + * this continues to trace the tree. Optionally, @callback can return + * DIE_FIND_CB_CHILD and DIE_FIND_CB_SIBLING, those means trace only + * the children and trace only the siblings respectively. + * Returns NULL if @callback can't find any appropriate DIE. + */ +Dwarf_Die *die_find_child(Dwarf_Die *rt_die, + int (*callback)(Dwarf_Die *, void *), + void *data, Dwarf_Die *die_mem) +{ + Dwarf_Die child_die; + int ret; + + ret = dwarf_child(rt_die, die_mem); + if (ret != 0) + return NULL; + + do { + ret = callback(die_mem, data); + if (ret == DIE_FIND_CB_END) + return die_mem; + + if ((ret & DIE_FIND_CB_CHILD) && + die_find_child(die_mem, callback, data, &child_die)) { + memcpy(die_mem, &child_die, sizeof(Dwarf_Die)); + return die_mem; + } + } while ((ret & DIE_FIND_CB_SIBLING) && + dwarf_siblingof(die_mem, die_mem) == 0); + + return NULL; +} + +struct __addr_die_search_param { + Dwarf_Addr addr; + Dwarf_Die *die_mem; +}; + +/* die_find callback for non-inlined function search */ +static int __die_search_func_cb(Dwarf_Die *fn_die, void *data) +{ + struct __addr_die_search_param *ad = data; + + if (dwarf_tag(fn_die) == DW_TAG_subprogram && + dwarf_haspc(fn_die, ad->addr)) { + memcpy(ad->die_mem, fn_die, sizeof(Dwarf_Die)); + return DWARF_CB_ABORT; + } + return DWARF_CB_OK; +} + +/** + * die_find_realfunc - Search a non-inlined function at given address + * @cu_die: a CU DIE which including @addr + * @addr: target address + * @die_mem: a buffer for result DIE + * + * Search a non-inlined function DIE which includes @addr. Stores the + * DIE to @die_mem and returns it if found. Returns NULl if failed. + */ +Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr, + Dwarf_Die *die_mem) +{ + struct __addr_die_search_param ad; + ad.addr = addr; + ad.die_mem = die_mem; + /* dwarf_getscopes can't find subprogram. */ + if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0)) + return NULL; + else + return die_mem; +} + +/* die_find callback for inline function search */ +static int __die_find_inline_cb(Dwarf_Die *die_mem, void *data) +{ + Dwarf_Addr *addr = data; + + if (dwarf_tag(die_mem) == DW_TAG_inlined_subroutine && + dwarf_haspc(die_mem, *addr)) + return DIE_FIND_CB_END; + + return DIE_FIND_CB_CONTINUE; +} + +/** + * die_find_inlinefunc - Search an inlined function at given address + * @cu_die: a CU DIE which including @addr + * @addr: target address + * @die_mem: a buffer for result DIE + * + * Search an inlined function DIE which includes @addr. Stores the + * DIE to @die_mem and returns it if found. Returns NULl if failed. + * If several inlined functions are expanded recursively, this trace + * it and returns deepest one. + */ +Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr, + Dwarf_Die *die_mem) +{ + Dwarf_Die tmp_die; + + sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr, &tmp_die); + if (!sp_die) + return NULL; + + /* Inlined function could be recursive. Trace it until fail */ + while (sp_die) { + memcpy(die_mem, sp_die, sizeof(Dwarf_Die)); + sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr, + &tmp_die); + } + + return die_mem; +} + +struct __instance_walk_param { + void *addr; + int (*callback)(Dwarf_Die *, void *); + void *data; + int retval; +}; + +static int __die_walk_instances_cb(Dwarf_Die *inst, void *data) +{ + struct __instance_walk_param *iwp = data; + Dwarf_Attribute attr_mem; + Dwarf_Die origin_mem; + Dwarf_Attribute *attr; + Dwarf_Die *origin; + int tmp; + + attr = dwarf_attr(inst, DW_AT_abstract_origin, &attr_mem); + if (attr == NULL) + return DIE_FIND_CB_CONTINUE; + + origin = dwarf_formref_die(attr, &origin_mem); + if (origin == NULL || origin->addr != iwp->addr) + return DIE_FIND_CB_CONTINUE; + + /* Ignore redundant instances */ + if (dwarf_tag(inst) == DW_TAG_inlined_subroutine) { + dwarf_decl_line(origin, &tmp); + if (die_get_call_lineno(inst) == tmp) { + tmp = die_get_decl_fileno(origin); + if (die_get_call_fileno(inst) == tmp) + return DIE_FIND_CB_CONTINUE; + } + } + + iwp->retval = iwp->callback(inst, iwp->data); + + return (iwp->retval) ? DIE_FIND_CB_END : DIE_FIND_CB_CONTINUE; +} + +/** + * die_walk_instances - Walk on instances of given DIE + * @or_die: an abstract original DIE + * @callback: a callback function which is called with instance DIE + * @data: user data + * + * Walk on the instances of give @in_die. @in_die must be an inlined function + * declartion. This returns the return value of @callback if it returns + * non-zero value, or -ENOENT if there is no instance. + */ +int die_walk_instances(Dwarf_Die *or_die, int (*callback)(Dwarf_Die *, void *), + void *data) +{ + Dwarf_Die cu_die; + Dwarf_Die die_mem; + struct __instance_walk_param iwp = { + .addr = or_die->addr, + .callback = callback, + .data = data, + .retval = -ENOENT, + }; + + if (dwarf_diecu(or_die, &cu_die, NULL, NULL) == NULL) + return -ENOENT; + + die_find_child(&cu_die, __die_walk_instances_cb, &iwp, &die_mem); + + return iwp.retval; +} + +/* Line walker internal parameters */ +struct __line_walk_param { + bool recursive; + line_walk_callback_t callback; + void *data; + int retval; +}; + +static int __die_walk_funclines_cb(Dwarf_Die *in_die, void *data) +{ + struct __line_walk_param *lw = data; + Dwarf_Addr addr = 0; + const char *fname; + int lineno; + + if (dwarf_tag(in_die) == DW_TAG_inlined_subroutine) { + fname = die_get_call_file(in_die); + lineno = die_get_call_lineno(in_die); + if (fname && lineno > 0 && dwarf_entrypc(in_die, &addr) == 0) { + lw->retval = lw->callback(fname, lineno, addr, lw->data); + if (lw->retval != 0) + return DIE_FIND_CB_END; + } + } + if (!lw->recursive) + /* Don't need to search recursively */ + return DIE_FIND_CB_SIBLING; + + if (addr) { + fname = dwarf_decl_file(in_die); + if (fname && dwarf_decl_line(in_die, &lineno) == 0) { + lw->retval = lw->callback(fname, lineno, addr, lw->data); + if (lw->retval != 0) + return DIE_FIND_CB_END; + } + } + + /* Continue to search nested inlined function call-sites */ + return DIE_FIND_CB_CONTINUE; +} + +/* Walk on lines of blocks included in given DIE */ +static int __die_walk_funclines(Dwarf_Die *sp_die, bool recursive, + line_walk_callback_t callback, void *data) +{ + struct __line_walk_param lw = { + .recursive = recursive, + .callback = callback, + .data = data, + .retval = 0, + }; + Dwarf_Die die_mem; + Dwarf_Addr addr; + const char *fname; + int lineno; + + /* Handle function declaration line */ + fname = dwarf_decl_file(sp_die); + if (fname && dwarf_decl_line(sp_die, &lineno) == 0 && + dwarf_entrypc(sp_die, &addr) == 0) { + lw.retval = callback(fname, lineno, addr, data); + if (lw.retval != 0) + goto done; + } + die_find_child(sp_die, __die_walk_funclines_cb, &lw, &die_mem); +done: + return lw.retval; +} + +static int __die_walk_culines_cb(Dwarf_Die *sp_die, void *data) +{ + struct __line_walk_param *lw = data; + + lw->retval = __die_walk_funclines(sp_die, true, lw->callback, lw->data); + if (lw->retval != 0) + return DWARF_CB_ABORT; + + return DWARF_CB_OK; +} + +/** + * die_walk_lines - Walk on lines inside given DIE + * @rt_die: a root DIE (CU, subprogram or inlined_subroutine) + * @callback: callback routine + * @data: user data + * + * Walk on all lines inside given @rt_die and call @callback on each line. + * If the @rt_die is a function, walk only on the lines inside the function, + * otherwise @rt_die must be a CU DIE. + * Note that this walks not only dwarf line list, but also function entries + * and inline call-site. + */ +int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data) +{ + Dwarf_Lines *lines; + Dwarf_Line *line; + Dwarf_Addr addr; + const char *fname; + int lineno, ret = 0; + Dwarf_Die die_mem, *cu_die; + size_t nlines, i; + + /* Get the CU die */ + if (dwarf_tag(rt_die) != DW_TAG_compile_unit) + cu_die = dwarf_diecu(rt_die, &die_mem, NULL, NULL); + else + cu_die = rt_die; + if (!cu_die) { + pr_debug2("Failed to get CU from given DIE.\n"); + return -EINVAL; + } + + /* Get lines list in the CU */ + if (dwarf_getsrclines(cu_die, &lines, &nlines) != 0) { + pr_debug2("Failed to get source lines on this CU.\n"); + return -ENOENT; + } + pr_debug2("Get %zd lines from this CU\n", nlines); + + /* Walk on the lines on lines list */ + for (i = 0; i < nlines; i++) { + line = dwarf_onesrcline(lines, i); + if (line == NULL || + dwarf_lineno(line, &lineno) != 0 || + dwarf_lineaddr(line, &addr) != 0) { + pr_debug2("Failed to get line info. " + "Possible error in debuginfo.\n"); + continue; + } + /* Filter lines based on address */ + if (rt_die != cu_die) + /* + * Address filtering + * The line is included in given function, and + * no inline block includes it. + */ + if (!dwarf_haspc(rt_die, addr) || + die_find_inlinefunc(rt_die, addr, &die_mem)) + continue; + /* Get source line */ + fname = dwarf_linesrc(line, NULL, NULL); + + ret = callback(fname, lineno, addr, data); + if (ret != 0) + return ret; + } + + /* + * Dwarf lines doesn't include function declarations and inlined + * subroutines. We have to check functions list or given function. + */ + if (rt_die != cu_die) + /* + * Don't need walk functions recursively, because nested + * inlined functions don't have lines of the specified DIE. + */ + ret = __die_walk_funclines(rt_die, false, callback, data); + else { + struct __line_walk_param param = { + .callback = callback, + .data = data, + .retval = 0, + }; + dwarf_getfuncs(cu_die, __die_walk_culines_cb, ¶m, 0); + ret = param.retval; + } + + return ret; +} + +struct __find_variable_param { + const char *name; + Dwarf_Addr addr; +}; + +static int __die_find_variable_cb(Dwarf_Die *die_mem, void *data) +{ + struct __find_variable_param *fvp = data; + int tag; + + tag = dwarf_tag(die_mem); + if ((tag == DW_TAG_formal_parameter || + tag == DW_TAG_variable) && + die_compare_name(die_mem, fvp->name)) + return DIE_FIND_CB_END; + + if (dwarf_haspc(die_mem, fvp->addr)) + return DIE_FIND_CB_CONTINUE; + else + return DIE_FIND_CB_SIBLING; +} + +/** + * die_find_variable_at - Find a given name variable at given address + * @sp_die: a function DIE + * @name: variable name + * @addr: address + * @die_mem: a buffer for result DIE + * + * Find a variable DIE called @name at @addr in @sp_die. + */ +Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name, + Dwarf_Addr addr, Dwarf_Die *die_mem) +{ + struct __find_variable_param fvp = { .name = name, .addr = addr}; + + return die_find_child(sp_die, __die_find_variable_cb, (void *)&fvp, + die_mem); +} + +static int __die_find_member_cb(Dwarf_Die *die_mem, void *data) +{ + const char *name = data; + + if ((dwarf_tag(die_mem) == DW_TAG_member) && + die_compare_name(die_mem, name)) + return DIE_FIND_CB_END; + + return DIE_FIND_CB_SIBLING; +} + +/** + * die_find_member - Find a given name member in a data structure + * @st_die: a data structure type DIE + * @name: member name + * @die_mem: a buffer for result DIE + * + * Find a member DIE called @name in @st_die. + */ +Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name, + Dwarf_Die *die_mem) +{ + return die_find_child(st_die, __die_find_member_cb, (void *)name, + die_mem); +} + +/** + * die_get_typename - Get the name of given variable DIE + * @vr_die: a variable DIE + * @buf: a buffer for result type name + * @len: a max-length of @buf + * + * Get the name of @vr_die and stores it to @buf. Return the actual length + * of type name if succeeded. Return -E2BIG if @len is not enough long, and + * Return -ENOENT if failed to find type name. + * Note that the result will stores typedef name if possible, and stores + * "*(function_type)" if the type is a function pointer. + */ +int die_get_typename(Dwarf_Die *vr_die, char *buf, int len) +{ + Dwarf_Die type; + int tag, ret, ret2; + const char *tmp = ""; + + if (__die_get_real_type(vr_die, &type) == NULL) + return -ENOENT; + + tag = dwarf_tag(&type); + if (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type) + tmp = "*"; + else if (tag == DW_TAG_subroutine_type) { + /* Function pointer */ + ret = snprintf(buf, len, "(function_type)"); + return (ret >= len) ? -E2BIG : ret; + } else { + if (!dwarf_diename(&type)) + return -ENOENT; + if (tag == DW_TAG_union_type) + tmp = "union "; + else if (tag == DW_TAG_structure_type) + tmp = "struct "; + /* Write a base name */ + ret = snprintf(buf, len, "%s%s", tmp, dwarf_diename(&type)); + return (ret >= len) ? -E2BIG : ret; + } + ret = die_get_typename(&type, buf, len); + if (ret > 0) { + ret2 = snprintf(buf + ret, len - ret, "%s", tmp); + ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret; + } + return ret; +} + +/** + * die_get_varname - Get the name and type of given variable DIE + * @vr_die: a variable DIE + * @buf: a buffer for type and variable name + * @len: the max-length of @buf + * + * Get the name and type of @vr_die and stores it in @buf as "type\tname". + */ +int die_get_varname(Dwarf_Die *vr_die, char *buf, int len) +{ + int ret, ret2; + + ret = die_get_typename(vr_die, buf, len); + if (ret < 0) { + pr_debug("Failed to get type, make it unknown.\n"); + ret = snprintf(buf, len, "(unknown_type)"); + } + if (ret > 0) { + ret2 = snprintf(buf + ret, len - ret, "\t%s", + dwarf_diename(vr_die)); + ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret; + } + return ret; +} + diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h new file mode 100644 index 000000000000..6ce1717784b7 --- /dev/null +++ b/tools/perf/util/dwarf-aux.h @@ -0,0 +1,111 @@ +#ifndef _DWARF_AUX_H +#define _DWARF_AUX_H +/* + * dwarf-aux.h : libdw auxiliary interfaces + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <dwarf.h> +#include <elfutils/libdw.h> +#include <elfutils/libdwfl.h> +#include <elfutils/version.h> + +/* Find the realpath of the target file */ +extern const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname); + +/* Get DW_AT_comp_dir (should be NULL with older gcc) */ +extern const char *cu_get_comp_dir(Dwarf_Die *cu_die); + +/* Get a line number and file name for given address */ +extern int cu_find_lineinfo(Dwarf_Die *cudie, unsigned long addr, + const char **fname, int *lineno); + +/* Walk on funcitons at given address */ +extern int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr, + int (*callback)(Dwarf_Die *, void *), void *data); + +/* Compare diename and tname */ +extern bool die_compare_name(Dwarf_Die *dw_die, const char *tname); + +/* Get callsite line number of inline-function instance */ +extern int die_get_call_lineno(Dwarf_Die *in_die); + +/* Get callsite file name of inlined function instance */ +extern const char *die_get_call_file(Dwarf_Die *in_die); + +/* Get type die */ +extern Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem); + +/* Get a type die, but skip qualifiers and typedef */ +extern Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem); + +/* Check whether the DIE is signed or not */ +extern bool die_is_signed_type(Dwarf_Die *tp_die); + +/* Get data_member_location offset */ +extern int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs); + +/* Return values for die_find_child() callbacks */ +enum { + DIE_FIND_CB_END = 0, /* End of Search */ + DIE_FIND_CB_CHILD = 1, /* Search only children */ + DIE_FIND_CB_SIBLING = 2, /* Search only siblings */ + DIE_FIND_CB_CONTINUE = 3, /* Search children and siblings */ +}; + +/* Search child DIEs */ +extern Dwarf_Die *die_find_child(Dwarf_Die *rt_die, + int (*callback)(Dwarf_Die *, void *), + void *data, Dwarf_Die *die_mem); + +/* Search a non-inlined function including given address */ +extern Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr, + Dwarf_Die *die_mem); + +/* Search an inlined function including given address */ +extern Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr, + Dwarf_Die *die_mem); + +/* Walk on the instances of given DIE */ +extern int die_walk_instances(Dwarf_Die *in_die, + int (*callback)(Dwarf_Die *, void *), void *data); + +/* Walker on lines (Note: line number will not be sorted) */ +typedef int (* line_walk_callback_t) (const char *fname, int lineno, + Dwarf_Addr addr, void *data); + +/* + * Walk on lines inside given DIE. If the DIE is a subprogram, walk only on + * the lines inside the subprogram, otherwise the DIE must be a CU DIE. + */ +extern int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, + void *data); + +/* Find a variable called 'name' at given address */ +extern Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name, + Dwarf_Addr addr, Dwarf_Die *die_mem); + +/* Find a member called 'name' */ +extern Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name, + Dwarf_Die *die_mem); + +/* Get the name of given variable DIE */ +extern int die_get_typename(Dwarf_Die *vr_die, char *buf, int len); + +/* Get the name and type of given variable DIE, stored as "type\tname" */ +extern int die_get_varname(Dwarf_Die *vr_die, char *buf, int len); +#endif diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index 3c1b8a632101..437f8ca679a0 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -169,12 +169,17 @@ static int perf_event__synthesize_mmap_events(union perf_event *event, continue; pbf += n + 3; if (*pbf == 'x') { /* vm_exec */ + char anonstr[] = "//anon\n"; char *execname = strchr(bf, '/'); /* Catch VDSO */ if (execname == NULL) execname = strstr(bf, "[vdso]"); + /* Catch anonymous mmaps */ + if ((execname == NULL) && !strstr(bf, "[")) + execname = anonstr; + if (execname == NULL) continue; diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index 1d7f66488a88..357a85b85248 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -186,6 +186,6 @@ const char *perf_event__name(unsigned int id); int perf_event__parse_sample(const union perf_event *event, u64 type, int sample_size, bool sample_id_all, - struct perf_sample *sample); + struct perf_sample *sample, bool swapped); #endif /* __PERF_RECORD_H */ diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c index b021ea9265c3..fbb4b4ab9cc6 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -85,10 +85,45 @@ int perf_evlist__add_default(struct perf_evlist *evlist) struct perf_evsel *evsel = perf_evsel__new(&attr, 0); if (evsel == NULL) - return -ENOMEM; + goto error; + + /* use strdup() because free(evsel) assumes name is allocated */ + evsel->name = strdup("cycles"); + if (!evsel->name) + goto error_free; perf_evlist__add(evlist, evsel); return 0; +error_free: + perf_evsel__delete(evsel); +error: + return -ENOMEM; +} + +void perf_evlist__disable(struct perf_evlist *evlist) +{ + int cpu, thread; + struct perf_evsel *pos; + + for (cpu = 0; cpu < evlist->cpus->nr; cpu++) { + list_for_each_entry(pos, &evlist->entries, node) { + for (thread = 0; thread < evlist->threads->nr; thread++) + ioctl(FD(pos, cpu, thread), PERF_EVENT_IOC_DISABLE); + } + } +} + +void perf_evlist__enable(struct perf_evlist *evlist) +{ + int cpu, thread; + struct perf_evsel *pos; + + for (cpu = 0; cpu < evlist->cpus->nr; cpu++) { + list_for_each_entry(pos, &evlist->entries, node) { + for (thread = 0; thread < evlist->threads->nr; thread++) + ioctl(FD(pos, cpu, thread), PERF_EVENT_IOC_ENABLE); + } + } } int perf_evlist__alloc_pollfd(struct perf_evlist *evlist) @@ -498,3 +533,39 @@ bool perf_evlist__sample_id_all(const struct perf_evlist *evlist) first = list_entry(evlist->entries.next, struct perf_evsel, node); return first->attr.sample_id_all; } + +void perf_evlist__set_selected(struct perf_evlist *evlist, + struct perf_evsel *evsel) +{ + evlist->selected = evsel; +} + +int perf_evlist__open(struct perf_evlist *evlist, bool group) +{ + struct perf_evsel *evsel, *first; + int err, ncpus, nthreads; + + first = list_entry(evlist->entries.next, struct perf_evsel, node); + + list_for_each_entry(evsel, &evlist->entries, node) { + struct xyarray *group_fd = NULL; + + if (group && evsel != first) + group_fd = first->fd; + + err = perf_evsel__open(evsel, evlist->cpus, evlist->threads, + group, group_fd); + if (err < 0) + goto out_err; + } + + return 0; +out_err: + ncpus = evlist->cpus ? evlist->cpus->nr : 1; + nthreads = evlist->threads ? evlist->threads->nr : 1; + + list_for_each_entry_reverse(evsel, &evlist->entries, node) + perf_evsel__close(evsel, ncpus, nthreads); + + return err; +} diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h index b2b862374f37..1779ffef7828 100644 --- a/tools/perf/util/evlist.h +++ b/tools/perf/util/evlist.h @@ -25,6 +25,7 @@ struct perf_evlist { struct pollfd *pollfd; struct thread_map *threads; struct cpu_map *cpus; + struct perf_evsel *selected; }; struct perf_evsel; @@ -49,10 +50,18 @@ struct perf_evsel *perf_evlist__id2evsel(struct perf_evlist *evlist, u64 id); union perf_event *perf_evlist__mmap_read(struct perf_evlist *self, int idx); +int perf_evlist__open(struct perf_evlist *evlist, bool group); + int perf_evlist__alloc_mmap(struct perf_evlist *evlist); int perf_evlist__mmap(struct perf_evlist *evlist, int pages, bool overwrite); void perf_evlist__munmap(struct perf_evlist *evlist); +void perf_evlist__disable(struct perf_evlist *evlist); +void perf_evlist__enable(struct perf_evlist *evlist); + +void perf_evlist__set_selected(struct perf_evlist *evlist, + struct perf_evsel *evsel); + static inline void perf_evlist__set_maps(struct perf_evlist *evlist, struct cpu_map *cpus, struct thread_map *threads) diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 0239eb87b232..e42626422587 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -7,6 +7,8 @@ * Released under the GPL v2. (and only v2, not any later version) */ +#include <byteswap.h> +#include "asm/bug.h" #include "evsel.h" #include "evlist.h" #include "util.h" @@ -14,6 +16,7 @@ #include "thread_map.h" #define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y)) +#define GROUP_FD(group_fd, cpu) (*(int *)xyarray__entry(group_fd, cpu, 0)) int __perf_evsel__sample_size(u64 sample_type) { @@ -37,6 +40,7 @@ void perf_evsel__init(struct perf_evsel *evsel, evsel->idx = idx; evsel->attr = *attr; INIT_LIST_HEAD(&evsel->node); + hists__init(&evsel->hists); } struct perf_evsel *perf_evsel__new(struct perf_event_attr *attr, int idx) @@ -201,15 +205,16 @@ int __perf_evsel__read(struct perf_evsel *evsel, } static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, - struct thread_map *threads, bool group) + struct thread_map *threads, bool group, + struct xyarray *group_fds) { int cpu, thread; unsigned long flags = 0; - int pid = -1; + int pid = -1, err; if (evsel->fd == NULL && perf_evsel__alloc_fd(evsel, cpus->nr, threads->nr) < 0) - return -1; + return -ENOMEM; if (evsel->cgrp) { flags = PERF_FLAG_PID_CGROUP; @@ -217,7 +222,7 @@ static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, } for (cpu = 0; cpu < cpus->nr; cpu++) { - int group_fd = -1; + int group_fd = group_fds ? GROUP_FD(group_fds, cpu) : -1; for (thread = 0; thread < threads->nr; thread++) { @@ -228,8 +233,10 @@ static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, pid, cpus->map[cpu], group_fd, flags); - if (FD(evsel, cpu, thread) < 0) + if (FD(evsel, cpu, thread) < 0) { + err = -errno; goto out_close; + } if (group && group_fd == -1) group_fd = FD(evsel, cpu, thread); @@ -246,7 +253,17 @@ out_close: } thread = threads->nr; } while (--cpu >= 0); - return -1; + return err; +} + +void perf_evsel__close(struct perf_evsel *evsel, int ncpus, int nthreads) +{ + if (evsel->fd == NULL) + return; + + perf_evsel__close_fd(evsel, ncpus, nthreads); + perf_evsel__free_fd(evsel); + evsel->fd = NULL; } static struct { @@ -266,7 +283,8 @@ static struct { }; int perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, - struct thread_map *threads, bool group) + struct thread_map *threads, bool group, + struct xyarray *group_fd) { if (cpus == NULL) { /* Work around old compiler warnings about strict aliasing */ @@ -276,19 +294,23 @@ int perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, if (threads == NULL) threads = &empty_thread_map.map; - return __perf_evsel__open(evsel, cpus, threads, group); + return __perf_evsel__open(evsel, cpus, threads, group, group_fd); } int perf_evsel__open_per_cpu(struct perf_evsel *evsel, - struct cpu_map *cpus, bool group) + struct cpu_map *cpus, bool group, + struct xyarray *group_fd) { - return __perf_evsel__open(evsel, cpus, &empty_thread_map.map, group); + return __perf_evsel__open(evsel, cpus, &empty_thread_map.map, group, + group_fd); } int perf_evsel__open_per_thread(struct perf_evsel *evsel, - struct thread_map *threads, bool group) + struct thread_map *threads, bool group, + struct xyarray *group_fd) { - return __perf_evsel__open(evsel, &empty_cpu_map.map, threads, group); + return __perf_evsel__open(evsel, &empty_cpu_map.map, threads, group, + group_fd); } static int perf_event__parse_id_sample(const union perf_event *event, u64 type, @@ -342,10 +364,20 @@ static bool sample_overlap(const union perf_event *event, int perf_event__parse_sample(const union perf_event *event, u64 type, int sample_size, bool sample_id_all, - struct perf_sample *data) + struct perf_sample *data, bool swapped) { const u64 *array; + /* + * used for cross-endian analysis. See git commit 65014ab3 + * for why this goofiness is needed. + */ + union { + u64 val64; + u32 val32[2]; + } u; + + data->cpu = data->pid = data->tid = -1; data->stream_id = data->id = data->time = -1ULL; @@ -366,9 +398,16 @@ int perf_event__parse_sample(const union perf_event *event, u64 type, } if (type & PERF_SAMPLE_TID) { - u32 *p = (u32 *)array; - data->pid = p[0]; - data->tid = p[1]; + u.val64 = *array; + if (swapped) { + /* undo swap of u64, then swap on individual u32s */ + u.val64 = bswap_64(u.val64); + u.val32[0] = bswap_32(u.val32[0]); + u.val32[1] = bswap_32(u.val32[1]); + } + + data->pid = u.val32[0]; + data->tid = u.val32[1]; array++; } @@ -377,6 +416,7 @@ int perf_event__parse_sample(const union perf_event *event, u64 type, array++; } + data->addr = 0; if (type & PERF_SAMPLE_ADDR) { data->addr = *array; array++; @@ -394,8 +434,15 @@ int perf_event__parse_sample(const union perf_event *event, u64 type, } if (type & PERF_SAMPLE_CPU) { - u32 *p = (u32 *)array; - data->cpu = *p; + + u.val64 = *array; + if (swapped) { + /* undo swap of u64, then swap on individual u32s */ + u.val64 = bswap_64(u.val64); + u.val32[0] = bswap_32(u.val32[0]); + } + + data->cpu = u.val32[0]; array++; } @@ -422,18 +469,27 @@ int perf_event__parse_sample(const union perf_event *event, u64 type, } if (type & PERF_SAMPLE_RAW) { - u32 *p = (u32 *)array; + const u64 *pdata; + + u.val64 = *array; + if (WARN_ONCE(swapped, + "Endianness of raw data not corrected!\n")) { + /* undo swap of u64, then swap on individual u32s */ + u.val64 = bswap_64(u.val64); + u.val32[0] = bswap_32(u.val32[0]); + u.val32[1] = bswap_32(u.val32[1]); + } if (sample_overlap(event, array, sizeof(u32))) return -EFAULT; - data->raw_size = *p; - p++; + data->raw_size = u.val32[0]; + pdata = (void *) array + sizeof(u32); - if (sample_overlap(event, p, data->raw_size)) + if (sample_overlap(event, pdata, data->raw_size)) return -EFAULT; - data->raw_data = p; + data->raw_data = (void *) pdata; } return 0; diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 7e9366e4490b..b1d15e6f7ae3 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -61,6 +61,7 @@ struct perf_evsel { off_t id_offset; }; struct cgroup_sel *cgrp; + bool supported; }; struct cpu_map; @@ -81,11 +82,15 @@ void perf_evsel__free_id(struct perf_evsel *evsel); void perf_evsel__close_fd(struct perf_evsel *evsel, int ncpus, int nthreads); int perf_evsel__open_per_cpu(struct perf_evsel *evsel, - struct cpu_map *cpus, bool group); + struct cpu_map *cpus, bool group, + struct xyarray *group_fds); int perf_evsel__open_per_thread(struct perf_evsel *evsel, - struct thread_map *threads, bool group); + struct thread_map *threads, bool group, + struct xyarray *group_fds); int perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, - struct thread_map *threads, bool group); + struct thread_map *threads, bool group, + struct xyarray *group_fds); +void perf_evsel__close(struct perf_evsel *evsel, int ncpus, int nthreads); #define perf_evsel__match(evsel, t, c) \ (evsel->attr.type == PERF_TYPE_##t && \ diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index afb0849fe530..bcd05d05b4f0 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -1,5 +1,6 @@ #define _FILE_OFFSET_BITS 64 +#include "util.h" #include <sys/types.h> #include <byteswap.h> #include <unistd.h> @@ -7,22 +8,29 @@ #include <stdlib.h> #include <linux/list.h> #include <linux/kernel.h> +#include <sys/utsname.h> #include "evlist.h" #include "evsel.h" -#include "util.h" #include "header.h" #include "../perf.h" #include "trace-event.h" #include "session.h" #include "symbol.h" #include "debug.h" +#include "cpumap.h" static bool no_buildid_cache = false; static int event_count; static struct perf_trace_event_type *events; +static u32 header_argc; +static const char **header_argv; + +static int dsos__write_buildid_table(struct perf_header *header, int fd); +static int perf_session__cache_build_ids(struct perf_session *session); + int perf_header__push_event(u64 id, const char *name) { if (strlen(name) > MAX_EVENT_NAME) @@ -110,6 +118,1020 @@ static int write_padded(int fd, const void *bf, size_t count, return err; } +static int do_write_string(int fd, const char *str) +{ + u32 len, olen; + int ret; + + olen = strlen(str) + 1; + len = ALIGN(olen, NAME_ALIGN); + + /* write len, incl. \0 */ + ret = do_write(fd, &len, sizeof(len)); + if (ret < 0) + return ret; + + return write_padded(fd, str, olen, len); +} + +static char *do_read_string(int fd, struct perf_header *ph) +{ + ssize_t sz, ret; + u32 len; + char *buf; + + sz = read(fd, &len, sizeof(len)); + if (sz < (ssize_t)sizeof(len)) + return NULL; + + if (ph->needs_swap) + len = bswap_32(len); + + buf = malloc(len); + if (!buf) + return NULL; + + ret = read(fd, buf, len); + if (ret == (ssize_t)len) { + /* + * strings are padded by zeroes + * thus the actual strlen of buf + * may be less than len + */ + return buf; + } + + free(buf); + return NULL; +} + +int +perf_header__set_cmdline(int argc, const char **argv) +{ + int i; + + header_argc = (u32)argc; + + /* do not include NULL termination */ + header_argv = calloc(argc, sizeof(char *)); + if (!header_argv) + return -ENOMEM; + + /* + * must copy argv contents because it gets moved + * around during option parsing + */ + for (i = 0; i < argc ; i++) + header_argv[i] = argv[i]; + + return 0; +} + +static int write_trace_info(int fd, struct perf_header *h __used, + struct perf_evlist *evlist) +{ + return read_tracing_data(fd, &evlist->entries); +} + + +static int write_build_id(int fd, struct perf_header *h, + struct perf_evlist *evlist __used) +{ + struct perf_session *session; + int err; + + session = container_of(h, struct perf_session, header); + + err = dsos__write_buildid_table(h, fd); + if (err < 0) { + pr_debug("failed to write buildid table\n"); + return err; + } + if (!no_buildid_cache) + perf_session__cache_build_ids(session); + + return 0; +} + +static int write_hostname(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + struct utsname uts; + int ret; + + ret = uname(&uts); + if (ret < 0) + return -1; + + return do_write_string(fd, uts.nodename); +} + +static int write_osrelease(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + struct utsname uts; + int ret; + + ret = uname(&uts); + if (ret < 0) + return -1; + + return do_write_string(fd, uts.release); +} + +static int write_arch(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + struct utsname uts; + int ret; + + ret = uname(&uts); + if (ret < 0) + return -1; + + return do_write_string(fd, uts.machine); +} + +static int write_version(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + return do_write_string(fd, perf_version_string); +} + +static int write_cpudesc(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ +#ifndef CPUINFO_PROC +#define CPUINFO_PROC NULL +#endif + FILE *file; + char *buf = NULL; + char *s, *p; + const char *search = CPUINFO_PROC; + size_t len = 0; + int ret = -1; + + if (!search) + return -1; + + file = fopen("/proc/cpuinfo", "r"); + if (!file) + return -1; + + while (getline(&buf, &len, file) > 0) { + ret = strncmp(buf, search, strlen(search)); + if (!ret) + break; + } + + if (ret) + goto done; + + s = buf; + + p = strchr(buf, ':'); + if (p && *(p+1) == ' ' && *(p+2)) + s = p + 2; + p = strchr(s, '\n'); + if (p) + *p = '\0'; + + /* squash extra space characters (branding string) */ + p = s; + while (*p) { + if (isspace(*p)) { + char *r = p + 1; + char *q = r; + *p = ' '; + while (*q && isspace(*q)) + q++; + if (q != (p+1)) + while ((*r++ = *q++)); + } + p++; + } + ret = do_write_string(fd, s); +done: + free(buf); + fclose(file); + return ret; +} + +static int write_nrcpus(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + long nr; + u32 nrc, nra; + int ret; + + nr = sysconf(_SC_NPROCESSORS_CONF); + if (nr < 0) + return -1; + + nrc = (u32)(nr & UINT_MAX); + + nr = sysconf(_SC_NPROCESSORS_ONLN); + if (nr < 0) + return -1; + + nra = (u32)(nr & UINT_MAX); + + ret = do_write(fd, &nrc, sizeof(nrc)); + if (ret < 0) + return ret; + + return do_write(fd, &nra, sizeof(nra)); +} + +static int write_event_desc(int fd, struct perf_header *h __used, + struct perf_evlist *evlist) +{ + struct perf_evsel *attr; + u32 nre = 0, nri, sz; + int ret; + + list_for_each_entry(attr, &evlist->entries, node) + nre++; + + /* + * write number of events + */ + ret = do_write(fd, &nre, sizeof(nre)); + if (ret < 0) + return ret; + + /* + * size of perf_event_attr struct + */ + sz = (u32)sizeof(attr->attr); + ret = do_write(fd, &sz, sizeof(sz)); + if (ret < 0) + return ret; + + list_for_each_entry(attr, &evlist->entries, node) { + + ret = do_write(fd, &attr->attr, sz); + if (ret < 0) + return ret; + /* + * write number of unique id per event + * there is one id per instance of an event + * + * copy into an nri to be independent of the + * type of ids, + */ + nri = attr->ids; + ret = do_write(fd, &nri, sizeof(nri)); + if (ret < 0) + return ret; + + /* + * write event string as passed on cmdline + */ + ret = do_write_string(fd, attr->name); + if (ret < 0) + return ret; + /* + * write unique ids for this event + */ + ret = do_write(fd, attr->id, attr->ids * sizeof(u64)); + if (ret < 0) + return ret; + } + return 0; +} + +static int write_cmdline(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + char buf[MAXPATHLEN]; + char proc[32]; + u32 i, n; + int ret; + + /* + * actual atual path to perf binary + */ + sprintf(proc, "/proc/%d/exe", getpid()); + ret = readlink(proc, buf, sizeof(buf)); + if (ret <= 0) + return -1; + + /* readlink() does not add null termination */ + buf[ret] = '\0'; + + /* account for binary path */ + n = header_argc + 1; + + ret = do_write(fd, &n, sizeof(n)); + if (ret < 0) + return ret; + + ret = do_write_string(fd, buf); + if (ret < 0) + return ret; + + for (i = 0 ; i < header_argc; i++) { + ret = do_write_string(fd, header_argv[i]); + if (ret < 0) + return ret; + } + return 0; +} + +#define CORE_SIB_FMT \ + "/sys/devices/system/cpu/cpu%d/topology/core_siblings_list" +#define THRD_SIB_FMT \ + "/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list" + +struct cpu_topo { + u32 core_sib; + u32 thread_sib; + char **core_siblings; + char **thread_siblings; +}; + +static int build_cpu_topo(struct cpu_topo *tp, int cpu) +{ + FILE *fp; + char filename[MAXPATHLEN]; + char *buf = NULL, *p; + size_t len = 0; + u32 i = 0; + int ret = -1; + + sprintf(filename, CORE_SIB_FMT, cpu); + fp = fopen(filename, "r"); + if (!fp) + return -1; + + if (getline(&buf, &len, fp) <= 0) + goto done; + + fclose(fp); + + p = strchr(buf, '\n'); + if (p) + *p = '\0'; + + for (i = 0; i < tp->core_sib; i++) { + if (!strcmp(buf, tp->core_siblings[i])) + break; + } + if (i == tp->core_sib) { + tp->core_siblings[i] = buf; + tp->core_sib++; + buf = NULL; + len = 0; + } + + sprintf(filename, THRD_SIB_FMT, cpu); + fp = fopen(filename, "r"); + if (!fp) + goto done; + + if (getline(&buf, &len, fp) <= 0) + goto done; + + p = strchr(buf, '\n'); + if (p) + *p = '\0'; + + for (i = 0; i < tp->thread_sib; i++) { + if (!strcmp(buf, tp->thread_siblings[i])) + break; + } + if (i == tp->thread_sib) { + tp->thread_siblings[i] = buf; + tp->thread_sib++; + buf = NULL; + } + ret = 0; +done: + if(fp) + fclose(fp); + free(buf); + return ret; +} + +static void free_cpu_topo(struct cpu_topo *tp) +{ + u32 i; + + if (!tp) + return; + + for (i = 0 ; i < tp->core_sib; i++) + free(tp->core_siblings[i]); + + for (i = 0 ; i < tp->thread_sib; i++) + free(tp->thread_siblings[i]); + + free(tp); +} + +static struct cpu_topo *build_cpu_topology(void) +{ + struct cpu_topo *tp; + void *addr; + u32 nr, i; + size_t sz; + long ncpus; + int ret = -1; + + ncpus = sysconf(_SC_NPROCESSORS_CONF); + if (ncpus < 0) + return NULL; + + nr = (u32)(ncpus & UINT_MAX); + + sz = nr * sizeof(char *); + + addr = calloc(1, sizeof(*tp) + 2 * sz); + if (!addr) + return NULL; + + tp = addr; + + addr += sizeof(*tp); + tp->core_siblings = addr; + addr += sz; + tp->thread_siblings = addr; + + for (i = 0; i < nr; i++) { + ret = build_cpu_topo(tp, i); + if (ret < 0) + break; + } + if (ret) { + free_cpu_topo(tp); + tp = NULL; + } + return tp; +} + +static int write_cpu_topology(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + struct cpu_topo *tp; + u32 i; + int ret; + + tp = build_cpu_topology(); + if (!tp) + return -1; + + ret = do_write(fd, &tp->core_sib, sizeof(tp->core_sib)); + if (ret < 0) + goto done; + + for (i = 0; i < tp->core_sib; i++) { + ret = do_write_string(fd, tp->core_siblings[i]); + if (ret < 0) + goto done; + } + ret = do_write(fd, &tp->thread_sib, sizeof(tp->thread_sib)); + if (ret < 0) + goto done; + + for (i = 0; i < tp->thread_sib; i++) { + ret = do_write_string(fd, tp->thread_siblings[i]); + if (ret < 0) + break; + } +done: + free_cpu_topo(tp); + return ret; +} + + + +static int write_total_mem(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + char *buf = NULL; + FILE *fp; + size_t len = 0; + int ret = -1, n; + uint64_t mem; + + fp = fopen("/proc/meminfo", "r"); + if (!fp) + return -1; + + while (getline(&buf, &len, fp) > 0) { + ret = strncmp(buf, "MemTotal:", 9); + if (!ret) + break; + } + if (!ret) { + n = sscanf(buf, "%*s %"PRIu64, &mem); + if (n == 1) + ret = do_write(fd, &mem, sizeof(mem)); + } + free(buf); + fclose(fp); + return ret; +} + +static int write_topo_node(int fd, int node) +{ + char str[MAXPATHLEN]; + char field[32]; + char *buf = NULL, *p; + size_t len = 0; + FILE *fp; + u64 mem_total, mem_free, mem; + int ret = -1; + + sprintf(str, "/sys/devices/system/node/node%d/meminfo", node); + fp = fopen(str, "r"); + if (!fp) + return -1; + + while (getline(&buf, &len, fp) > 0) { + /* skip over invalid lines */ + if (!strchr(buf, ':')) + continue; + if (sscanf(buf, "%*s %*d %s %"PRIu64, field, &mem) != 2) + goto done; + if (!strcmp(field, "MemTotal:")) + mem_total = mem; + if (!strcmp(field, "MemFree:")) + mem_free = mem; + } + + fclose(fp); + + ret = do_write(fd, &mem_total, sizeof(u64)); + if (ret) + goto done; + + ret = do_write(fd, &mem_free, sizeof(u64)); + if (ret) + goto done; + + ret = -1; + sprintf(str, "/sys/devices/system/node/node%d/cpulist", node); + + fp = fopen(str, "r"); + if (!fp) + goto done; + + if (getline(&buf, &len, fp) <= 0) + goto done; + + p = strchr(buf, '\n'); + if (p) + *p = '\0'; + + ret = do_write_string(fd, buf); +done: + free(buf); + fclose(fp); + return ret; +} + +static int write_numa_topology(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + char *buf = NULL; + size_t len = 0; + FILE *fp; + struct cpu_map *node_map = NULL; + char *c; + u32 nr, i, j; + int ret = -1; + + fp = fopen("/sys/devices/system/node/online", "r"); + if (!fp) + return -1; + + if (getline(&buf, &len, fp) <= 0) + goto done; + + c = strchr(buf, '\n'); + if (c) + *c = '\0'; + + node_map = cpu_map__new(buf); + if (!node_map) + goto done; + + nr = (u32)node_map->nr; + + ret = do_write(fd, &nr, sizeof(nr)); + if (ret < 0) + goto done; + + for (i = 0; i < nr; i++) { + j = (u32)node_map->map[i]; + ret = do_write(fd, &j, sizeof(j)); + if (ret < 0) + break; + + ret = write_topo_node(fd, i); + if (ret < 0) + break; + } +done: + free(buf); + fclose(fp); + free(node_map); + return ret; +} + +/* + * default get_cpuid(): nothing gets recorded + * actual implementation must be in arch/$(ARCH)/util/header.c + */ +int __attribute__((weak)) get_cpuid(char *buffer __used, size_t sz __used) +{ + return -1; +} + +static int write_cpuid(int fd, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + char buffer[64]; + int ret; + + ret = get_cpuid(buffer, sizeof(buffer)); + if (!ret) + goto write_it; + + return -1; +write_it: + return do_write_string(fd, buffer); +} + +static void print_hostname(struct perf_header *ph, int fd, FILE *fp) +{ + char *str = do_read_string(fd, ph); + fprintf(fp, "# hostname : %s\n", str); + free(str); +} + +static void print_osrelease(struct perf_header *ph, int fd, FILE *fp) +{ + char *str = do_read_string(fd, ph); + fprintf(fp, "# os release : %s\n", str); + free(str); +} + +static void print_arch(struct perf_header *ph, int fd, FILE *fp) +{ + char *str = do_read_string(fd, ph); + fprintf(fp, "# arch : %s\n", str); + free(str); +} + +static void print_cpudesc(struct perf_header *ph, int fd, FILE *fp) +{ + char *str = do_read_string(fd, ph); + fprintf(fp, "# cpudesc : %s\n", str); + free(str); +} + +static void print_nrcpus(struct perf_header *ph, int fd, FILE *fp) +{ + ssize_t ret; + u32 nr; + + ret = read(fd, &nr, sizeof(nr)); + if (ret != (ssize_t)sizeof(nr)) + nr = -1; /* interpreted as error */ + + if (ph->needs_swap) + nr = bswap_32(nr); + + fprintf(fp, "# nrcpus online : %u\n", nr); + + ret = read(fd, &nr, sizeof(nr)); + if (ret != (ssize_t)sizeof(nr)) + nr = -1; /* interpreted as error */ + + if (ph->needs_swap) + nr = bswap_32(nr); + + fprintf(fp, "# nrcpus avail : %u\n", nr); +} + +static void print_version(struct perf_header *ph, int fd, FILE *fp) +{ + char *str = do_read_string(fd, ph); + fprintf(fp, "# perf version : %s\n", str); + free(str); +} + +static void print_cmdline(struct perf_header *ph, int fd, FILE *fp) +{ + ssize_t ret; + char *str; + u32 nr, i; + + ret = read(fd, &nr, sizeof(nr)); + if (ret != (ssize_t)sizeof(nr)) + return; + + if (ph->needs_swap) + nr = bswap_32(nr); + + fprintf(fp, "# cmdline : "); + + for (i = 0; i < nr; i++) { + str = do_read_string(fd, ph); + fprintf(fp, "%s ", str); + free(str); + } + fputc('\n', fp); +} + +static void print_cpu_topology(struct perf_header *ph, int fd, FILE *fp) +{ + ssize_t ret; + u32 nr, i; + char *str; + + ret = read(fd, &nr, sizeof(nr)); + if (ret != (ssize_t)sizeof(nr)) + return; + + if (ph->needs_swap) + nr = bswap_32(nr); + + for (i = 0; i < nr; i++) { + str = do_read_string(fd, ph); + fprintf(fp, "# sibling cores : %s\n", str); + free(str); + } + + ret = read(fd, &nr, sizeof(nr)); + if (ret != (ssize_t)sizeof(nr)) + return; + + if (ph->needs_swap) + nr = bswap_32(nr); + + for (i = 0; i < nr; i++) { + str = do_read_string(fd, ph); + fprintf(fp, "# sibling threads : %s\n", str); + free(str); + } +} + +static void print_event_desc(struct perf_header *ph, int fd, FILE *fp) +{ + struct perf_event_attr attr; + uint64_t id; + void *buf = NULL; + char *str; + u32 nre, sz, nr, i, j, msz; + int ret; + + /* number of events */ + ret = read(fd, &nre, sizeof(nre)); + if (ret != (ssize_t)sizeof(nre)) + goto error; + + if (ph->needs_swap) + nre = bswap_32(nre); + + ret = read(fd, &sz, sizeof(sz)); + if (ret != (ssize_t)sizeof(sz)) + goto error; + + if (ph->needs_swap) + sz = bswap_32(sz); + + /* + * ensure it is at least to our ABI rev + */ + if (sz < (u32)sizeof(attr)) + goto error; + + memset(&attr, 0, sizeof(attr)); + + /* read entire region to sync up to next field */ + buf = malloc(sz); + if (!buf) + goto error; + + msz = sizeof(attr); + if (sz < msz) + msz = sz; + + for (i = 0 ; i < nre; i++) { + + ret = read(fd, buf, sz); + if (ret != (ssize_t)sz) + goto error; + + if (ph->needs_swap) + perf_event__attr_swap(buf); + + memcpy(&attr, buf, msz); + + ret = read(fd, &nr, sizeof(nr)); + if (ret != (ssize_t)sizeof(nr)) + goto error; + + if (ph->needs_swap) + nr = bswap_32(nr); + + str = do_read_string(fd, ph); + fprintf(fp, "# event : name = %s, ", str); + free(str); + + fprintf(fp, "type = %d, config = 0x%"PRIx64 + ", config1 = 0x%"PRIx64", config2 = 0x%"PRIx64, + attr.type, + (u64)attr.config, + (u64)attr.config1, + (u64)attr.config2); + + fprintf(fp, ", excl_usr = %d, excl_kern = %d", + attr.exclude_user, + attr.exclude_kernel); + + if (nr) + fprintf(fp, ", id = {"); + + for (j = 0 ; j < nr; j++) { + ret = read(fd, &id, sizeof(id)); + if (ret != (ssize_t)sizeof(id)) + goto error; + + if (ph->needs_swap) + id = bswap_64(id); + + if (j) + fputc(',', fp); + + fprintf(fp, " %"PRIu64, id); + } + if (nr && j == nr) + fprintf(fp, " }"); + fputc('\n', fp); + } + free(buf); + return; +error: + fprintf(fp, "# event desc: not available or unable to read\n"); +} + +static void print_total_mem(struct perf_header *h __used, int fd, FILE *fp) +{ + uint64_t mem; + ssize_t ret; + + ret = read(fd, &mem, sizeof(mem)); + if (ret != sizeof(mem)) + goto error; + + if (h->needs_swap) + mem = bswap_64(mem); + + fprintf(fp, "# total memory : %"PRIu64" kB\n", mem); + return; +error: + fprintf(fp, "# total memory : unknown\n"); +} + +static void print_numa_topology(struct perf_header *h __used, int fd, FILE *fp) +{ + ssize_t ret; + u32 nr, c, i; + char *str; + uint64_t mem_total, mem_free; + + /* nr nodes */ + ret = read(fd, &nr, sizeof(nr)); + if (ret != (ssize_t)sizeof(nr)) + goto error; + + if (h->needs_swap) + nr = bswap_32(nr); + + for (i = 0; i < nr; i++) { + + /* node number */ + ret = read(fd, &c, sizeof(c)); + if (ret != (ssize_t)sizeof(c)) + goto error; + + if (h->needs_swap) + c = bswap_32(c); + + ret = read(fd, &mem_total, sizeof(u64)); + if (ret != sizeof(u64)) + goto error; + + ret = read(fd, &mem_free, sizeof(u64)); + if (ret != sizeof(u64)) + goto error; + + if (h->needs_swap) { + mem_total = bswap_64(mem_total); + mem_free = bswap_64(mem_free); + } + + fprintf(fp, "# node%u meminfo : total = %"PRIu64" kB," + " free = %"PRIu64" kB\n", + c, + mem_total, + mem_free); + + str = do_read_string(fd, h); + fprintf(fp, "# node%u cpu list : %s\n", c, str); + free(str); + } + return; +error: + fprintf(fp, "# numa topology : not available\n"); +} + +static void print_cpuid(struct perf_header *ph, int fd, FILE *fp) +{ + char *str = do_read_string(fd, ph); + fprintf(fp, "# cpuid : %s\n", str); + free(str); +} + +struct feature_ops { + int (*write)(int fd, struct perf_header *h, struct perf_evlist *evlist); + void (*print)(struct perf_header *h, int fd, FILE *fp); + const char *name; + bool full_only; +}; + +#define FEAT_OPA(n, w, p) \ + [n] = { .name = #n, .write = w, .print = p } +#define FEAT_OPF(n, w, p) \ + [n] = { .name = #n, .write = w, .print = p, .full_only = true } + +static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = { + FEAT_OPA(HEADER_TRACE_INFO, write_trace_info, NULL), + FEAT_OPA(HEADER_BUILD_ID, write_build_id, NULL), + FEAT_OPA(HEADER_HOSTNAME, write_hostname, print_hostname), + FEAT_OPA(HEADER_OSRELEASE, write_osrelease, print_osrelease), + FEAT_OPA(HEADER_VERSION, write_version, print_version), + FEAT_OPA(HEADER_ARCH, write_arch, print_arch), + FEAT_OPA(HEADER_NRCPUS, write_nrcpus, print_nrcpus), + FEAT_OPA(HEADER_CPUDESC, write_cpudesc, print_cpudesc), + FEAT_OPA(HEADER_CPUID, write_cpuid, print_cpuid), + FEAT_OPA(HEADER_TOTAL_MEM, write_total_mem, print_total_mem), + FEAT_OPA(HEADER_EVENT_DESC, write_event_desc, print_event_desc), + FEAT_OPA(HEADER_CMDLINE, write_cmdline, print_cmdline), + FEAT_OPF(HEADER_CPU_TOPOLOGY, write_cpu_topology, print_cpu_topology), + FEAT_OPF(HEADER_NUMA_TOPOLOGY, write_numa_topology, print_numa_topology), +}; + +struct header_print_data { + FILE *fp; + bool full; /* extended list of headers */ +}; + +static int perf_file_section__fprintf_info(struct perf_file_section *section, + struct perf_header *ph, + int feat, int fd, void *data) +{ + struct header_print_data *hd = data; + + if (lseek(fd, section->offset, SEEK_SET) == (off_t)-1) { + pr_debug("Failed to lseek to %" PRIu64 " offset for feature " + "%d, continuing...\n", section->offset, feat); + return 0; + } + if (feat < HEADER_TRACE_INFO || feat >= HEADER_LAST_FEATURE) { + pr_warning("unknown feature %d\n", feat); + return -1; + } + if (!feat_ops[feat].print) + return 0; + + if (!feat_ops[feat].full_only || hd->full) + feat_ops[feat].print(ph, fd, hd->fp); + else + fprintf(hd->fp, "# %s info available, use -I to display\n", + feat_ops[feat].name); + + return 0; +} + +int perf_header__fprintf_info(struct perf_session *session, FILE *fp, bool full) +{ + struct header_print_data hd; + struct perf_header *header = &session->header; + int fd = session->fd; + hd.fp = fp; + hd.full = full; + + perf_header__process_sections(header, fd, &hd, + perf_file_section__fprintf_info); + return 0; +} + #define dsos__for_each_with_build_id(pos, head) \ list_for_each_entry(pos, head, node) \ if (!pos->has_build_id) \ @@ -189,8 +1211,8 @@ int build_id_cache__add_s(const char *sbuild_id, const char *debugdir, const char *name, bool is_kallsyms) { const size_t size = PATH_MAX; - char *realname, *filename = malloc(size), - *linkname = malloc(size), *targetname; + char *realname, *filename = zalloc(size), + *linkname = zalloc(size), *targetname; int len, err = -1; if (is_kallsyms) { @@ -254,8 +1276,8 @@ static int build_id_cache__add_b(const u8 *build_id, size_t build_id_size, int build_id_cache__remove_s(const char *sbuild_id, const char *debugdir) { const size_t size = PATH_MAX; - char *filename = malloc(size), - *linkname = malloc(size); + char *filename = zalloc(size), + *linkname = zalloc(size); int err = -1; if (filename == NULL || linkname == NULL) @@ -267,7 +1289,7 @@ int build_id_cache__remove_s(const char *sbuild_id, const char *debugdir) if (access(linkname, F_OK)) goto out_free; - if (readlink(linkname, filename, size) < 0) + if (readlink(linkname, filename, size - 1) < 0) goto out_free; if (unlink(linkname)) @@ -356,15 +1378,41 @@ static bool perf_session__read_build_ids(struct perf_session *session, bool with return ret; } +static int do_write_feat(int fd, struct perf_header *h, int type, + struct perf_file_section **p, + struct perf_evlist *evlist) +{ + int err; + int ret = 0; + + if (perf_header__has_feat(h, type)) { + + (*p)->offset = lseek(fd, 0, SEEK_CUR); + + err = feat_ops[type].write(fd, h, evlist); + if (err < 0) { + pr_debug("failed to write feature %d\n", type); + + /* undo anything written */ + lseek(fd, (*p)->offset, SEEK_SET); + + return -1; + } + (*p)->size = lseek(fd, 0, SEEK_CUR) - (*p)->offset; + (*p)++; + } + return ret; +} + static int perf_header__adds_write(struct perf_header *header, struct perf_evlist *evlist, int fd) { int nr_sections; struct perf_session *session; - struct perf_file_section *feat_sec; + struct perf_file_section *feat_sec, *p; int sec_size; u64 sec_start; - int idx = 0, err; + int err; session = container_of(header, struct perf_session, header); @@ -376,7 +1424,7 @@ static int perf_header__adds_write(struct perf_header *header, if (!nr_sections) return 0; - feat_sec = calloc(sizeof(*feat_sec), nr_sections); + feat_sec = p = calloc(sizeof(*feat_sec), nr_sections); if (feat_sec == NULL) return -ENOMEM; @@ -385,36 +1433,69 @@ static int perf_header__adds_write(struct perf_header *header, sec_start = header->data_offset + header->data_size; lseek(fd, sec_start + sec_size, SEEK_SET); - if (perf_header__has_feat(header, HEADER_TRACE_INFO)) { - struct perf_file_section *trace_sec; - - trace_sec = &feat_sec[idx++]; + err = do_write_feat(fd, header, HEADER_TRACE_INFO, &p, evlist); + if (err) + goto out_free; - /* Write trace info */ - trace_sec->offset = lseek(fd, 0, SEEK_CUR); - read_tracing_data(fd, &evlist->entries); - trace_sec->size = lseek(fd, 0, SEEK_CUR) - trace_sec->offset; + err = do_write_feat(fd, header, HEADER_BUILD_ID, &p, evlist); + if (err) { + perf_header__clear_feat(header, HEADER_BUILD_ID); + goto out_free; } - if (perf_header__has_feat(header, HEADER_BUILD_ID)) { - struct perf_file_section *buildid_sec; + err = do_write_feat(fd, header, HEADER_HOSTNAME, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_HOSTNAME); - buildid_sec = &feat_sec[idx++]; + err = do_write_feat(fd, header, HEADER_OSRELEASE, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_OSRELEASE); - /* Write build-ids */ - buildid_sec->offset = lseek(fd, 0, SEEK_CUR); - err = dsos__write_buildid_table(header, fd); - if (err < 0) { - pr_debug("failed to write buildid table\n"); - goto out_free; - } - buildid_sec->size = lseek(fd, 0, SEEK_CUR) - - buildid_sec->offset; - if (!no_buildid_cache) - perf_session__cache_build_ids(session); - } + err = do_write_feat(fd, header, HEADER_VERSION, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_VERSION); + + err = do_write_feat(fd, header, HEADER_ARCH, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_ARCH); + + err = do_write_feat(fd, header, HEADER_NRCPUS, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_NRCPUS); + + err = do_write_feat(fd, header, HEADER_CPUDESC, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_CPUDESC); + + err = do_write_feat(fd, header, HEADER_CPUID, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_CPUID); + + err = do_write_feat(fd, header, HEADER_TOTAL_MEM, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_TOTAL_MEM); + + err = do_write_feat(fd, header, HEADER_CMDLINE, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_CMDLINE); + + err = do_write_feat(fd, header, HEADER_EVENT_DESC, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_EVENT_DESC); + + err = do_write_feat(fd, header, HEADER_CPU_TOPOLOGY, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_CPU_TOPOLOGY); + + err = do_write_feat(fd, header, HEADER_NUMA_TOPOLOGY, &p, evlist); + if (err) + perf_header__clear_feat(header, HEADER_NUMA_TOPOLOGY); lseek(fd, sec_start, SEEK_SET); + /* + * may write more than needed due to dropped feature, but + * this is okay, reader will skip the mising entries + */ err = do_write(fd, feat_sec, sec_size); if (err < 0) pr_debug("failed to write feature section\n"); @@ -554,9 +1635,10 @@ static int perf_header__getbuffer64(struct perf_header *header, } int perf_header__process_sections(struct perf_header *header, int fd, + void *data, int (*process)(struct perf_file_section *section, - struct perf_header *ph, - int feat, int fd)) + struct perf_header *ph, + int feat, int fd, void *data)) { struct perf_file_section *feat_sec; int nr_sections; @@ -584,7 +1666,7 @@ int perf_header__process_sections(struct perf_header *header, int fd, if (perf_header__has_feat(header, feat)) { struct perf_file_section *sec = &feat_sec[idx++]; - err = process(sec, header, feat, fd); + err = process(sec, header, feat, fd, data); if (err < 0) break; } @@ -621,21 +1703,41 @@ int perf_file_header__read(struct perf_file_header *header, bitmap_zero(header->adds_features, HEADER_FEAT_BITS); else return -1; + } else if (ph->needs_swap) { + unsigned int i; + /* + * feature bitmap is declared as an array of unsigned longs -- + * not good since its size can differ between the host that + * generated the data file and the host analyzing the file. + * + * We need to handle endianness, but we don't know the size of + * the unsigned long where the file was generated. Take a best + * guess at determining it: try 64-bit swap first (ie., file + * created on a 64-bit host), and check if the hostname feature + * bit is set (this feature bit is forced on as of fbe96f2). + * If the bit is not, undo the 64-bit swap and try a 32-bit + * swap. If the hostname bit is still not set (e.g., older data + * file), punt and fallback to the original behavior -- + * clearing all feature bits and setting buildid. + */ + for (i = 0; i < BITS_TO_LONGS(HEADER_FEAT_BITS); ++i) + header->adds_features[i] = bswap_64(header->adds_features[i]); + + if (!test_bit(HEADER_HOSTNAME, header->adds_features)) { + for (i = 0; i < BITS_TO_LONGS(HEADER_FEAT_BITS); ++i) { + header->adds_features[i] = bswap_64(header->adds_features[i]); + header->adds_features[i] = bswap_32(header->adds_features[i]); + } + } + + if (!test_bit(HEADER_HOSTNAME, header->adds_features)) { + bitmap_zero(header->adds_features, HEADER_FEAT_BITS); + set_bit(HEADER_BUILD_ID, header->adds_features); + } } memcpy(&ph->adds_features, &header->adds_features, sizeof(ph->adds_features)); - /* - * FIXME: hack that assumes that if we need swap the perf.data file - * may be coming from an arch with a different word-size, ergo different - * DEFINE_BITMAP format, investigate more later, but for now its mostly - * safe to assume that we have a build-id section. Trace files probably - * have several other issues in this realm anyway... - */ - if (ph->needs_swap) { - memset(&ph->adds_features, 0, sizeof(ph->adds_features)); - perf_header__set_feat(ph, HEADER_BUILD_ID); - } ph->event_offset = header->event_types.offset; ph->event_size = header->event_types.size; @@ -726,7 +1828,16 @@ static int perf_header__read_build_ids_abi_quirk(struct perf_header *header, return -1; bev.header = old_bev.header; - bev.pid = 0; + + /* + * As the pid is the missing value, we need to fill + * it properly. The header.misc value give us nice hint. + */ + bev.pid = HOST_KERNEL_ID; + if (bev.header.misc == PERF_RECORD_MISC_GUEST_USER || + bev.header.misc == PERF_RECORD_MISC_GUEST_KERNEL) + bev.pid = DEFAULT_GUEST_KERNEL_ID; + memcpy(bev.build_id, old_bev.build_id, sizeof(bev.build_id)); __event_process_build_id(&bev, filename, session); @@ -787,7 +1898,7 @@ out: static int perf_file_section__process(struct perf_file_section *section, struct perf_header *ph, - int feat, int fd) + int feat, int fd, void *data __used) { if (lseek(fd, section->offset, SEEK_SET) == (off_t)-1) { pr_debug("Failed to lseek to %" PRIu64 " offset for feature " @@ -804,6 +1915,21 @@ static int perf_file_section__process(struct perf_file_section *section, if (perf_header__read_build_ids(ph, fd, section->offset, section->size)) pr_debug("Failed to read buildids, continuing...\n"); break; + + case HEADER_HOSTNAME: + case HEADER_OSRELEASE: + case HEADER_VERSION: + case HEADER_ARCH: + case HEADER_NRCPUS: + case HEADER_CPUDESC: + case HEADER_CPUID: + case HEADER_TOTAL_MEM: + case HEADER_CMDLINE: + case HEADER_EVENT_DESC: + case HEADER_CPU_TOPOLOGY: + case HEADER_NUMA_TOPOLOGY: + break; + default: pr_debug("unknown feature %d, continuing...\n", feat); } @@ -877,9 +2003,12 @@ int perf_session__read_header(struct perf_session *session, int fd) struct perf_evsel *evsel; off_t tmp; - if (perf_header__getbuffer64(header, fd, &f_attr, sizeof(f_attr))) + if (readn(fd, &f_attr, sizeof(f_attr)) <= 0) goto out_errno; + if (header->needs_swap) + perf_event__attr_swap(&f_attr.attr); + tmp = lseek(fd, 0, SEEK_CUR); evsel = perf_evsel__new(&f_attr.attr, i); @@ -923,7 +2052,8 @@ int perf_session__read_header(struct perf_session *session, int fd) event_count = f_header.event_types.size / sizeof(struct perf_trace_event_type); } - perf_header__process_sections(header, fd, perf_file_section__process); + perf_header__process_sections(header, fd, NULL, + perf_file_section__process); lseek(fd, header->data_offset, SEEK_SET); @@ -1088,15 +2218,29 @@ int perf_event__synthesize_tracing_data(int fd, struct perf_evlist *evlist, struct perf_session *session __unused) { union perf_event ev; + struct tracing_data *tdata; ssize_t size = 0, aligned_size = 0, padding; int err __used = 0; + /* + * We are going to store the size of the data followed + * by the data contents. Since the fd descriptor is a pipe, + * we cannot seek back to store the size of the data once + * we know it. Instead we: + * + * - write the tracing data to the temp file + * - get/write the data size to pipe + * - write the tracing data from the temp file + * to the pipe + */ + tdata = tracing_data_get(&evlist->entries, fd, true); + if (!tdata) + return -1; + memset(&ev, 0, sizeof(ev)); ev.tracing_data.header.type = PERF_RECORD_HEADER_TRACING_DATA; - size = read_tracing_data_size(fd, &evlist->entries); - if (size <= 0) - return size; + size = tdata->size; aligned_size = ALIGN(size, sizeof(u64)); padding = aligned_size - size; ev.tracing_data.header.size = sizeof(ev.tracing_data); @@ -1104,7 +2248,12 @@ int perf_event__synthesize_tracing_data(int fd, struct perf_evlist *evlist, process(&ev, NULL, session); - err = read_tracing_data(fd, &evlist->entries); + /* + * The put function will copy all the tracing data + * stored in temp file to the pipe. + */ + tracing_data_put(tdata); + write_padded(fd, NULL, 0, padding); return aligned_size; diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index 1886256768a1..3d5a742f4a2a 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -12,6 +12,20 @@ enum { HEADER_TRACE_INFO = 1, HEADER_BUILD_ID, + + HEADER_HOSTNAME, + HEADER_OSRELEASE, + HEADER_VERSION, + HEADER_ARCH, + HEADER_NRCPUS, + HEADER_CPUDESC, + HEADER_CPUID, + HEADER_TOTAL_MEM, + HEADER_CMDLINE, + HEADER_EVENT_DESC, + HEADER_CPU_TOPOLOGY, + HEADER_NUMA_TOPOLOGY, + HEADER_LAST_FEATURE, }; @@ -68,10 +82,15 @@ void perf_header__set_feat(struct perf_header *header, int feat); void perf_header__clear_feat(struct perf_header *header, int feat); bool perf_header__has_feat(const struct perf_header *header, int feat); +int perf_header__set_cmdline(int argc, const char **argv); + int perf_header__process_sections(struct perf_header *header, int fd, + void *data, int (*process)(struct perf_file_section *section, - struct perf_header *ph, - int feat, int fd)); + struct perf_header *ph, + int feat, int fd, void *data)); + +int perf_header__fprintf_info(struct perf_session *s, FILE *fp, bool full); int build_id_cache__add_s(const char *sbuild_id, const char *debugdir, const char *name, bool is_kallsyms); @@ -104,4 +123,10 @@ int perf_event__synthesize_build_id(struct dso *pos, u16 misc, struct perf_session *session); int perf_event__process_build_id(union perf_event *event, struct perf_session *session); + +/* + * arch specific callback + */ +int get_cpuid(char *buffer, size_t sz); + #endif /* __PERF_HEADER_H */ diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 627a02e03c57..a36a3fa81ffb 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -6,6 +6,11 @@ #include "sort.h" #include <math.h> +static bool hists__filter_entry_by_dso(struct hists *hists, + struct hist_entry *he); +static bool hists__filter_entry_by_thread(struct hists *hists, + struct hist_entry *he); + enum hist_filter { HIST_FILTER__DSO, HIST_FILTER__THREAD, @@ -14,59 +19,60 @@ enum hist_filter { struct callchain_param callchain_param = { .mode = CHAIN_GRAPH_REL, - .min_percent = 0.5 + .min_percent = 0.5, + .order = ORDER_CALLEE }; -u16 hists__col_len(struct hists *self, enum hist_column col) +u16 hists__col_len(struct hists *hists, enum hist_column col) { - return self->col_len[col]; + return hists->col_len[col]; } -void hists__set_col_len(struct hists *self, enum hist_column col, u16 len) +void hists__set_col_len(struct hists *hists, enum hist_column col, u16 len) { - self->col_len[col] = len; + hists->col_len[col] = len; } -bool hists__new_col_len(struct hists *self, enum hist_column col, u16 len) +bool hists__new_col_len(struct hists *hists, enum hist_column col, u16 len) { - if (len > hists__col_len(self, col)) { - hists__set_col_len(self, col, len); + if (len > hists__col_len(hists, col)) { + hists__set_col_len(hists, col, len); return true; } return false; } -static void hists__reset_col_len(struct hists *self) +static void hists__reset_col_len(struct hists *hists) { enum hist_column col; for (col = 0; col < HISTC_NR_COLS; ++col) - hists__set_col_len(self, col, 0); + hists__set_col_len(hists, col, 0); } -static void hists__calc_col_len(struct hists *self, struct hist_entry *h) +static void hists__calc_col_len(struct hists *hists, struct hist_entry *h) { u16 len; if (h->ms.sym) - hists__new_col_len(self, HISTC_SYMBOL, h->ms.sym->namelen); + hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen); else { const unsigned int unresolved_col_width = BITS_PER_LONG / 4; - if (hists__col_len(self, HISTC_DSO) < unresolved_col_width && + if (hists__col_len(hists, HISTC_DSO) < unresolved_col_width && !symbol_conf.col_width_list_str && !symbol_conf.field_sep && !symbol_conf.dso_list) - hists__set_col_len(self, HISTC_DSO, + hists__set_col_len(hists, HISTC_DSO, unresolved_col_width); } len = thread__comm_len(h->thread); - if (hists__new_col_len(self, HISTC_COMM, len)) - hists__set_col_len(self, HISTC_THREAD, len + 6); + if (hists__new_col_len(hists, HISTC_COMM, len)) + hists__set_col_len(hists, HISTC_THREAD, len + 6); if (h->ms.map) { len = dso__name_len(h->ms.map->dso); - hists__new_col_len(self, HISTC_DSO, len); + hists__new_col_len(hists, HISTC_DSO, len); } } @@ -91,6 +97,67 @@ static void hist_entry__add_cpumode_period(struct hist_entry *self, } } +static void hist_entry__decay(struct hist_entry *he) +{ + he->period = (he->period * 7) / 8; + he->nr_events = (he->nr_events * 7) / 8; +} + +static bool hists__decay_entry(struct hists *hists, struct hist_entry *he) +{ + u64 prev_period = he->period; + + if (prev_period == 0) + return true; + + hist_entry__decay(he); + + if (!he->filtered) + hists->stats.total_period -= prev_period - he->period; + + return he->period == 0; +} + +static void __hists__decay_entries(struct hists *hists, bool zap_user, + bool zap_kernel, bool threaded) +{ + struct rb_node *next = rb_first(&hists->entries); + struct hist_entry *n; + + while (next) { + n = rb_entry(next, struct hist_entry, rb_node); + next = rb_next(&n->rb_node); + /* + * We may be annotating this, for instance, so keep it here in + * case some it gets new samples, we'll eventually free it when + * the user stops browsing and it agains gets fully decayed. + */ + if (((zap_user && n->level == '.') || + (zap_kernel && n->level != '.') || + hists__decay_entry(hists, n)) && + !n->used) { + rb_erase(&n->rb_node, &hists->entries); + + if (sort__need_collapse || threaded) + rb_erase(&n->rb_node_in, &hists->entries_collapsed); + + hist_entry__free(n); + --hists->nr_entries; + } + } +} + +void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel) +{ + return __hists__decay_entries(hists, zap_user, zap_kernel, false); +} + +void hists__decay_entries_threaded(struct hists *hists, + bool zap_user, bool zap_kernel) +{ + return __hists__decay_entries(hists, zap_user, zap_kernel, true); +} + /* * histogram, sorted on item, collects periods */ @@ -112,11 +179,12 @@ static struct hist_entry *hist_entry__new(struct hist_entry *template) return self; } -static void hists__inc_nr_entries(struct hists *self, struct hist_entry *h) +static void hists__inc_nr_entries(struct hists *hists, struct hist_entry *h) { if (!h->filtered) { - hists__calc_col_len(self, h); - ++self->nr_entries; + hists__calc_col_len(hists, h); + ++hists->nr_entries; + hists->stats.total_period += h->period; } } @@ -127,11 +195,11 @@ static u8 symbol__parent_filter(const struct symbol *parent) return 0; } -struct hist_entry *__hists__add_entry(struct hists *self, +struct hist_entry *__hists__add_entry(struct hists *hists, struct addr_location *al, struct symbol *sym_parent, u64 period) { - struct rb_node **p = &self->entries.rb_node; + struct rb_node **p; struct rb_node *parent = NULL; struct hist_entry *he; struct hist_entry entry = { @@ -149,9 +217,13 @@ struct hist_entry *__hists__add_entry(struct hists *self, }; int cmp; + pthread_mutex_lock(&hists->lock); + + p = &hists->entries_in->rb_node; + while (*p != NULL) { parent = *p; - he = rb_entry(parent, struct hist_entry, rb_node); + he = rb_entry(parent, struct hist_entry, rb_node_in); cmp = hist_entry__cmp(&entry, he); @@ -169,12 +241,14 @@ struct hist_entry *__hists__add_entry(struct hists *self, he = hist_entry__new(&entry); if (!he) - return NULL; - rb_link_node(&he->rb_node, parent, p); - rb_insert_color(&he->rb_node, &self->entries); - hists__inc_nr_entries(self, he); + goto out_unlock; + + rb_link_node(&he->rb_node_in, parent, p); + rb_insert_color(&he->rb_node_in, hists->entries_in); out: hist_entry__add_cpumode_period(he, al->cpumode, period); +out_unlock: + pthread_mutex_unlock(&hists->lock); return he; } @@ -221,7 +295,7 @@ void hist_entry__free(struct hist_entry *he) * collapse the histogram */ -static bool hists__collapse_insert_entry(struct hists *self, +static bool hists__collapse_insert_entry(struct hists *hists, struct rb_root *root, struct hist_entry *he) { @@ -232,15 +306,16 @@ static bool hists__collapse_insert_entry(struct hists *self, while (*p != NULL) { parent = *p; - iter = rb_entry(parent, struct hist_entry, rb_node); + iter = rb_entry(parent, struct hist_entry, rb_node_in); cmp = hist_entry__collapse(iter, he); if (!cmp) { iter->period += he->period; + iter->nr_events += he->nr_events; if (symbol_conf.use_callchain) { - callchain_cursor_reset(&self->callchain_cursor); - callchain_merge(&self->callchain_cursor, iter->callchain, + callchain_cursor_reset(&hists->callchain_cursor); + callchain_merge(&hists->callchain_cursor, iter->callchain, he->callchain); } hist_entry__free(he); @@ -253,35 +328,68 @@ static bool hists__collapse_insert_entry(struct hists *self, p = &(*p)->rb_right; } - rb_link_node(&he->rb_node, parent, p); - rb_insert_color(&he->rb_node, root); + rb_link_node(&he->rb_node_in, parent, p); + rb_insert_color(&he->rb_node_in, root); return true; } -void hists__collapse_resort(struct hists *self) +static struct rb_root *hists__get_rotate_entries_in(struct hists *hists) +{ + struct rb_root *root; + + pthread_mutex_lock(&hists->lock); + + root = hists->entries_in; + if (++hists->entries_in > &hists->entries_in_array[1]) + hists->entries_in = &hists->entries_in_array[0]; + + pthread_mutex_unlock(&hists->lock); + + return root; +} + +static void hists__apply_filters(struct hists *hists, struct hist_entry *he) +{ + hists__filter_entry_by_dso(hists, he); + hists__filter_entry_by_thread(hists, he); +} + +static void __hists__collapse_resort(struct hists *hists, bool threaded) { - struct rb_root tmp; + struct rb_root *root; struct rb_node *next; struct hist_entry *n; - if (!sort__need_collapse) + if (!sort__need_collapse && !threaded) return; - tmp = RB_ROOT; - next = rb_first(&self->entries); - self->nr_entries = 0; - hists__reset_col_len(self); + root = hists__get_rotate_entries_in(hists); + next = rb_first(root); while (next) { - n = rb_entry(next, struct hist_entry, rb_node); - next = rb_next(&n->rb_node); - - rb_erase(&n->rb_node, &self->entries); - if (hists__collapse_insert_entry(self, &tmp, n)) - hists__inc_nr_entries(self, n); + n = rb_entry(next, struct hist_entry, rb_node_in); + next = rb_next(&n->rb_node_in); + + rb_erase(&n->rb_node_in, root); + if (hists__collapse_insert_entry(hists, &hists->entries_collapsed, n)) { + /* + * If it wasn't combined with one of the entries already + * collapsed, we need to apply the filters that may have + * been set by, say, the hist_browser. + */ + hists__apply_filters(hists, n); + } } +} + +void hists__collapse_resort(struct hists *hists) +{ + return __hists__collapse_resort(hists, false); +} - self->entries = tmp; +void hists__collapse_resort_threaded(struct hists *hists) +{ + return __hists__collapse_resort(hists, true); } /* @@ -314,31 +422,44 @@ static void __hists__insert_output_entry(struct rb_root *entries, rb_insert_color(&he->rb_node, entries); } -void hists__output_resort(struct hists *self) +static void __hists__output_resort(struct hists *hists, bool threaded) { - struct rb_root tmp; + struct rb_root *root; struct rb_node *next; struct hist_entry *n; u64 min_callchain_hits; - min_callchain_hits = self->stats.total_period * (callchain_param.min_percent / 100); + min_callchain_hits = hists->stats.total_period * (callchain_param.min_percent / 100); + + if (sort__need_collapse || threaded) + root = &hists->entries_collapsed; + else + root = hists->entries_in; - tmp = RB_ROOT; - next = rb_first(&self->entries); + next = rb_first(root); + hists->entries = RB_ROOT; - self->nr_entries = 0; - hists__reset_col_len(self); + hists->nr_entries = 0; + hists->stats.total_period = 0; + hists__reset_col_len(hists); while (next) { - n = rb_entry(next, struct hist_entry, rb_node); - next = rb_next(&n->rb_node); + n = rb_entry(next, struct hist_entry, rb_node_in); + next = rb_next(&n->rb_node_in); - rb_erase(&n->rb_node, &self->entries); - __hists__insert_output_entry(&tmp, n, min_callchain_hits); - hists__inc_nr_entries(self, n); + __hists__insert_output_entry(&hists->entries, n, min_callchain_hits); + hists__inc_nr_entries(hists, n); } +} - self->entries = tmp; +void hists__output_resort(struct hists *hists) +{ + return __hists__output_resort(hists, false); +} + +void hists__output_resort_threaded(struct hists *hists) +{ + return __hists__output_resort(hists, true); } static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin) @@ -593,12 +714,27 @@ static size_t hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, return ret; } -int hist_entry__snprintf(struct hist_entry *self, char *s, size_t size, - struct hists *hists, struct hists *pair_hists, - bool show_displacement, long displacement, - bool color, u64 session_total) +void hists__output_recalc_col_len(struct hists *hists, int max_rows) +{ + struct rb_node *next = rb_first(&hists->entries); + struct hist_entry *n; + int row = 0; + + hists__reset_col_len(hists); + + while (next && row++ < max_rows) { + n = rb_entry(next, struct hist_entry, rb_node); + if (!n->filtered) + hists__calc_col_len(hists, n); + next = rb_next(&n->rb_node); + } +} + +static int hist_entry__pcnt_snprintf(struct hist_entry *self, char *s, + size_t size, struct hists *pair_hists, + bool show_displacement, long displacement, + bool color, u64 session_total) { - struct sort_entry *se; u64 period, total, period_sys, period_us, period_guest_sys, period_guest_us; u64 nr_events; const char *sep = symbol_conf.field_sep; @@ -663,6 +799,13 @@ int hist_entry__snprintf(struct hist_entry *self, char *s, size_t size, ret += snprintf(s + ret, size - ret, "%11" PRIu64, nr_events); } + if (symbol_conf.show_total_period) { + if (sep) + ret += snprintf(s + ret, size - ret, "%c%" PRIu64, *sep, period); + else + ret += snprintf(s + ret, size - ret, " %12" PRIu64, period); + } + if (pair_hists) { char bf[32]; double old_percent = 0, new_percent = 0, diff; @@ -697,26 +840,42 @@ int hist_entry__snprintf(struct hist_entry *self, char *s, size_t size, } } + return ret; +} + +int hist_entry__snprintf(struct hist_entry *he, char *s, size_t size, + struct hists *hists) +{ + const char *sep = symbol_conf.field_sep; + struct sort_entry *se; + int ret = 0; + list_for_each_entry(se, &hist_entry__sort_list, list) { if (se->elide) continue; ret += snprintf(s + ret, size - ret, "%s", sep ?: " "); - ret += se->se_snprintf(self, s + ret, size - ret, + ret += se->se_snprintf(he, s + ret, size - ret, hists__col_len(hists, se->se_width_idx)); } return ret; } -int hist_entry__fprintf(struct hist_entry *self, struct hists *hists, +int hist_entry__fprintf(struct hist_entry *he, size_t size, struct hists *hists, struct hists *pair_hists, bool show_displacement, long displacement, FILE *fp, u64 session_total) { char bf[512]; - hist_entry__snprintf(self, bf, sizeof(bf), hists, pair_hists, - show_displacement, displacement, - true, session_total); + int ret; + + if (size == 0 || size > sizeof(bf)) + size = sizeof(bf); + + ret = hist_entry__pcnt_snprintf(he, bf, size, pair_hists, + show_displacement, displacement, + true, session_total); + hist_entry__snprintf(he, bf + ret, size - ret, hists); return fprintf(fp, "%s\n", bf); } @@ -737,8 +896,9 @@ static size_t hist_entry__fprintf_callchain(struct hist_entry *self, left_margin); } -size_t hists__fprintf(struct hists *self, struct hists *pair, - bool show_displacement, FILE *fp) +size_t hists__fprintf(struct hists *hists, struct hists *pair, + bool show_displacement, bool show_header, int max_rows, + int max_cols, FILE *fp) { struct sort_entry *se; struct rb_node *nd; @@ -748,9 +908,13 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, unsigned int width; const char *sep = symbol_conf.field_sep; const char *col_width = symbol_conf.col_width_list_str; + int nr_rows = 0; init_rem_hits(); + if (!show_header) + goto print_entries; + fprintf(fp, "# %s", pair ? "Baseline" : "Overhead"); if (symbol_conf.show_nr_samples) { @@ -760,6 +924,13 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, fputs(" Samples ", fp); } + if (symbol_conf.show_total_period) { + if (sep) + ret += fprintf(fp, "%cPeriod", *sep); + else + ret += fprintf(fp, " Period "); + } + if (symbol_conf.show_cpu_utilization) { if (sep) { ret += fprintf(fp, "%csys", *sep); @@ -802,18 +973,21 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, width = strlen(se->se_header); if (symbol_conf.col_width_list_str) { if (col_width) { - hists__set_col_len(self, se->se_width_idx, + hists__set_col_len(hists, se->se_width_idx, atoi(col_width)); col_width = strchr(col_width, ','); if (col_width) ++col_width; } } - if (!hists__new_col_len(self, se->se_width_idx, width)) - width = hists__col_len(self, se->se_width_idx); + if (!hists__new_col_len(hists, se->se_width_idx, width)) + width = hists__col_len(hists, se->se_width_idx); fprintf(fp, " %*s", width, se->se_header); } + fprintf(fp, "\n"); + if (max_rows && ++nr_rows >= max_rows) + goto out; if (sep) goto print_entries; @@ -821,6 +995,8 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, fprintf(fp, "# ........"); if (symbol_conf.show_nr_samples) fprintf(fp, " .........."); + if (symbol_conf.show_total_period) + fprintf(fp, " ............"); if (pair) { fprintf(fp, " .........."); if (show_displacement) @@ -833,19 +1009,28 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, continue; fprintf(fp, " "); - width = hists__col_len(self, se->se_width_idx); + width = hists__col_len(hists, se->se_width_idx); if (width == 0) width = strlen(se->se_header); for (i = 0; i < width; i++) fprintf(fp, "."); } - fprintf(fp, "\n#\n"); + fprintf(fp, "\n"); + if (max_rows && ++nr_rows >= max_rows) + goto out; + + fprintf(fp, "#\n"); + if (max_rows && ++nr_rows >= max_rows) + goto out; print_entries: - for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { + for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); + if (h->filtered) + continue; + if (show_displacement) { if (h->pair != NULL) displacement = ((long)h->pair->position - @@ -854,19 +1039,22 @@ print_entries: displacement = 0; ++position; } - ret += hist_entry__fprintf(h, self, pair, show_displacement, - displacement, fp, self->stats.total_period); + ret += hist_entry__fprintf(h, max_cols, hists, pair, show_displacement, + displacement, fp, hists->stats.total_period); if (symbol_conf.use_callchain) - ret += hist_entry__fprintf_callchain(h, self, fp, - self->stats.total_period); + ret += hist_entry__fprintf_callchain(h, hists, fp, + hists->stats.total_period); + if (max_rows && ++nr_rows >= max_rows) + goto out; + if (h->ms.map == NULL && verbose > 1) { __map_groups__fprintf_maps(&h->thread->mg, MAP__FUNCTION, verbose, fp); fprintf(fp, "%.10s end\n", graph_dotted_line); } } - +out: free(rem_sq_bracket); return ret; @@ -875,7 +1063,7 @@ print_entries: /* * See hists__fprintf to match the column widths */ -unsigned int hists__sort_list_width(struct hists *self) +unsigned int hists__sort_list_width(struct hists *hists) { struct sort_entry *se; int ret = 9; /* total % */ @@ -892,9 +1080,12 @@ unsigned int hists__sort_list_width(struct hists *self) if (symbol_conf.show_nr_samples) ret += 11; + if (symbol_conf.show_total_period) + ret += 13; + list_for_each_entry(se, &hist_entry__sort_list, list) if (!se->elide) - ret += 2 + hists__col_len(self, se->se_width_idx); + ret += 2 + hists__col_len(hists, se->se_width_idx); if (verbose) /* Addr + origin */ ret += 3 + BITS_PER_LONG / 4; @@ -902,63 +1093,84 @@ unsigned int hists__sort_list_width(struct hists *self) return ret; } -static void hists__remove_entry_filter(struct hists *self, struct hist_entry *h, +static void hists__remove_entry_filter(struct hists *hists, struct hist_entry *h, enum hist_filter filter) { h->filtered &= ~(1 << filter); if (h->filtered) return; - ++self->nr_entries; + ++hists->nr_entries; if (h->ms.unfolded) - self->nr_entries += h->nr_rows; + hists->nr_entries += h->nr_rows; h->row_offset = 0; - self->stats.total_period += h->period; - self->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events; + hists->stats.total_period += h->period; + hists->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events; - hists__calc_col_len(self, h); + hists__calc_col_len(hists, h); } -void hists__filter_by_dso(struct hists *self, const struct dso *dso) + +static bool hists__filter_entry_by_dso(struct hists *hists, + struct hist_entry *he) +{ + if (hists->dso_filter != NULL && + (he->ms.map == NULL || he->ms.map->dso != hists->dso_filter)) { + he->filtered |= (1 << HIST_FILTER__DSO); + return true; + } + + return false; +} + +void hists__filter_by_dso(struct hists *hists) { struct rb_node *nd; - self->nr_entries = self->stats.total_period = 0; - self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; - hists__reset_col_len(self); + hists->nr_entries = hists->stats.total_period = 0; + hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0; + hists__reset_col_len(hists); - for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { + for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); if (symbol_conf.exclude_other && !h->parent) continue; - if (dso != NULL && (h->ms.map == NULL || h->ms.map->dso != dso)) { - h->filtered |= (1 << HIST_FILTER__DSO); + if (hists__filter_entry_by_dso(hists, h)) continue; - } - hists__remove_entry_filter(self, h, HIST_FILTER__DSO); + hists__remove_entry_filter(hists, h, HIST_FILTER__DSO); } } -void hists__filter_by_thread(struct hists *self, const struct thread *thread) +static bool hists__filter_entry_by_thread(struct hists *hists, + struct hist_entry *he) +{ + if (hists->thread_filter != NULL && + he->thread != hists->thread_filter) { + he->filtered |= (1 << HIST_FILTER__THREAD); + return true; + } + + return false; +} + +void hists__filter_by_thread(struct hists *hists) { struct rb_node *nd; - self->nr_entries = self->stats.total_period = 0; - self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; - hists__reset_col_len(self); + hists->nr_entries = hists->stats.total_period = 0; + hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0; + hists__reset_col_len(hists); - for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { + for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - if (thread != NULL && h->thread != thread) { - h->filtered |= (1 << HIST_FILTER__THREAD); + if (hists__filter_entry_by_thread(hists, h)) continue; - } - hists__remove_entry_filter(self, h, HIST_FILTER__THREAD); + hists__remove_entry_filter(hists, h, HIST_FILTER__THREAD); } } @@ -972,13 +1184,13 @@ int hist_entry__annotate(struct hist_entry *he, size_t privsize) return symbol__annotate(he->ms.sym, he->ms.map, privsize); } -void hists__inc_nr_events(struct hists *self, u32 type) +void hists__inc_nr_events(struct hists *hists, u32 type) { - ++self->stats.nr_events[0]; - ++self->stats.nr_events[type]; + ++hists->stats.nr_events[0]; + ++hists->stats.nr_events[type]; } -size_t hists__fprintf_nr_events(struct hists *self, FILE *fp) +size_t hists__fprintf_nr_events(struct hists *hists, FILE *fp) { int i; size_t ret = 0; @@ -986,7 +1198,7 @@ size_t hists__fprintf_nr_events(struct hists *self, FILE *fp) for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) { const char *name; - if (self->stats.nr_events[i] == 0) + if (hists->stats.nr_events[i] == 0) continue; name = perf_event__name(i); @@ -994,8 +1206,18 @@ size_t hists__fprintf_nr_events(struct hists *self, FILE *fp) continue; ret += fprintf(fp, "%16s events: %10d\n", name, - self->stats.nr_events[i]); + hists->stats.nr_events[i]); } return ret; } + +void hists__init(struct hists *hists) +{ + memset(hists, 0, sizeof(*hists)); + hists->entries_in_array[0] = hists->entries_in_array[1] = RB_ROOT; + hists->entries_in = &hists->entries_in_array[0]; + hists->entries_collapsed = RB_ROOT; + hists->entries = RB_ROOT; + pthread_mutex_init(&hists->lock, NULL); +} diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 3beb97c4d822..c86c1d27bd1e 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -2,6 +2,7 @@ #define __PERF_HIST_H #include <linux/types.h> +#include <pthread.h> #include "callchain.h" extern struct callchain_param callchain_param; @@ -27,6 +28,7 @@ struct events_stats { u64 total_lost; u64 total_invalid_chains; u32 nr_events[PERF_RECORD_HEADER_MAX]; + u32 nr_lost_warned; u32 nr_unknown_events; u32 nr_invalid_chains; u32 nr_unknown_id; @@ -42,9 +44,18 @@ enum hist_column { HISTC_NR_COLS, /* Last entry */ }; +struct thread; +struct dso; + struct hists { + struct rb_root entries_in_array[2]; + struct rb_root *entries_in; struct rb_root entries; + struct rb_root entries_collapsed; u64 nr_entries; + const struct thread *thread_filter; + const struct dso *dso_filter; + pthread_mutex_t lock; struct events_stats stats; u64 event_stream; u16 col_len[HISTC_NR_COLS]; @@ -52,34 +63,42 @@ struct hists { struct callchain_cursor callchain_cursor; }; +void hists__init(struct hists *hists); + struct hist_entry *__hists__add_entry(struct hists *self, struct addr_location *al, struct symbol *parent, u64 period); extern int64_t hist_entry__cmp(struct hist_entry *, struct hist_entry *); extern int64_t hist_entry__collapse(struct hist_entry *, struct hist_entry *); -int hist_entry__fprintf(struct hist_entry *self, struct hists *hists, +int hist_entry__fprintf(struct hist_entry *he, size_t size, struct hists *hists, struct hists *pair_hists, bool show_displacement, - long displacement, FILE *fp, u64 total); + long displacement, FILE *fp, u64 session_total); int hist_entry__snprintf(struct hist_entry *self, char *bf, size_t size, - struct hists *hists, struct hists *pair_hists, - bool show_displacement, long displacement, - bool color, u64 total); + struct hists *hists); void hist_entry__free(struct hist_entry *); void hists__output_resort(struct hists *self); +void hists__output_resort_threaded(struct hists *hists); void hists__collapse_resort(struct hists *self); +void hists__collapse_resort_threaded(struct hists *hists); + +void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel); +void hists__decay_entries_threaded(struct hists *hists, bool zap_user, + bool zap_kernel); +void hists__output_recalc_col_len(struct hists *hists, int max_rows); void hists__inc_nr_events(struct hists *self, u32 type); size_t hists__fprintf_nr_events(struct hists *self, FILE *fp); size_t hists__fprintf(struct hists *self, struct hists *pair, - bool show_displacement, FILE *fp); + bool show_displacement, bool show_header, + int max_rows, int max_cols, FILE *fp); int hist_entry__inc_addr_samples(struct hist_entry *self, int evidx, u64 addr); int hist_entry__annotate(struct hist_entry *self, size_t privsize); -void hists__filter_by_dso(struct hists *self, const struct dso *dso); -void hists__filter_by_thread(struct hists *self, const struct thread *thread); +void hists__filter_by_dso(struct hists *hists); +void hists__filter_by_thread(struct hists *hists); u16 hists__col_len(struct hists *self, enum hist_column col); void hists__set_col_len(struct hists *self, enum hist_column col, u16 len); @@ -90,26 +109,33 @@ struct perf_evlist; #ifdef NO_NEWT_SUPPORT static inline int perf_evlist__tui_browse_hists(struct perf_evlist *evlist __used, - const char *help __used) + const char *help __used, + void(*timer)(void *arg) __used, + void *arg __used, + int refresh __used) { return 0; } static inline int hist_entry__tui_annotate(struct hist_entry *self __used, - int evidx __used) + int evidx __used, + int nr_events __used, + void(*timer)(void *arg) __used, + void *arg __used, + int delay_secs __used) { return 0; } -#define KEY_LEFT -1 -#define KEY_RIGHT -2 +#define K_LEFT -1 +#define K_RIGHT -2 #else -#include <newt.h> -int hist_entry__tui_annotate(struct hist_entry *self, int evidx); - -#define KEY_LEFT NEWT_KEY_LEFT -#define KEY_RIGHT NEWT_KEY_RIGHT +#include "ui/keysyms.h" +int hist_entry__tui_annotate(struct hist_entry *he, int evidx, int nr_events, + void(*timer)(void *arg), void *arg, int delay_secs); -int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help); +int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help, + void(*timer)(void *arg), void *arg, + int refresh); #endif unsigned int hists__sort_list_width(struct hists *self); diff --git a/tools/perf/util/include/linux/compiler.h b/tools/perf/util/include/linux/compiler.h index 791f9dd27ebf..547628e97f3d 100644 --- a/tools/perf/util/include/linux/compiler.h +++ b/tools/perf/util/include/linux/compiler.h @@ -5,7 +5,9 @@ #define __always_inline inline #endif #define __user +#ifndef __attribute_const__ #define __attribute_const__ +#endif #define __used __attribute__((__unused__)) diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index a16ecab5229d..78284b13e808 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c @@ -18,6 +18,13 @@ static inline int is_anon_memory(const char *filename) return strcmp(filename, "//anon") == 0; } +static inline int is_no_dso_memory(const char *filename) +{ + return !strcmp(filename, "[stack]") || + !strcmp(filename, "[vdso]") || + !strcmp(filename, "[heap]"); +} + void map__init(struct map *self, enum map_type type, u64 start, u64 end, u64 pgoff, struct dso *dso) { @@ -42,9 +49,10 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, if (self != NULL) { char newfilename[PATH_MAX]; struct dso *dso; - int anon; + int anon, no_dso; anon = is_anon_memory(filename); + no_dso = is_no_dso_memory(filename); if (anon) { snprintf(newfilename, sizeof(newfilename), "/tmp/perf-%d.map", pid); @@ -57,12 +65,16 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, map__init(self, type, start, start + len, pgoff, dso); - if (anon) { -set_identity: + if (anon || no_dso) { self->map_ip = self->unmap_ip = identity__map_ip; - } else if (strcmp(filename, "[vdso]") == 0) { - dso__set_loaded(dso, self->type); - goto set_identity; + + /* + * Set memory without DSO as loaded. All map__find_* + * functions still return NULL, and we avoid the + * unnecessary map__load warning. + */ + if (no_dso) + dso__set_loaded(dso, self->type); } } return self; @@ -127,8 +139,8 @@ int map__load(struct map *self, symbol_filter_t filter) if (len > sizeof(DSO__DELETED) && strcmp(name + real_len + 1, DSO__DELETED) == 0) { - pr_warning("%.*s was updated, restart the long " - "running apps that use it!\n", + pr_warning("%.*s was updated (is prelink enabled?). " + "Restart the long running apps that use it!\n", (int)real_len, name); } else { pr_warning("no symbols found in %s, maybe install " @@ -220,55 +232,55 @@ u64 map__objdump_2ip(struct map *map, u64 addr) return ip; } -void map_groups__init(struct map_groups *self) +void map_groups__init(struct map_groups *mg) { int i; for (i = 0; i < MAP__NR_TYPES; ++i) { - self->maps[i] = RB_ROOT; - INIT_LIST_HEAD(&self->removed_maps[i]); + mg->maps[i] = RB_ROOT; + INIT_LIST_HEAD(&mg->removed_maps[i]); } - self->machine = NULL; + mg->machine = NULL; } -static void maps__delete(struct rb_root *self) +static void maps__delete(struct rb_root *maps) { - struct rb_node *next = rb_first(self); + struct rb_node *next = rb_first(maps); while (next) { struct map *pos = rb_entry(next, struct map, rb_node); next = rb_next(&pos->rb_node); - rb_erase(&pos->rb_node, self); + rb_erase(&pos->rb_node, maps); map__delete(pos); } } -static void maps__delete_removed(struct list_head *self) +static void maps__delete_removed(struct list_head *maps) { struct map *pos, *n; - list_for_each_entry_safe(pos, n, self, node) { + list_for_each_entry_safe(pos, n, maps, node) { list_del(&pos->node); map__delete(pos); } } -void map_groups__exit(struct map_groups *self) +void map_groups__exit(struct map_groups *mg) { int i; for (i = 0; i < MAP__NR_TYPES; ++i) { - maps__delete(&self->maps[i]); - maps__delete_removed(&self->removed_maps[i]); + maps__delete(&mg->maps[i]); + maps__delete_removed(&mg->removed_maps[i]); } } -void map_groups__flush(struct map_groups *self) +void map_groups__flush(struct map_groups *mg) { int type; for (type = 0; type < MAP__NR_TYPES; type++) { - struct rb_root *root = &self->maps[type]; + struct rb_root *root = &mg->maps[type]; struct rb_node *next = rb_first(root); while (next) { @@ -280,17 +292,17 @@ void map_groups__flush(struct map_groups *self) * instance in some hist_entry instances, so * just move them to a separate list. */ - list_add_tail(&pos->node, &self->removed_maps[pos->type]); + list_add_tail(&pos->node, &mg->removed_maps[pos->type]); } } } -struct symbol *map_groups__find_symbol(struct map_groups *self, +struct symbol *map_groups__find_symbol(struct map_groups *mg, enum map_type type, u64 addr, struct map **mapp, symbol_filter_t filter) { - struct map *map = map_groups__find(self, type, addr); + struct map *map = map_groups__find(mg, type, addr); if (map != NULL) { if (mapp != NULL) @@ -301,7 +313,7 @@ struct symbol *map_groups__find_symbol(struct map_groups *self, return NULL; } -struct symbol *map_groups__find_symbol_by_name(struct map_groups *self, +struct symbol *map_groups__find_symbol_by_name(struct map_groups *mg, enum map_type type, const char *name, struct map **mapp, @@ -309,7 +321,7 @@ struct symbol *map_groups__find_symbol_by_name(struct map_groups *self, { struct rb_node *nd; - for (nd = rb_first(&self->maps[type]); nd; nd = rb_next(nd)) { + for (nd = rb_first(&mg->maps[type]); nd; nd = rb_next(nd)) { struct map *pos = rb_entry(nd, struct map, rb_node); struct symbol *sym = map__find_symbol_by_name(pos, name, filter); @@ -323,13 +335,13 @@ struct symbol *map_groups__find_symbol_by_name(struct map_groups *self, return NULL; } -size_t __map_groups__fprintf_maps(struct map_groups *self, +size_t __map_groups__fprintf_maps(struct map_groups *mg, enum map_type type, int verbose, FILE *fp) { size_t printed = fprintf(fp, "%s:\n", map_type__name[type]); struct rb_node *nd; - for (nd = rb_first(&self->maps[type]); nd; nd = rb_next(nd)) { + for (nd = rb_first(&mg->maps[type]); nd; nd = rb_next(nd)) { struct map *pos = rb_entry(nd, struct map, rb_node); printed += fprintf(fp, "Map:"); printed += map__fprintf(pos, fp); @@ -342,22 +354,22 @@ size_t __map_groups__fprintf_maps(struct map_groups *self, return printed; } -size_t map_groups__fprintf_maps(struct map_groups *self, int verbose, FILE *fp) +size_t map_groups__fprintf_maps(struct map_groups *mg, int verbose, FILE *fp) { size_t printed = 0, i; for (i = 0; i < MAP__NR_TYPES; ++i) - printed += __map_groups__fprintf_maps(self, i, verbose, fp); + printed += __map_groups__fprintf_maps(mg, i, verbose, fp); return printed; } -static size_t __map_groups__fprintf_removed_maps(struct map_groups *self, +static size_t __map_groups__fprintf_removed_maps(struct map_groups *mg, enum map_type type, int verbose, FILE *fp) { struct map *pos; size_t printed = 0; - list_for_each_entry(pos, &self->removed_maps[type], node) { + list_for_each_entry(pos, &mg->removed_maps[type], node) { printed += fprintf(fp, "Map:"); printed += map__fprintf(pos, fp); if (verbose > 1) { @@ -368,26 +380,26 @@ static size_t __map_groups__fprintf_removed_maps(struct map_groups *self, return printed; } -static size_t map_groups__fprintf_removed_maps(struct map_groups *self, +static size_t map_groups__fprintf_removed_maps(struct map_groups *mg, int verbose, FILE *fp) { size_t printed = 0, i; for (i = 0; i < MAP__NR_TYPES; ++i) - printed += __map_groups__fprintf_removed_maps(self, i, verbose, fp); + printed += __map_groups__fprintf_removed_maps(mg, i, verbose, fp); return printed; } -size_t map_groups__fprintf(struct map_groups *self, int verbose, FILE *fp) +size_t map_groups__fprintf(struct map_groups *mg, int verbose, FILE *fp) { - size_t printed = map_groups__fprintf_maps(self, verbose, fp); + size_t printed = map_groups__fprintf_maps(mg, verbose, fp); printed += fprintf(fp, "Removed maps:\n"); - return printed + map_groups__fprintf_removed_maps(self, verbose, fp); + return printed + map_groups__fprintf_removed_maps(mg, verbose, fp); } -int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, +int map_groups__fixup_overlappings(struct map_groups *mg, struct map *map, int verbose, FILE *fp) { - struct rb_root *root = &self->maps[map->type]; + struct rb_root *root = &mg->maps[map->type]; struct rb_node *next = rb_first(root); int err = 0; @@ -418,7 +430,7 @@ int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, } before->end = map->start - 1; - map_groups__insert(self, before); + map_groups__insert(mg, before); if (verbose >= 2) map__fprintf(before, fp); } @@ -432,7 +444,7 @@ int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, } after->start = map->end + 1; - map_groups__insert(self, after); + map_groups__insert(mg, after); if (verbose >= 2) map__fprintf(after, fp); } @@ -441,7 +453,7 @@ move_map: * If we have references, just move them to a separate list. */ if (pos->referenced) - list_add_tail(&pos->node, &self->removed_maps[map->type]); + list_add_tail(&pos->node, &mg->removed_maps[map->type]); else map__delete(pos); @@ -455,7 +467,7 @@ move_map: /* * XXX This should not really _copy_ te maps, but refcount them. */ -int map_groups__clone(struct map_groups *self, +int map_groups__clone(struct map_groups *mg, struct map_groups *parent, enum map_type type) { struct rb_node *nd; @@ -464,7 +476,7 @@ int map_groups__clone(struct map_groups *self, struct map *new = map__clone(map); if (new == NULL) return -ENOMEM; - map_groups__insert(self, new); + map_groups__insert(mg, new); } return 0; } diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index b397c0383728..890d85545d0f 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -123,17 +123,17 @@ void map__fixup_end(struct map *self); void map__reloc_vmlinux(struct map *self); -size_t __map_groups__fprintf_maps(struct map_groups *self, +size_t __map_groups__fprintf_maps(struct map_groups *mg, enum map_type type, int verbose, FILE *fp); void maps__insert(struct rb_root *maps, struct map *map); -void maps__remove(struct rb_root *self, struct map *map); +void maps__remove(struct rb_root *maps, struct map *map); struct map *maps__find(struct rb_root *maps, u64 addr); -void map_groups__init(struct map_groups *self); -void map_groups__exit(struct map_groups *self); -int map_groups__clone(struct map_groups *self, +void map_groups__init(struct map_groups *mg); +void map_groups__exit(struct map_groups *mg); +int map_groups__clone(struct map_groups *mg, struct map_groups *parent, enum map_type type); -size_t map_groups__fprintf(struct map_groups *self, int verbose, FILE *fp); -size_t map_groups__fprintf_maps(struct map_groups *self, int verbose, FILE *fp); +size_t map_groups__fprintf(struct map_groups *mg, int verbose, FILE *fp); +size_t map_groups__fprintf_maps(struct map_groups *mg, int verbose, FILE *fp); typedef void (*machine__process_t)(struct machine *self, void *data); @@ -162,29 +162,29 @@ static inline bool machine__is_host(struct machine *self) return self ? self->pid == HOST_KERNEL_ID : false; } -static inline void map_groups__insert(struct map_groups *self, struct map *map) +static inline void map_groups__insert(struct map_groups *mg, struct map *map) { - maps__insert(&self->maps[map->type], map); - map->groups = self; + maps__insert(&mg->maps[map->type], map); + map->groups = mg; } -static inline void map_groups__remove(struct map_groups *self, struct map *map) +static inline void map_groups__remove(struct map_groups *mg, struct map *map) { - maps__remove(&self->maps[map->type], map); + maps__remove(&mg->maps[map->type], map); } -static inline struct map *map_groups__find(struct map_groups *self, +static inline struct map *map_groups__find(struct map_groups *mg, enum map_type type, u64 addr) { - return maps__find(&self->maps[type], addr); + return maps__find(&mg->maps[type], addr); } -struct symbol *map_groups__find_symbol(struct map_groups *self, +struct symbol *map_groups__find_symbol(struct map_groups *mg, enum map_type type, u64 addr, struct map **mapp, symbol_filter_t filter); -struct symbol *map_groups__find_symbol_by_name(struct map_groups *self, +struct symbol *map_groups__find_symbol_by_name(struct map_groups *mg, enum map_type type, const char *name, struct map **mapp, @@ -208,11 +208,11 @@ struct symbol *machine__find_kernel_function(struct machine *self, u64 addr, } static inline -struct symbol *map_groups__find_function_by_name(struct map_groups *self, +struct symbol *map_groups__find_function_by_name(struct map_groups *mg, const char *name, struct map **mapp, symbol_filter_t filter) { - return map_groups__find_symbol_by_name(self, MAP__FUNCTION, name, mapp, filter); + return map_groups__find_symbol_by_name(mg, MAP__FUNCTION, name, mapp, filter); } static inline @@ -225,13 +225,13 @@ struct symbol *machine__find_kernel_function_by_name(struct machine *self, filter); } -int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, +int map_groups__fixup_overlappings(struct map_groups *mg, struct map *map, int verbose, FILE *fp); -struct map *map_groups__find_by_name(struct map_groups *self, +struct map *map_groups__find_by_name(struct map_groups *mg, enum map_type type, const char *name); struct map *machine__new_module(struct machine *self, u64 start, const char *filename); -void map_groups__flush(struct map_groups *self); +void map_groups__flush(struct map_groups *mg); #endif /* __PERF_MAP_H */ diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 41982c373faf..928918b796b2 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -86,22 +86,24 @@ static const char *sw_event_names[PERF_COUNT_SW_MAX] = { #define MAX_ALIASES 8 -static const char *hw_cache[][MAX_ALIASES] = { +static const char *hw_cache[PERF_COUNT_HW_CACHE_MAX][MAX_ALIASES] = { { "L1-dcache", "l1-d", "l1d", "L1-data", }, { "L1-icache", "l1-i", "l1i", "L1-instruction", }, - { "LLC", "L2" }, + { "LLC", "L2", }, { "dTLB", "d-tlb", "Data-TLB", }, { "iTLB", "i-tlb", "Instruction-TLB", }, { "branch", "branches", "bpu", "btb", "bpc", }, + { "node", }, }; -static const char *hw_cache_op[][MAX_ALIASES] = { +static const char *hw_cache_op[PERF_COUNT_HW_CACHE_OP_MAX][MAX_ALIASES] = { { "load", "loads", "read", }, { "store", "stores", "write", }, { "prefetch", "prefetches", "speculative-read", "speculative-load", }, }; -static const char *hw_cache_result[][MAX_ALIASES] = { +static const char *hw_cache_result[PERF_COUNT_HW_CACHE_RESULT_MAX] + [MAX_ALIASES] = { { "refs", "Reference", "ops", "access", }, { "misses", "miss", }, }; @@ -124,6 +126,7 @@ static unsigned long hw_cache_stat[C(MAX)] = { [C(DTLB)] = (CACHE_READ | CACHE_WRITE | CACHE_PREFETCH), [C(ITLB)] = (CACHE_READ), [C(BPU)] = (CACHE_READ), + [C(NODE)] = (CACHE_READ | CACHE_WRITE | CACHE_PREFETCH), }; #define for_each_subsystem(sys_dir, sys_dirent, sys_next) \ @@ -393,7 +396,7 @@ parse_generic_hw_event(const char **str, struct perf_event_attr *attr) PERF_COUNT_HW_CACHE_OP_MAX); if (cache_op >= 0) { if (!is_cache_op_valid(cache_type, cache_op)) - return 0; + return EVT_FAILED; continue; } } @@ -475,7 +478,7 @@ parse_single_tracepoint_event(char *sys_name, /* sys + ':' + event + ':' + flags*/ #define MAX_EVOPT_LEN (MAX_EVENT_LENGTH * 2 + 2 + 128) static enum event_result -parse_multiple_tracepoint_event(const struct option *opt, char *sys_name, +parse_multiple_tracepoint_event(struct perf_evlist *evlist, char *sys_name, const char *evt_exp, char *flags) { char evt_path[MAXPATHLEN]; @@ -509,7 +512,7 @@ parse_multiple_tracepoint_event(const struct option *opt, char *sys_name, if (len < 0) return EVT_FAILED; - if (parse_events(opt, event_opt, 0)) + if (parse_events(evlist, event_opt, 0)) return EVT_FAILED; } @@ -517,7 +520,7 @@ parse_multiple_tracepoint_event(const struct option *opt, char *sys_name, } static enum event_result -parse_tracepoint_event(const struct option *opt, const char **strp, +parse_tracepoint_event(struct perf_evlist *evlist, const char **strp, struct perf_event_attr *attr) { const char *evt_name; @@ -557,8 +560,8 @@ parse_tracepoint_event(const struct option *opt, const char **strp, return EVT_FAILED; if (strpbrk(evt_name, "*?")) { *strp += strlen(sys_name) + evt_length + 1; /* 1 == the ':' */ - return parse_multiple_tracepoint_event(opt, sys_name, evt_name, - flags); + return parse_multiple_tracepoint_event(evlist, sys_name, + evt_name, flags); } else { return parse_single_tracepoint_event(sys_name, evt_name, evt_length, attr, strp); @@ -694,7 +697,11 @@ parse_raw_event(const char **strp, struct perf_event_attr *attr) return EVT_FAILED; n = hex2u64(str + 1, &config); if (n > 0) { - *strp = str + n + 1; + const char *end = str + n + 1; + if (*end != '\0' && *end != ',' && *end != ':') + return EVT_FAILED; + + *strp = end; attr->type = PERF_TYPE_RAW; attr->config = config; return EVT_HANDLED; @@ -778,12 +785,12 @@ parse_event_modifier(const char **strp, struct perf_event_attr *attr) * Symbolic names are (almost) exactly matched. */ static enum event_result -parse_event_symbols(const struct option *opt, const char **str, +parse_event_symbols(struct perf_evlist *evlist, const char **str, struct perf_event_attr *attr) { enum event_result ret; - ret = parse_tracepoint_event(opt, str, attr); + ret = parse_tracepoint_event(evlist, str, attr); if (ret != EVT_FAILED) goto modifier; @@ -822,9 +829,8 @@ modifier: return ret; } -int parse_events(const struct option *opt, const char *str, int unset __used) +int parse_events(struct perf_evlist *evlist , const char *str, int unset __used) { - struct perf_evlist *evlist = *(struct perf_evlist **)opt->value; struct perf_event_attr attr; enum event_result ret; const char *ostr; @@ -832,7 +838,7 @@ int parse_events(const struct option *opt, const char *str, int unset __used) for (;;) { ostr = str; memset(&attr, 0, sizeof(attr)); - ret = parse_event_symbols(opt, &str, &attr); + ret = parse_event_symbols(evlist, &str, &attr); if (ret == EVT_FAILED) return -1; @@ -863,6 +869,13 @@ int parse_events(const struct option *opt, const char *str, int unset __used) return 0; } +int parse_events_option(const struct option *opt, const char *str, + int unset __used) +{ + struct perf_evlist *evlist = *(struct perf_evlist **)opt->value; + return parse_events(evlist, str, unset); +} + int parse_filter(const struct option *opt, const char *str, int unset __used) { @@ -1088,6 +1101,4 @@ void print_events(const char *event_glob) printf("\n"); print_tracepoint_events(NULL, NULL); - - exit(129); } diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h index 746d3fcbfc2a..2f8e375e038d 100644 --- a/tools/perf/util/parse-events.h +++ b/tools/perf/util/parse-events.h @@ -8,6 +8,7 @@ struct list_head; struct perf_evsel; +struct perf_evlist; struct option; @@ -24,7 +25,10 @@ const char *event_type(int type); const char *event_name(struct perf_evsel *event); extern const char *__event_name(int type, u64 config); -extern int parse_events(const struct option *opt, const char *str, int unset); +extern int parse_events_option(const struct option *opt, const char *str, + int unset); +extern int parse_events(struct perf_evlist *evlist, const char *str, + int unset); extern int parse_filter(const struct option *opt, const char *str, int unset); #define EVENTS_HELP_MAX (128*1024) diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index f0223166e761..eb25900e2211 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -117,6 +117,10 @@ static struct map *kernel_get_module_map(const char *module) struct rb_node *nd; struct map_groups *grp = &machine.kmaps; + /* A file path -- this is an offline module */ + if (module && strchr(module, '/')) + return machine__new_module(&machine, 0, module); + if (!module) module = "kernel"; @@ -170,16 +174,24 @@ const char *kernel_get_module_path(const char *module) } #ifdef DWARF_SUPPORT -static int open_vmlinux(const char *module) +/* Open new debuginfo of given module */ +static struct debuginfo *open_debuginfo(const char *module) { - const char *path = kernel_get_module_path(module); - if (!path) { - pr_err("Failed to find path of %s module.\n", - module ?: "kernel"); - return -ENOENT; + const char *path; + + /* A file path -- this is an offline module */ + if (module && strchr(module, '/')) + path = module; + else { + path = kernel_get_module_path(module); + + if (!path) { + pr_err("Failed to find path of %s module.\n", + module ?: "kernel"); + return NULL; + } } - pr_debug("Try to open %s\n", path); - return open(path, O_RDONLY); + return debuginfo__new(path); } /* @@ -193,13 +205,24 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, struct map *map; u64 addr; int ret = -ENOENT; + struct debuginfo *dinfo; sym = __find_kernel_function_by_name(tp->symbol, &map); if (sym) { addr = map->unmap_ip(map, sym->start + tp->offset); pr_debug("try to find %s+%ld@%" PRIx64 "\n", tp->symbol, tp->offset, addr); - ret = find_perf_probe_point((unsigned long)addr, pp); + + dinfo = debuginfo__new_online_kernel(addr); + if (dinfo) { + ret = debuginfo__find_probe_point(dinfo, + (unsigned long)addr, pp); + debuginfo__delete(dinfo); + } else { + pr_debug("Failed to open debuginfo at 0x%" PRIx64 "\n", + addr); + ret = -ENOENT; + } } if (ret <= 0) { pr_debug("Failed to find corresponding probes from " @@ -214,30 +237,70 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, return 0; } +static int add_module_to_probe_trace_events(struct probe_trace_event *tevs, + int ntevs, const char *module) +{ + int i, ret = 0; + char *tmp; + + if (!module) + return 0; + + tmp = strrchr(module, '/'); + if (tmp) { + /* This is a module path -- get the module name */ + module = strdup(tmp + 1); + if (!module) + return -ENOMEM; + tmp = strchr(module, '.'); + if (tmp) + *tmp = '\0'; + tmp = (char *)module; /* For free() */ + } + + for (i = 0; i < ntevs; i++) { + tevs[i].point.module = strdup(module); + if (!tevs[i].point.module) { + ret = -ENOMEM; + break; + } + } + + if (tmp) + free(tmp); + + return ret; +} + /* Try to find perf_probe_event with debuginfo */ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, - struct probe_trace_event **tevs, - int max_tevs, const char *module) + struct probe_trace_event **tevs, + int max_tevs, const char *module) { bool need_dwarf = perf_probe_event_need_dwarf(pev); - int fd, ntevs; + struct debuginfo *dinfo = open_debuginfo(module); + int ntevs, ret = 0; - fd = open_vmlinux(module); - if (fd < 0) { + if (!dinfo) { if (need_dwarf) { pr_warning("Failed to open debuginfo file.\n"); - return fd; + return -ENOENT; } - pr_debug("Could not open vmlinux. Try to use symbols.\n"); + pr_debug("Could not open debuginfo. Try to use symbols.\n"); return 0; } - /* Searching trace events corresponding to probe event */ - ntevs = find_probe_trace_events(fd, pev, tevs, max_tevs); + /* Searching trace events corresponding to a probe event */ + ntevs = debuginfo__find_trace_events(dinfo, pev, tevs, max_tevs); + + debuginfo__delete(dinfo); if (ntevs > 0) { /* Succeeded to find trace events */ pr_debug("find %d probe_trace_events.\n", ntevs); - return ntevs; + if (module) + ret = add_module_to_probe_trace_events(*tevs, ntevs, + module); + return ret < 0 ? ret : ntevs; } if (ntevs == 0) { /* No error but failed to find probe point. */ @@ -371,8 +434,9 @@ int show_line_range(struct line_range *lr, const char *module) { int l = 1; struct line_node *ln; + struct debuginfo *dinfo; FILE *fp; - int fd, ret; + int ret; char *tmp; /* Search a line range */ @@ -380,13 +444,14 @@ int show_line_range(struct line_range *lr, const char *module) if (ret < 0) return ret; - fd = open_vmlinux(module); - if (fd < 0) { + dinfo = open_debuginfo(module); + if (!dinfo) { pr_warning("Failed to open debuginfo file.\n"); - return fd; + return -ENOENT; } - ret = find_line_range(fd, lr); + ret = debuginfo__find_line_range(dinfo, lr); + debuginfo__delete(dinfo); if (ret == 0) { pr_warning("Specified source line is not found.\n"); return -ENOENT; @@ -448,7 +513,8 @@ end: return ret; } -static int show_available_vars_at(int fd, struct perf_probe_event *pev, +static int show_available_vars_at(struct debuginfo *dinfo, + struct perf_probe_event *pev, int max_vls, struct strfilter *_filter, bool externs) { @@ -463,7 +529,8 @@ static int show_available_vars_at(int fd, struct perf_probe_event *pev, return -EINVAL; pr_debug("Searching variables at %s\n", buf); - ret = find_available_vars_at(fd, pev, &vls, max_vls, externs); + ret = debuginfo__find_available_vars_at(dinfo, pev, &vls, + max_vls, externs); if (ret <= 0) { pr_err("Failed to find variables at %s (%d)\n", buf, ret); goto end; @@ -504,24 +571,26 @@ int show_available_vars(struct perf_probe_event *pevs, int npevs, int max_vls, const char *module, struct strfilter *_filter, bool externs) { - int i, fd, ret = 0; + int i, ret = 0; + struct debuginfo *dinfo; ret = init_vmlinux(); if (ret < 0) return ret; + dinfo = open_debuginfo(module); + if (!dinfo) { + pr_warning("Failed to open debuginfo file.\n"); + return -ENOENT; + } + setup_pager(); - for (i = 0; i < npevs && ret >= 0; i++) { - fd = open_vmlinux(module); - if (fd < 0) { - pr_warning("Failed to open debug information file.\n"); - ret = fd; - break; - } - ret = show_available_vars_at(fd, &pevs[i], max_vls, _filter, + for (i = 0; i < npevs && ret >= 0; i++) + ret = show_available_vars_at(dinfo, &pevs[i], max_vls, _filter, externs); - } + + debuginfo__delete(dinfo); return ret; } @@ -990,7 +1059,7 @@ bool perf_probe_event_need_dwarf(struct perf_probe_event *pev) /* Parse probe_events event into struct probe_point */ static int parse_probe_trace_command(const char *cmd, - struct probe_trace_event *tev) + struct probe_trace_event *tev) { struct probe_trace_point *tp = &tev->point; char pr; @@ -1023,8 +1092,14 @@ static int parse_probe_trace_command(const char *cmd, tp->retprobe = (pr == 'r'); - /* Scan function name and offset */ - ret = sscanf(argv[1], "%a[^+]+%lu", (float *)(void *)&tp->symbol, + /* Scan module name(if there), function name and offset */ + p = strchr(argv[1], ':'); + if (p) { + tp->module = strndup(argv[1], p - argv[1]); + p++; + } else + p = argv[1]; + ret = sscanf(p, "%a[^+]+%lu", (float *)(void *)&tp->symbol, &tp->offset); if (ret == 1) tp->offset = 0; @@ -1269,9 +1344,10 @@ char *synthesize_probe_trace_command(struct probe_trace_event *tev) if (buf == NULL) return NULL; - len = e_snprintf(buf, MAX_CMDLEN, "%c:%s/%s %s+%lu", + len = e_snprintf(buf, MAX_CMDLEN, "%c:%s/%s %s%s%s+%lu", tp->retprobe ? 'r' : 'p', tev->group, tev->event, + tp->module ?: "", tp->module ? ":" : "", tp->symbol, tp->offset); if (len <= 0) goto error; @@ -1378,6 +1454,8 @@ static void clear_probe_trace_event(struct probe_trace_event *tev) free(tev->group); if (tev->point.symbol) free(tev->point.symbol); + if (tev->point.module) + free(tev->point.module); for (i = 0; i < tev->nargs; i++) { if (tev->args[i].name) free(tev->args[i].name); @@ -1729,7 +1807,7 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev, /* Convert perf_probe_event with debuginfo */ ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, module); if (ret != 0) - return ret; + return ret; /* Found in debuginfo or got an error */ /* Allocate trace event buffer */ tev = *tevs = zalloc(sizeof(struct probe_trace_event)); @@ -1742,6 +1820,15 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev, ret = -ENOMEM; goto error; } + + if (module) { + tev->point.module = strdup(module); + if (tev->point.module == NULL) { + ret = -ENOMEM; + goto error; + } + } + tev->point.offset = pev->point.offset; tev->point.retprobe = pev->point.retprobe; tev->nargs = pev->nargs; @@ -1869,8 +1956,10 @@ static int __del_trace_probe_event(int fd, struct str_node *ent) pr_debug("Writing event: %s\n", buf); ret = write(fd, buf, strlen(buf)); - if (ret < 0) + if (ret < 0) { + ret = -errno; goto error; + } printf("Remove event: %s\n", ent->s); return 0; diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h index 3434fc9d79d5..a7dee835f49c 100644 --- a/tools/perf/util/probe-event.h +++ b/tools/perf/util/probe-event.h @@ -10,6 +10,7 @@ extern bool probe_event_dry_run; /* kprobe-tracer tracing point */ struct probe_trace_point { char *symbol; /* Base symbol */ + char *module; /* Module name */ unsigned long offset; /* Offset from symbol */ bool retprobe; /* Return probe flag */ }; diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 3b9d0b800d5c..5d732621a462 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -43,21 +43,6 @@ /* Kprobe tracer basic type is up to u64 */ #define MAX_BASIC_TYPE_BITS 64 -/* - * Compare the tail of two strings. - * Return 0 if whole of either string is same as another's tail part. - */ -static int strtailcmp(const char *s1, const char *s2) -{ - int i1 = strlen(s1); - int i2 = strlen(s2); - while (--i1 >= 0 && --i2 >= 0) { - if (s1[i1] != s2[i2]) - return s1[i1] - s2[i2]; - } - return 0; -} - /* Line number list operations */ /* Add a line to line number list */ @@ -131,29 +116,37 @@ static const Dwfl_Callbacks offline_callbacks = { }; /* Get a Dwarf from offline image */ -static Dwarf *dwfl_init_offline_dwarf(int fd, Dwfl **dwflp, Dwarf_Addr *bias) +static int debuginfo__init_offline_dwarf(struct debuginfo *self, + const char *path) { Dwfl_Module *mod; - Dwarf *dbg = NULL; + int fd; - if (!dwflp) - return NULL; + fd = open(path, O_RDONLY); + if (fd < 0) + return fd; - *dwflp = dwfl_begin(&offline_callbacks); - if (!*dwflp) - return NULL; + self->dwfl = dwfl_begin(&offline_callbacks); + if (!self->dwfl) + goto error; - mod = dwfl_report_offline(*dwflp, "", "", fd); + mod = dwfl_report_offline(self->dwfl, "", "", fd); if (!mod) goto error; - dbg = dwfl_module_getdwarf(mod, bias); - if (!dbg) { + self->dbg = dwfl_module_getdwarf(mod, &self->bias); + if (!self->dbg) + goto error; + + return 0; error: - dwfl_end(*dwflp); - *dwflp = NULL; - } - return dbg; + if (self->dwfl) + dwfl_end(self->dwfl); + else + close(fd); + memset(self, 0, sizeof(*self)); + + return -ENOENT; } #if _ELFUTILS_PREREQ(0, 148) @@ -189,597 +182,81 @@ static const Dwfl_Callbacks kernel_callbacks = { }; /* Get a Dwarf from live kernel image */ -static Dwarf *dwfl_init_live_kernel_dwarf(Dwarf_Addr addr, Dwfl **dwflp, - Dwarf_Addr *bias) +static int debuginfo__init_online_kernel_dwarf(struct debuginfo *self, + Dwarf_Addr addr) { - Dwarf *dbg; - - if (!dwflp) - return NULL; - - *dwflp = dwfl_begin(&kernel_callbacks); - if (!*dwflp) - return NULL; + self->dwfl = dwfl_begin(&kernel_callbacks); + if (!self->dwfl) + return -EINVAL; /* Load the kernel dwarves: Don't care the result here */ - dwfl_linux_kernel_report_kernel(*dwflp); - dwfl_linux_kernel_report_modules(*dwflp); + dwfl_linux_kernel_report_kernel(self->dwfl); + dwfl_linux_kernel_report_modules(self->dwfl); - dbg = dwfl_addrdwarf(*dwflp, addr, bias); + self->dbg = dwfl_addrdwarf(self->dwfl, addr, &self->bias); /* Here, check whether we could get a real dwarf */ - if (!dbg) { + if (!self->dbg) { pr_debug("Failed to find kernel dwarf at %lx\n", (unsigned long)addr); - dwfl_end(*dwflp); - *dwflp = NULL; + dwfl_end(self->dwfl); + memset(self, 0, sizeof(*self)); + return -ENOENT; } - return dbg; + + return 0; } #else /* With older elfutils, this just support kernel module... */ -static Dwarf *dwfl_init_live_kernel_dwarf(Dwarf_Addr addr __used, Dwfl **dwflp, - Dwarf_Addr *bias) +static int debuginfo__init_online_kernel_dwarf(struct debuginfo *self, + Dwarf_Addr addr __used) { - int fd; const char *path = kernel_get_module_path("kernel"); if (!path) { pr_err("Failed to find vmlinux path\n"); - return NULL; + return -ENOENT; } pr_debug2("Use file %s for debuginfo\n", path); - fd = open(path, O_RDONLY); - if (fd < 0) - return NULL; - - return dwfl_init_offline_dwarf(fd, dwflp, bias); + return debuginfo__init_offline_dwarf(self, path); } #endif -/* Dwarf wrappers */ - -/* Find the realpath of the target file. */ -static const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname) +struct debuginfo *debuginfo__new(const char *path) { - Dwarf_Files *files; - size_t nfiles, i; - const char *src = NULL; - int ret; - - if (!fname) + struct debuginfo *self = zalloc(sizeof(struct debuginfo)); + if (!self) return NULL; - ret = dwarf_getsrcfiles(cu_die, &files, &nfiles); - if (ret != 0) - return NULL; - - for (i = 0; i < nfiles; i++) { - src = dwarf_filesrc(files, i, NULL, NULL); - if (strtailcmp(src, fname) == 0) - break; + if (debuginfo__init_offline_dwarf(self, path) < 0) { + free(self); + self = NULL; } - if (i == nfiles) - return NULL; - return src; -} -/* Get DW_AT_comp_dir (should be NULL with older gcc) */ -static const char *cu_get_comp_dir(Dwarf_Die *cu_die) -{ - Dwarf_Attribute attr; - if (dwarf_attr(cu_die, DW_AT_comp_dir, &attr) == NULL) - return NULL; - return dwarf_formstring(&attr); + return self; } -/* Get a line number and file name for given address */ -static int cu_find_lineinfo(Dwarf_Die *cudie, unsigned long addr, - const char **fname, int *lineno) +struct debuginfo *debuginfo__new_online_kernel(unsigned long addr) { - Dwarf_Line *line; - Dwarf_Addr laddr; - - line = dwarf_getsrc_die(cudie, (Dwarf_Addr)addr); - if (line && dwarf_lineaddr(line, &laddr) == 0 && - addr == (unsigned long)laddr && dwarf_lineno(line, lineno) == 0) { - *fname = dwarf_linesrc(line, NULL, NULL); - if (!*fname) - /* line number is useless without filename */ - *lineno = 0; - } - - return *lineno ?: -ENOENT; -} - -/* Compare diename and tname */ -static bool die_compare_name(Dwarf_Die *dw_die, const char *tname) -{ - const char *name; - name = dwarf_diename(dw_die); - return name ? (strcmp(tname, name) == 0) : false; -} - -/* Get callsite line number of inline-function instance */ -static int die_get_call_lineno(Dwarf_Die *in_die) -{ - Dwarf_Attribute attr; - Dwarf_Word ret; - - if (!dwarf_attr(in_die, DW_AT_call_line, &attr)) - return -ENOENT; - - dwarf_formudata(&attr, &ret); - return (int)ret; -} - -/* Get type die */ -static Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) -{ - Dwarf_Attribute attr; - - if (dwarf_attr_integrate(vr_die, DW_AT_type, &attr) && - dwarf_formref_die(&attr, die_mem)) - return die_mem; - else + struct debuginfo *self = zalloc(sizeof(struct debuginfo)); + if (!self) return NULL; -} - -/* Get a type die, but skip qualifiers */ -static Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) -{ - int tag; - - do { - vr_die = die_get_type(vr_die, die_mem); - if (!vr_die) - break; - tag = dwarf_tag(vr_die); - } while (tag == DW_TAG_const_type || - tag == DW_TAG_restrict_type || - tag == DW_TAG_volatile_type || - tag == DW_TAG_shared_type); - - return vr_die; -} - -/* Get a type die, but skip qualifiers and typedef */ -static Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) -{ - do { - vr_die = __die_get_real_type(vr_die, die_mem); - } while (vr_die && dwarf_tag(vr_die) == DW_TAG_typedef); - - return vr_die; -} - -static int die_get_attr_udata(Dwarf_Die *tp_die, unsigned int attr_name, - Dwarf_Word *result) -{ - Dwarf_Attribute attr; - - if (dwarf_attr(tp_die, attr_name, &attr) == NULL || - dwarf_formudata(&attr, result) != 0) - return -ENOENT; - - return 0; -} -static bool die_is_signed_type(Dwarf_Die *tp_die) -{ - Dwarf_Word ret; - - if (die_get_attr_udata(tp_die, DW_AT_encoding, &ret)) - return false; - - return (ret == DW_ATE_signed_char || ret == DW_ATE_signed || - ret == DW_ATE_signed_fixed); -} - -static int die_get_byte_size(Dwarf_Die *tp_die) -{ - Dwarf_Word ret; - - if (die_get_attr_udata(tp_die, DW_AT_byte_size, &ret)) - return 0; - - return (int)ret; -} - -static int die_get_bit_size(Dwarf_Die *tp_die) -{ - Dwarf_Word ret; - - if (die_get_attr_udata(tp_die, DW_AT_bit_size, &ret)) - return 0; - - return (int)ret; -} - -static int die_get_bit_offset(Dwarf_Die *tp_die) -{ - Dwarf_Word ret; - - if (die_get_attr_udata(tp_die, DW_AT_bit_offset, &ret)) - return 0; - - return (int)ret; -} - -/* Get data_member_location offset */ -static int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs) -{ - Dwarf_Attribute attr; - Dwarf_Op *expr; - size_t nexpr; - int ret; - - if (dwarf_attr(mb_die, DW_AT_data_member_location, &attr) == NULL) - return -ENOENT; - - if (dwarf_formudata(&attr, offs) != 0) { - /* DW_AT_data_member_location should be DW_OP_plus_uconst */ - ret = dwarf_getlocation(&attr, &expr, &nexpr); - if (ret < 0 || nexpr == 0) - return -ENOENT; - - if (expr[0].atom != DW_OP_plus_uconst || nexpr != 1) { - pr_debug("Unable to get offset:Unexpected OP %x (%zd)\n", - expr[0].atom, nexpr); - return -ENOTSUP; - } - *offs = (Dwarf_Word)expr[0].number; - } - return 0; -} - -/* Return values for die_find callbacks */ -enum { - DIE_FIND_CB_FOUND = 0, /* End of Search */ - DIE_FIND_CB_CHILD = 1, /* Search only children */ - DIE_FIND_CB_SIBLING = 2, /* Search only siblings */ - DIE_FIND_CB_CONTINUE = 3, /* Search children and siblings */ -}; - -/* Search a child die */ -static Dwarf_Die *die_find_child(Dwarf_Die *rt_die, - int (*callback)(Dwarf_Die *, void *), - void *data, Dwarf_Die *die_mem) -{ - Dwarf_Die child_die; - int ret; - - ret = dwarf_child(rt_die, die_mem); - if (ret != 0) - return NULL; - - do { - ret = callback(die_mem, data); - if (ret == DIE_FIND_CB_FOUND) - return die_mem; - - if ((ret & DIE_FIND_CB_CHILD) && - die_find_child(die_mem, callback, data, &child_die)) { - memcpy(die_mem, &child_die, sizeof(Dwarf_Die)); - return die_mem; - } - } while ((ret & DIE_FIND_CB_SIBLING) && - dwarf_siblingof(die_mem, die_mem) == 0); - - return NULL; -} - -struct __addr_die_search_param { - Dwarf_Addr addr; - Dwarf_Die *die_mem; -}; - -static int __die_search_func_cb(Dwarf_Die *fn_die, void *data) -{ - struct __addr_die_search_param *ad = data; - - if (dwarf_tag(fn_die) == DW_TAG_subprogram && - dwarf_haspc(fn_die, ad->addr)) { - memcpy(ad->die_mem, fn_die, sizeof(Dwarf_Die)); - return DWARF_CB_ABORT; + if (debuginfo__init_online_kernel_dwarf(self, (Dwarf_Addr)addr) < 0) { + free(self); + self = NULL; } - return DWARF_CB_OK; -} -/* Search a real subprogram including this line, */ -static Dwarf_Die *die_find_real_subprogram(Dwarf_Die *cu_die, Dwarf_Addr addr, - Dwarf_Die *die_mem) -{ - struct __addr_die_search_param ad; - ad.addr = addr; - ad.die_mem = die_mem; - /* dwarf_getscopes can't find subprogram. */ - if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0)) - return NULL; - else - return die_mem; + return self; } -/* die_find callback for inline function search */ -static int __die_find_inline_cb(Dwarf_Die *die_mem, void *data) +void debuginfo__delete(struct debuginfo *self) { - Dwarf_Addr *addr = data; - - if (dwarf_tag(die_mem) == DW_TAG_inlined_subroutine && - dwarf_haspc(die_mem, *addr)) - return DIE_FIND_CB_FOUND; - - return DIE_FIND_CB_CONTINUE; -} - -/* Similar to dwarf_getfuncs, but returns inlined_subroutine if exists. */ -static Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr, - Dwarf_Die *die_mem) -{ - Dwarf_Die tmp_die; - - sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr, &tmp_die); - if (!sp_die) - return NULL; - - /* Inlined function could be recursive. Trace it until fail */ - while (sp_die) { - memcpy(die_mem, sp_die, sizeof(Dwarf_Die)); - sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr, - &tmp_die); - } - - return die_mem; -} - -/* Walker on lines (Note: line number will not be sorted) */ -typedef int (* line_walk_handler_t) (const char *fname, int lineno, - Dwarf_Addr addr, void *data); - -struct __line_walk_param { - const char *fname; - line_walk_handler_t handler; - void *data; - int retval; -}; - -static int __die_walk_funclines_cb(Dwarf_Die *in_die, void *data) -{ - struct __line_walk_param *lw = data; - Dwarf_Addr addr; - int lineno; - - if (dwarf_tag(in_die) == DW_TAG_inlined_subroutine) { - lineno = die_get_call_lineno(in_die); - if (lineno > 0 && dwarf_entrypc(in_die, &addr) == 0) { - lw->retval = lw->handler(lw->fname, lineno, addr, - lw->data); - if (lw->retval != 0) - return DIE_FIND_CB_FOUND; - } + if (self) { + if (self->dwfl) + dwfl_end(self->dwfl); + free(self); } - return DIE_FIND_CB_SIBLING; -} - -/* Walk on lines of blocks included in given DIE */ -static int __die_walk_funclines(Dwarf_Die *sp_die, - line_walk_handler_t handler, void *data) -{ - struct __line_walk_param lw = { - .handler = handler, - .data = data, - .retval = 0, - }; - Dwarf_Die die_mem; - Dwarf_Addr addr; - int lineno; - - /* Handle function declaration line */ - lw.fname = dwarf_decl_file(sp_die); - if (lw.fname && dwarf_decl_line(sp_die, &lineno) == 0 && - dwarf_entrypc(sp_die, &addr) == 0) { - lw.retval = handler(lw.fname, lineno, addr, data); - if (lw.retval != 0) - goto done; - } - die_find_child(sp_die, __die_walk_funclines_cb, &lw, &die_mem); -done: - return lw.retval; -} - -static int __die_walk_culines_cb(Dwarf_Die *sp_die, void *data) -{ - struct __line_walk_param *lw = data; - - lw->retval = __die_walk_funclines(sp_die, lw->handler, lw->data); - if (lw->retval != 0) - return DWARF_CB_ABORT; - - return DWARF_CB_OK; -} - -/* - * Walk on lines inside given PDIE. If the PDIE is subprogram, walk only on - * the lines inside the subprogram, otherwise PDIE must be a CU DIE. - */ -static int die_walk_lines(Dwarf_Die *pdie, line_walk_handler_t handler, - void *data) -{ - Dwarf_Lines *lines; - Dwarf_Line *line; - Dwarf_Addr addr; - const char *fname; - int lineno, ret = 0; - Dwarf_Die die_mem, *cu_die; - size_t nlines, i; - - /* Get the CU die */ - if (dwarf_tag(pdie) == DW_TAG_subprogram) - cu_die = dwarf_diecu(pdie, &die_mem, NULL, NULL); - else - cu_die = pdie; - if (!cu_die) { - pr_debug2("Failed to get CU from subprogram\n"); - return -EINVAL; - } - - /* Get lines list in the CU */ - if (dwarf_getsrclines(cu_die, &lines, &nlines) != 0) { - pr_debug2("Failed to get source lines on this CU.\n"); - return -ENOENT; - } - pr_debug2("Get %zd lines from this CU\n", nlines); - - /* Walk on the lines on lines list */ - for (i = 0; i < nlines; i++) { - line = dwarf_onesrcline(lines, i); - if (line == NULL || - dwarf_lineno(line, &lineno) != 0 || - dwarf_lineaddr(line, &addr) != 0) { - pr_debug2("Failed to get line info. " - "Possible error in debuginfo.\n"); - continue; - } - /* Filter lines based on address */ - if (pdie != cu_die) - /* - * Address filtering - * The line is included in given function, and - * no inline block includes it. - */ - if (!dwarf_haspc(pdie, addr) || - die_find_inlinefunc(pdie, addr, &die_mem)) - continue; - /* Get source line */ - fname = dwarf_linesrc(line, NULL, NULL); - - ret = handler(fname, lineno, addr, data); - if (ret != 0) - return ret; - } - - /* - * Dwarf lines doesn't include function declarations and inlined - * subroutines. We have to check functions list or given function. - */ - if (pdie != cu_die) - ret = __die_walk_funclines(pdie, handler, data); - else { - struct __line_walk_param param = { - .handler = handler, - .data = data, - .retval = 0, - }; - dwarf_getfuncs(cu_die, __die_walk_culines_cb, ¶m, 0); - ret = param.retval; - } - - return ret; -} - -struct __find_variable_param { - const char *name; - Dwarf_Addr addr; -}; - -static int __die_find_variable_cb(Dwarf_Die *die_mem, void *data) -{ - struct __find_variable_param *fvp = data; - int tag; - - tag = dwarf_tag(die_mem); - if ((tag == DW_TAG_formal_parameter || - tag == DW_TAG_variable) && - die_compare_name(die_mem, fvp->name)) - return DIE_FIND_CB_FOUND; - - if (dwarf_haspc(die_mem, fvp->addr)) - return DIE_FIND_CB_CONTINUE; - else - return DIE_FIND_CB_SIBLING; -} - -/* Find a variable called 'name' at given address */ -static Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name, - Dwarf_Addr addr, Dwarf_Die *die_mem) -{ - struct __find_variable_param fvp = { .name = name, .addr = addr}; - - return die_find_child(sp_die, __die_find_variable_cb, (void *)&fvp, - die_mem); -} - -static int __die_find_member_cb(Dwarf_Die *die_mem, void *data) -{ - const char *name = data; - - if ((dwarf_tag(die_mem) == DW_TAG_member) && - die_compare_name(die_mem, name)) - return DIE_FIND_CB_FOUND; - - return DIE_FIND_CB_SIBLING; -} - -/* Find a member called 'name' */ -static Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name, - Dwarf_Die *die_mem) -{ - return die_find_child(st_die, __die_find_member_cb, (void *)name, - die_mem); -} - -/* Get the name of given variable DIE */ -static int die_get_typename(Dwarf_Die *vr_die, char *buf, int len) -{ - Dwarf_Die type; - int tag, ret, ret2; - const char *tmp = ""; - - if (__die_get_real_type(vr_die, &type) == NULL) - return -ENOENT; - - tag = dwarf_tag(&type); - if (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type) - tmp = "*"; - else if (tag == DW_TAG_subroutine_type) { - /* Function pointer */ - ret = snprintf(buf, len, "(function_type)"); - return (ret >= len) ? -E2BIG : ret; - } else { - if (!dwarf_diename(&type)) - return -ENOENT; - if (tag == DW_TAG_union_type) - tmp = "union "; - else if (tag == DW_TAG_structure_type) - tmp = "struct "; - /* Write a base name */ - ret = snprintf(buf, len, "%s%s", tmp, dwarf_diename(&type)); - return (ret >= len) ? -E2BIG : ret; - } - ret = die_get_typename(&type, buf, len); - if (ret > 0) { - ret2 = snprintf(buf + ret, len - ret, "%s", tmp); - ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret; - } - return ret; -} - -/* Get the name and type of given variable DIE, stored as "type\tname" */ -static int die_get_varname(Dwarf_Die *vr_die, char *buf, int len) -{ - int ret, ret2; - - ret = die_get_typename(vr_die, buf, len); - if (ret < 0) { - pr_debug("Failed to get type, make it unknown.\n"); - ret = snprintf(buf, len, "(unknown_type)"); - } - if (ret > 0) { - ret2 = snprintf(buf + ret, len - ret, "\t%s", - dwarf_diename(vr_die)); - ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret; - } - return ret; } /* @@ -897,6 +374,7 @@ static int convert_variable_type(Dwarf_Die *vr_die, struct probe_trace_arg_ref **ref_ptr = &tvar->ref; Dwarf_Die type; char buf[16]; + int bsize, boffs, total; int ret; /* TODO: check all types */ @@ -906,11 +384,15 @@ static int convert_variable_type(Dwarf_Die *vr_die, return (tvar->type == NULL) ? -ENOMEM : 0; } - if (die_get_bit_size(vr_die) != 0) { + bsize = dwarf_bitsize(vr_die); + if (bsize > 0) { /* This is a bitfield */ - ret = snprintf(buf, 16, "b%d@%d/%zd", die_get_bit_size(vr_die), - die_get_bit_offset(vr_die), - BYTES_TO_BITS(die_get_byte_size(vr_die))); + boffs = dwarf_bitoffset(vr_die); + total = dwarf_bytesize(vr_die); + if (boffs < 0 || total < 0) + return -ENOENT; + ret = snprintf(buf, 16, "b%d@%d/%zd", bsize, boffs, + BYTES_TO_BITS(total)); goto formatted; } @@ -958,10 +440,11 @@ static int convert_variable_type(Dwarf_Die *vr_die, return (tvar->type == NULL) ? -ENOMEM : 0; } - ret = BYTES_TO_BITS(die_get_byte_size(&type)); - if (!ret) + ret = dwarf_bytesize(&type); + if (ret <= 0) /* No size ... try to use default type */ return 0; + ret = BYTES_TO_BITS(ret); /* Check the bitwidth */ if (ret > MAX_BASIC_TYPE_BITS) { @@ -1025,7 +508,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, else *ref_ptr = ref; } - ref->offset += die_get_byte_size(&type) * field->index; + ref->offset += dwarf_bytesize(&type) * field->index; if (!field->next) /* Save vr_die for converting types */ memcpy(die_mem, vr_die, sizeof(*die_mem)); @@ -1129,12 +612,12 @@ static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf) return ret; } -/* Find a variable in a subprogram die */ -static int find_variable(Dwarf_Die *sp_die, struct probe_finder *pf) +/* Find a variable in a scope DIE */ +static int find_variable(Dwarf_Die *sc_die, struct probe_finder *pf) { - Dwarf_Die vr_die, *scopes; + Dwarf_Die vr_die; char buf[32], *ptr; - int ret, nscopes; + int ret = 0; if (!is_c_varname(pf->pvar->var)) { /* Copy raw parameters */ @@ -1169,30 +652,16 @@ static int find_variable(Dwarf_Die *sp_die, struct probe_finder *pf) if (pf->tvar->name == NULL) return -ENOMEM; - pr_debug("Searching '%s' variable in context.\n", - pf->pvar->var); + pr_debug("Searching '%s' variable in context.\n", pf->pvar->var); /* Search child die for local variables and parameters. */ - if (die_find_variable_at(sp_die, pf->pvar->var, pf->addr, &vr_die)) - ret = convert_variable(&vr_die, pf); - else { - /* Search upper class */ - nscopes = dwarf_getscopes_die(sp_die, &scopes); - while (nscopes-- > 1) { - pr_debug("Searching variables in %s\n", - dwarf_diename(&scopes[nscopes])); - /* We should check this scope, so give dummy address */ - if (die_find_variable_at(&scopes[nscopes], - pf->pvar->var, 0, - &vr_die)) { - ret = convert_variable(&vr_die, pf); - goto found; - } - } - if (scopes) - free(scopes); - ret = -ENOENT; + if (!die_find_variable_at(sc_die, pf->pvar->var, pf->addr, &vr_die)) { + /* Search again in global variables */ + if (!die_find_variable_at(&pf->cu_die, pf->pvar->var, 0, &vr_die)) + ret = -ENOENT; } -found: + if (ret >= 0) + ret = convert_variable(&vr_die, pf); + if (ret < 0) pr_warning("Failed to find '%s' in this function.\n", pf->pvar->var); @@ -1235,27 +704,30 @@ static int convert_to_trace_point(Dwarf_Die *sp_die, Dwarf_Addr paddr, return 0; } -/* Call probe_finder callback with real subprogram DIE */ -static int call_probe_finder(Dwarf_Die *sp_die, struct probe_finder *pf) +/* Call probe_finder callback with scope DIE */ +static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf) { - Dwarf_Die die_mem; Dwarf_Attribute fb_attr; size_t nops; int ret; - /* If no real subprogram, find a real one */ - if (!sp_die || dwarf_tag(sp_die) != DW_TAG_subprogram) { - sp_die = die_find_real_subprogram(&pf->cu_die, - pf->addr, &die_mem); - if (!sp_die) { + if (!sc_die) { + pr_err("Caller must pass a scope DIE. Program error.\n"); + return -EINVAL; + } + + /* If not a real subprogram, find a real one */ + if (dwarf_tag(sc_die) != DW_TAG_subprogram) { + if (!die_find_realfunc(&pf->cu_die, pf->addr, &pf->sp_die)) { pr_warning("Failed to find probe point in any " "functions.\n"); return -ENOENT; } - } + } else + memcpy(&pf->sp_die, sc_die, sizeof(Dwarf_Die)); - /* Get the frame base attribute/ops */ - dwarf_attr(sp_die, DW_AT_frame_base, &fb_attr); + /* Get the frame base attribute/ops from subprogram */ + dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr); ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1); if (ret <= 0 || nops == 0) { pf->fb_ops = NULL; @@ -1273,7 +745,7 @@ static int call_probe_finder(Dwarf_Die *sp_die, struct probe_finder *pf) } /* Call finder's callback handler */ - ret = pf->callback(sp_die, pf); + ret = pf->callback(sc_die, pf); /* *pf->fb_ops will be cached in libdw. Don't free it. */ pf->fb_ops = NULL; @@ -1281,17 +753,82 @@ static int call_probe_finder(Dwarf_Die *sp_die, struct probe_finder *pf) return ret; } +struct find_scope_param { + const char *function; + const char *file; + int line; + int diff; + Dwarf_Die *die_mem; + bool found; +}; + +static int find_best_scope_cb(Dwarf_Die *fn_die, void *data) +{ + struct find_scope_param *fsp = data; + const char *file; + int lno; + + /* Skip if declared file name does not match */ + if (fsp->file) { + file = dwarf_decl_file(fn_die); + if (!file || strcmp(fsp->file, file) != 0) + return 0; + } + /* If the function name is given, that's what user expects */ + if (fsp->function) { + if (die_compare_name(fn_die, fsp->function)) { + memcpy(fsp->die_mem, fn_die, sizeof(Dwarf_Die)); + fsp->found = true; + return 1; + } + } else { + /* With the line number, find the nearest declared DIE */ + dwarf_decl_line(fn_die, &lno); + if (lno < fsp->line && fsp->diff > fsp->line - lno) { + /* Keep a candidate and continue */ + fsp->diff = fsp->line - lno; + memcpy(fsp->die_mem, fn_die, sizeof(Dwarf_Die)); + fsp->found = true; + } + } + return 0; +} + +/* Find an appropriate scope fits to given conditions */ +static Dwarf_Die *find_best_scope(struct probe_finder *pf, Dwarf_Die *die_mem) +{ + struct find_scope_param fsp = { + .function = pf->pev->point.function, + .file = pf->fname, + .line = pf->lno, + .diff = INT_MAX, + .die_mem = die_mem, + .found = false, + }; + + cu_walk_functions_at(&pf->cu_die, pf->addr, find_best_scope_cb, &fsp); + + return fsp.found ? die_mem : NULL; +} + static int probe_point_line_walker(const char *fname, int lineno, Dwarf_Addr addr, void *data) { struct probe_finder *pf = data; + Dwarf_Die *sc_die, die_mem; int ret; if (lineno != pf->lno || strtailcmp(fname, pf->fname) != 0) return 0; pf->addr = addr; - ret = call_probe_finder(NULL, pf); + sc_die = find_best_scope(pf, &die_mem); + if (!sc_die) { + pr_warning("Failed to find scope of probe point.\n"); + return -ENOENT; + } + + ret = call_probe_finder(sc_die, pf); /* Continue if no error, because the line will be in inline function */ return ret < 0 ? ret : 0; @@ -1345,6 +882,7 @@ static int probe_point_lazy_walker(const char *fname, int lineno, Dwarf_Addr addr, void *data) { struct probe_finder *pf = data; + Dwarf_Die *sc_die, die_mem; int ret; if (!line_list__has_line(&pf->lcache, lineno) || @@ -1354,7 +892,14 @@ static int probe_point_lazy_walker(const char *fname, int lineno, pr_debug("Probe line found: line:%d addr:0x%llx\n", lineno, (unsigned long long)addr); pf->addr = addr; - ret = call_probe_finder(NULL, pf); + pf->lno = lineno; + sc_die = find_best_scope(pf, &die_mem); + if (!sc_die) { + pr_warning("Failed to find scope of probe point.\n"); + return -ENOENT; + } + + ret = call_probe_finder(sc_die, pf); /* * Continue if no error, because the lazy pattern will match @@ -1379,42 +924,39 @@ static int find_probe_point_lazy(Dwarf_Die *sp_die, struct probe_finder *pf) return die_walk_lines(sp_die, probe_point_lazy_walker, pf); } -/* Callback parameter with return value */ -struct dwarf_callback_param { - void *data; - int retval; -}; - static int probe_point_inline_cb(Dwarf_Die *in_die, void *data) { - struct dwarf_callback_param *param = data; - struct probe_finder *pf = param->data; + struct probe_finder *pf = data; struct perf_probe_point *pp = &pf->pev->point; Dwarf_Addr addr; + int ret; if (pp->lazy_line) - param->retval = find_probe_point_lazy(in_die, pf); + ret = find_probe_point_lazy(in_die, pf); else { /* Get probe address */ if (dwarf_entrypc(in_die, &addr) != 0) { pr_warning("Failed to get entry address of %s.\n", dwarf_diename(in_die)); - param->retval = -ENOENT; - return DWARF_CB_ABORT; + return -ENOENT; } pf->addr = addr; pf->addr += pp->offset; pr_debug("found inline addr: 0x%jx\n", (uintmax_t)pf->addr); - param->retval = call_probe_finder(in_die, pf); - if (param->retval < 0) - return DWARF_CB_ABORT; + ret = call_probe_finder(in_die, pf); } - return DWARF_CB_OK; + return ret; } +/* Callback parameter with return value for libdw */ +struct dwarf_callback_param { + void *data; + int retval; +}; + /* Search function from function name */ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) { @@ -1451,14 +993,10 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) /* TODO: Check the address in this function */ param->retval = call_probe_finder(sp_die, pf); } - } else { - struct dwarf_callback_param _param = {.data = (void *)pf, - .retval = 0}; + } else /* Inlined function: search instances */ - dwarf_func_inline_instances(sp_die, probe_point_inline_cb, - &_param); - param->retval = _param.retval; - } + param->retval = die_walk_instances(sp_die, + probe_point_inline_cb, (void *)pf); return DWARF_CB_ABORT; /* Exit; no same symbol in this CU. */ } @@ -1504,28 +1042,18 @@ static int pubname_search_cb(Dwarf *dbg, Dwarf_Global *gl, void *data) } /* Find probe points from debuginfo */ -static int find_probes(int fd, struct probe_finder *pf) +static int debuginfo__find_probes(struct debuginfo *self, + struct probe_finder *pf) { struct perf_probe_point *pp = &pf->pev->point; Dwarf_Off off, noff; size_t cuhl; Dwarf_Die *diep; - Dwarf *dbg = NULL; - Dwfl *dwfl; - Dwarf_Addr bias; /* Currently ignored */ int ret = 0; - dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias); - if (!dbg) { - pr_warning("No debug information found in the vmlinux - " - "please rebuild with CONFIG_DEBUG_INFO=y.\n"); - close(fd); /* Without dwfl_end(), fd isn't closed. */ - return -EBADF; - } - #if _ELFUTILS_PREREQ(0, 142) /* Get the call frame information from this dwarf */ - pf->cfi = dwarf_getcfi(dbg); + pf->cfi = dwarf_getcfi(self->dbg); #endif off = 0; @@ -1544,7 +1072,8 @@ static int find_probes(int fd, struct probe_finder *pf) .data = pf, }; - dwarf_getpubnames(dbg, pubname_search_cb, &pubname_param, 0); + dwarf_getpubnames(self->dbg, pubname_search_cb, + &pubname_param, 0); if (pubname_param.found) { ret = probe_point_search_cb(&pf->sp_die, &probe_param); if (ret) @@ -1553,9 +1082,9 @@ static int find_probes(int fd, struct probe_finder *pf) } /* Loop on CUs (Compilation Unit) */ - while (!dwarf_nextcu(dbg, off, &noff, &cuhl, NULL, NULL, NULL)) { + while (!dwarf_nextcu(self->dbg, off, &noff, &cuhl, NULL, NULL, NULL)) { /* Get the DIE(Debugging Information Entry) of this CU */ - diep = dwarf_offdie(dbg, off + cuhl, &pf->cu_die); + diep = dwarf_offdie(self->dbg, off + cuhl, &pf->cu_die); if (!diep) continue; @@ -1582,14 +1111,12 @@ static int find_probes(int fd, struct probe_finder *pf) found: line_list__free(&pf->lcache); - if (dwfl) - dwfl_end(dwfl); return ret; } /* Add a found probe point into trace event list */ -static int add_probe_trace_event(Dwarf_Die *sp_die, struct probe_finder *pf) +static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf) { struct trace_event_finder *tf = container_of(pf, struct trace_event_finder, pf); @@ -1604,8 +1131,9 @@ static int add_probe_trace_event(Dwarf_Die *sp_die, struct probe_finder *pf) } tev = &tf->tevs[tf->ntevs++]; - ret = convert_to_trace_point(sp_die, pf->addr, pf->pev->point.retprobe, - &tev->point); + /* Trace point should be converted from subprogram DIE */ + ret = convert_to_trace_point(&pf->sp_die, pf->addr, + pf->pev->point.retprobe, &tev->point); if (ret < 0) return ret; @@ -1620,7 +1148,8 @@ static int add_probe_trace_event(Dwarf_Die *sp_die, struct probe_finder *pf) for (i = 0; i < pf->pev->nargs; i++) { pf->pvar = &pf->pev->args[i]; pf->tvar = &tev->args[i]; - ret = find_variable(sp_die, pf); + /* Variable should be found from scope DIE */ + ret = find_variable(sc_die, pf); if (ret != 0) return ret; } @@ -1629,8 +1158,9 @@ static int add_probe_trace_event(Dwarf_Die *sp_die, struct probe_finder *pf) } /* Find probe_trace_events specified by perf_probe_event from debuginfo */ -int find_probe_trace_events(int fd, struct perf_probe_event *pev, - struct probe_trace_event **tevs, int max_tevs) +int debuginfo__find_trace_events(struct debuginfo *self, + struct perf_probe_event *pev, + struct probe_trace_event **tevs, int max_tevs) { struct trace_event_finder tf = { .pf = {.pev = pev, .callback = add_probe_trace_event}, @@ -1645,7 +1175,7 @@ int find_probe_trace_events(int fd, struct perf_probe_event *pev, tf.tevs = *tevs; tf.ntevs = 0; - ret = find_probes(fd, &tf.pf); + ret = debuginfo__find_probes(self, &tf.pf); if (ret < 0) { free(*tevs); *tevs = NULL; @@ -1687,13 +1217,13 @@ static int collect_variables_cb(Dwarf_Die *die_mem, void *data) } /* Add a found vars into available variables list */ -static int add_available_vars(Dwarf_Die *sp_die, struct probe_finder *pf) +static int add_available_vars(Dwarf_Die *sc_die, struct probe_finder *pf) { struct available_var_finder *af = container_of(pf, struct available_var_finder, pf); struct variable_list *vl; - Dwarf_Die die_mem, *scopes = NULL; - int ret, nscopes; + Dwarf_Die die_mem; + int ret; /* Check number of tevs */ if (af->nvls == af->max_vls) { @@ -1702,8 +1232,9 @@ static int add_available_vars(Dwarf_Die *sp_die, struct probe_finder *pf) } vl = &af->vls[af->nvls++]; - ret = convert_to_trace_point(sp_die, pf->addr, pf->pev->point.retprobe, - &vl->point); + /* Trace point should be converted from subprogram DIE */ + ret = convert_to_trace_point(&pf->sp_die, pf->addr, + pf->pev->point.retprobe, &vl->point); if (ret < 0) return ret; @@ -1715,19 +1246,14 @@ static int add_available_vars(Dwarf_Die *sp_die, struct probe_finder *pf) if (vl->vars == NULL) return -ENOMEM; af->child = true; - die_find_child(sp_die, collect_variables_cb, (void *)af, &die_mem); + die_find_child(sc_die, collect_variables_cb, (void *)af, &die_mem); /* Find external variables */ if (!af->externs) goto out; /* Don't need to search child DIE for externs. */ af->child = false; - nscopes = dwarf_getscopes_die(sp_die, &scopes); - while (nscopes-- > 1) - die_find_child(&scopes[nscopes], collect_variables_cb, - (void *)af, &die_mem); - if (scopes) - free(scopes); + die_find_child(&pf->cu_die, collect_variables_cb, (void *)af, &die_mem); out: if (strlist__empty(vl->vars)) { @@ -1739,9 +1265,10 @@ out: } /* Find available variables at given probe point */ -int find_available_vars_at(int fd, struct perf_probe_event *pev, - struct variable_list **vls, int max_vls, - bool externs) +int debuginfo__find_available_vars_at(struct debuginfo *self, + struct perf_probe_event *pev, + struct variable_list **vls, + int max_vls, bool externs) { struct available_var_finder af = { .pf = {.pev = pev, .callback = add_available_vars}, @@ -1756,7 +1283,7 @@ int find_available_vars_at(int fd, struct perf_probe_event *pev, af.vls = *vls; af.nvls = 0; - ret = find_probes(fd, &af.pf); + ret = debuginfo__find_probes(self, &af.pf); if (ret < 0) { /* Free vlist for error */ while (af.nvls--) { @@ -1774,28 +1301,19 @@ int find_available_vars_at(int fd, struct perf_probe_event *pev, } /* Reverse search */ -int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt) +int debuginfo__find_probe_point(struct debuginfo *self, unsigned long addr, + struct perf_probe_point *ppt) { Dwarf_Die cudie, spdie, indie; - Dwarf *dbg = NULL; - Dwfl *dwfl = NULL; - Dwarf_Addr _addr, baseaddr, bias = 0; + Dwarf_Addr _addr, baseaddr; const char *fname = NULL, *func = NULL, *tmp; int baseline = 0, lineno = 0, ret = 0; - /* Open the live linux kernel */ - dbg = dwfl_init_live_kernel_dwarf(addr, &dwfl, &bias); - if (!dbg) { - pr_warning("No debug information found in the vmlinux - " - "please rebuild with CONFIG_DEBUG_INFO=y.\n"); - ret = -EINVAL; - goto end; - } - /* Adjust address with bias */ - addr += bias; + addr += self->bias; + /* Find cu die */ - if (!dwarf_addrdie(dbg, (Dwarf_Addr)addr - bias, &cudie)) { + if (!dwarf_addrdie(self->dbg, (Dwarf_Addr)addr - self->bias, &cudie)) { pr_warning("Failed to find debug information for address %lx\n", addr); ret = -EINVAL; @@ -1807,7 +1325,7 @@ int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt) /* Don't care whether it failed or not */ /* Find a corresponding function (name, baseline and baseaddr) */ - if (die_find_real_subprogram(&cudie, (Dwarf_Addr)addr, &spdie)) { + if (die_find_realfunc(&cudie, (Dwarf_Addr)addr, &spdie)) { /* Get function entry information */ tmp = dwarf_diename(&spdie); if (!tmp || @@ -1871,8 +1389,6 @@ post: } } end: - if (dwfl) - dwfl_end(dwfl); if (ret == 0 && (fname || func)) ret = 1; /* Found a point */ return ret; @@ -1929,10 +1445,14 @@ static int find_line_range_by_line(Dwarf_Die *sp_die, struct line_finder *lf) static int line_range_inline_cb(Dwarf_Die *in_die, void *data) { - struct dwarf_callback_param *param = data; + find_line_range_by_line(in_die, data); - param->retval = find_line_range_by_line(in_die, param->data); - return DWARF_CB_ABORT; /* No need to find other instances */ + /* + * We have to check all instances of inlined function, because + * some execution paths can be optimized out depends on the + * function argument of instances + */ + return 0; } /* Search function from function name */ @@ -1960,15 +1480,10 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data) pr_debug("New line range: %d to %d\n", lf->lno_s, lf->lno_e); lr->start = lf->lno_s; lr->end = lf->lno_e; - if (dwarf_func_inline(sp_die)) { - struct dwarf_callback_param _param; - _param.data = (void *)lf; - _param.retval = 0; - dwarf_func_inline_instances(sp_die, - line_range_inline_cb, - &_param); - param->retval = _param.retval; - } else + if (dwarf_func_inline(sp_die)) + param->retval = die_walk_instances(sp_die, + line_range_inline_cb, lf); + else param->retval = find_line_range_by_line(sp_die, lf); return DWARF_CB_ABORT; } @@ -1982,26 +1497,15 @@ static int find_line_range_by_func(struct line_finder *lf) return param.retval; } -int find_line_range(int fd, struct line_range *lr) +int debuginfo__find_line_range(struct debuginfo *self, struct line_range *lr) { struct line_finder lf = {.lr = lr, .found = 0}; int ret = 0; Dwarf_Off off = 0, noff; size_t cuhl; Dwarf_Die *diep; - Dwarf *dbg = NULL; - Dwfl *dwfl; - Dwarf_Addr bias; /* Currently ignored */ const char *comp_dir; - dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias); - if (!dbg) { - pr_warning("No debug information found in the vmlinux - " - "please rebuild with CONFIG_DEBUG_INFO=y.\n"); - close(fd); /* Without dwfl_end(), fd isn't closed. */ - return -EBADF; - } - /* Fastpath: lookup by function name from .debug_pubnames section */ if (lr->function) { struct pubname_callback_param pubname_param = { @@ -2010,7 +1514,8 @@ int find_line_range(int fd, struct line_range *lr) struct dwarf_callback_param line_range_param = { .data = (void *)&lf, .retval = 0}; - dwarf_getpubnames(dbg, pubname_search_cb, &pubname_param, 0); + dwarf_getpubnames(self->dbg, pubname_search_cb, + &pubname_param, 0); if (pubname_param.found) { line_range_search_cb(&lf.sp_die, &line_range_param); if (lf.found) @@ -2020,11 +1525,12 @@ int find_line_range(int fd, struct line_range *lr) /* Loop on CUs (Compilation Unit) */ while (!lf.found && ret >= 0) { - if (dwarf_nextcu(dbg, off, &noff, &cuhl, NULL, NULL, NULL) != 0) + if (dwarf_nextcu(self->dbg, off, &noff, &cuhl, + NULL, NULL, NULL) != 0) break; /* Get the DIE(Debugging Information Entry) of this CU */ - diep = dwarf_offdie(dbg, off + cuhl, &lf.cu_die); + diep = dwarf_offdie(self->dbg, off + cuhl, &lf.cu_die); if (!diep) continue; @@ -2058,7 +1564,6 @@ found: } pr_debug("path: %s\n", lr->path); - dwfl_end(dwfl); return (ret < 0) ? ret : lf.found; } diff --git a/tools/perf/util/probe-finder.h b/tools/perf/util/probe-finder.h index 605730a366db..1132c8f0ce89 100644 --- a/tools/perf/util/probe-finder.h +++ b/tools/perf/util/probe-finder.h @@ -16,33 +16,48 @@ static inline int is_c_varname(const char *name) } #ifdef DWARF_SUPPORT + +#include "dwarf-aux.h" + +/* TODO: export debuginfo data structure even if no dwarf support */ + +/* debug information structure */ +struct debuginfo { + Dwarf *dbg; + Dwfl *dwfl; + Dwarf_Addr bias; +}; + +extern struct debuginfo *debuginfo__new(const char *path); +extern struct debuginfo *debuginfo__new_online_kernel(unsigned long addr); +extern void debuginfo__delete(struct debuginfo *self); + /* Find probe_trace_events specified by perf_probe_event from debuginfo */ -extern int find_probe_trace_events(int fd, struct perf_probe_event *pev, - struct probe_trace_event **tevs, - int max_tevs); +extern int debuginfo__find_trace_events(struct debuginfo *self, + struct perf_probe_event *pev, + struct probe_trace_event **tevs, + int max_tevs); /* Find a perf_probe_point from debuginfo */ -extern int find_perf_probe_point(unsigned long addr, - struct perf_probe_point *ppt); +extern int debuginfo__find_probe_point(struct debuginfo *self, + unsigned long addr, + struct perf_probe_point *ppt); /* Find a line range */ -extern int find_line_range(int fd, struct line_range *lr); +extern int debuginfo__find_line_range(struct debuginfo *self, + struct line_range *lr); /* Find available variables */ -extern int find_available_vars_at(int fd, struct perf_probe_event *pev, - struct variable_list **vls, int max_points, - bool externs); - -#include <dwarf.h> -#include <elfutils/libdw.h> -#include <elfutils/libdwfl.h> -#include <elfutils/version.h> +extern int debuginfo__find_available_vars_at(struct debuginfo *self, + struct perf_probe_event *pev, + struct variable_list **vls, + int max_points, bool externs); struct probe_finder { struct perf_probe_event *pev; /* Target probe event */ /* Callback when a probe point is found */ - int (*callback)(Dwarf_Die *sp_die, struct probe_finder *pf); + int (*callback)(Dwarf_Die *sc_die, struct probe_finder *pf); /* For function searching */ int lno; /* Line number */ diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index a9ac0504aabd..9dd47a4f2596 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -187,16 +187,119 @@ static PyTypeObject pyrf_throttle_event__type = { .tp_repr = (reprfunc)pyrf_throttle_event__repr, }; +static char pyrf_lost_event__doc[] = PyDoc_STR("perf lost event object."); + +static PyMemberDef pyrf_lost_event__members[] = { + sample_members + member_def(lost_event, id, T_ULONGLONG, "event id"), + member_def(lost_event, lost, T_ULONGLONG, "number of lost events"), + { .name = NULL, }, +}; + +static PyObject *pyrf_lost_event__repr(struct pyrf_event *pevent) +{ + PyObject *ret; + char *s; + + if (asprintf(&s, "{ type: lost, id: %#" PRIx64 ", " + "lost: %#" PRIx64 " }", + pevent->event.lost.id, pevent->event.lost.lost) < 0) { + ret = PyErr_NoMemory(); + } else { + ret = PyString_FromString(s); + free(s); + } + return ret; +} + +static PyTypeObject pyrf_lost_event__type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "perf.lost_event", + .tp_basicsize = sizeof(struct pyrf_event), + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + .tp_doc = pyrf_lost_event__doc, + .tp_members = pyrf_lost_event__members, + .tp_repr = (reprfunc)pyrf_lost_event__repr, +}; + +static char pyrf_read_event__doc[] = PyDoc_STR("perf read event object."); + +static PyMemberDef pyrf_read_event__members[] = { + sample_members + member_def(read_event, pid, T_UINT, "event pid"), + member_def(read_event, tid, T_UINT, "event tid"), + { .name = NULL, }, +}; + +static PyObject *pyrf_read_event__repr(struct pyrf_event *pevent) +{ + return PyString_FromFormat("{ type: read, pid: %u, tid: %u }", + pevent->event.read.pid, + pevent->event.read.tid); + /* + * FIXME: return the array of read values, + * making this method useful ;-) + */ +} + +static PyTypeObject pyrf_read_event__type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "perf.read_event", + .tp_basicsize = sizeof(struct pyrf_event), + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + .tp_doc = pyrf_read_event__doc, + .tp_members = pyrf_read_event__members, + .tp_repr = (reprfunc)pyrf_read_event__repr, +}; + +static char pyrf_sample_event__doc[] = PyDoc_STR("perf sample event object."); + +static PyMemberDef pyrf_sample_event__members[] = { + sample_members + member_def(perf_event_header, type, T_UINT, "event type"), + { .name = NULL, }, +}; + +static PyObject *pyrf_sample_event__repr(struct pyrf_event *pevent) +{ + PyObject *ret; + char *s; + + if (asprintf(&s, "{ type: sample }") < 0) { + ret = PyErr_NoMemory(); + } else { + ret = PyString_FromString(s); + free(s); + } + return ret; +} + +static PyTypeObject pyrf_sample_event__type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "perf.sample_event", + .tp_basicsize = sizeof(struct pyrf_event), + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + .tp_doc = pyrf_sample_event__doc, + .tp_members = pyrf_sample_event__members, + .tp_repr = (reprfunc)pyrf_sample_event__repr, +}; + static int pyrf_event__setup_types(void) { int err; pyrf_mmap_event__type.tp_new = pyrf_task_event__type.tp_new = pyrf_comm_event__type.tp_new = + pyrf_lost_event__type.tp_new = + pyrf_read_event__type.tp_new = + pyrf_sample_event__type.tp_new = pyrf_throttle_event__type.tp_new = PyType_GenericNew; err = PyType_Ready(&pyrf_mmap_event__type); if (err < 0) goto out; + err = PyType_Ready(&pyrf_lost_event__type); + if (err < 0) + goto out; err = PyType_Ready(&pyrf_task_event__type); if (err < 0) goto out; @@ -206,20 +309,26 @@ static int pyrf_event__setup_types(void) err = PyType_Ready(&pyrf_throttle_event__type); if (err < 0) goto out; + err = PyType_Ready(&pyrf_read_event__type); + if (err < 0) + goto out; + err = PyType_Ready(&pyrf_sample_event__type); + if (err < 0) + goto out; out: return err; } static PyTypeObject *pyrf_event__type[] = { [PERF_RECORD_MMAP] = &pyrf_mmap_event__type, - [PERF_RECORD_LOST] = &pyrf_mmap_event__type, + [PERF_RECORD_LOST] = &pyrf_lost_event__type, [PERF_RECORD_COMM] = &pyrf_comm_event__type, [PERF_RECORD_EXIT] = &pyrf_task_event__type, [PERF_RECORD_THROTTLE] = &pyrf_throttle_event__type, [PERF_RECORD_UNTHROTTLE] = &pyrf_throttle_event__type, [PERF_RECORD_FORK] = &pyrf_task_event__type, - [PERF_RECORD_READ] = &pyrf_mmap_event__type, - [PERF_RECORD_SAMPLE] = &pyrf_mmap_event__type, + [PERF_RECORD_READ] = &pyrf_read_event__type, + [PERF_RECORD_SAMPLE] = &pyrf_sample_event__type, }; static PyObject *pyrf_event__new(union perf_event *event) @@ -247,7 +356,7 @@ struct pyrf_cpu_map { static int pyrf_cpu_map__init(struct pyrf_cpu_map *pcpus, PyObject *args, PyObject *kwargs) { - static char *kwlist[] = { "cpustr", NULL, NULL, }; + static char *kwlist[] = { "cpustr", NULL }; char *cpustr = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s", @@ -316,7 +425,7 @@ struct pyrf_thread_map { static int pyrf_thread_map__init(struct pyrf_thread_map *pthreads, PyObject *args, PyObject *kwargs) { - static char *kwlist[] = { "pid", "tid", NULL, NULL, }; + static char *kwlist[] = { "pid", "tid", NULL }; int pid = -1, tid = -1; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", @@ -418,7 +527,9 @@ static int pyrf_evsel__init(struct pyrf_evsel *pevsel, "wakeup_events", "bp_type", "bp_addr", - "bp_len", NULL, NULL, }; + "bp_len", + NULL + }; u64 sample_period = 0; u32 disabled = 0, inherit = 0, @@ -499,7 +610,7 @@ static PyObject *pyrf_evsel__open(struct pyrf_evsel *pevsel, struct thread_map *threads = NULL; PyObject *pcpus = NULL, *pthreads = NULL; int group = 0, inherit = 0; - static char *kwlist[] = {"cpus", "threads", "group", "inherit", NULL, NULL}; + static char *kwlist[] = { "cpus", "threads", "group", "inherit", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOii", kwlist, &pcpus, &pthreads, &group, &inherit)) @@ -512,7 +623,11 @@ static PyObject *pyrf_evsel__open(struct pyrf_evsel *pevsel, cpus = ((struct pyrf_cpu_map *)pcpus)->cpus; evsel->attr.inherit = inherit; - if (perf_evsel__open(evsel, cpus, threads, group) < 0) { + /* + * This will group just the fds for this single evsel, to group + * multiple events, use evlist.open(). + */ + if (perf_evsel__open(evsel, cpus, threads, group, NULL) < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -582,8 +697,7 @@ static PyObject *pyrf_evlist__mmap(struct pyrf_evlist *pevlist, PyObject *args, PyObject *kwargs) { struct perf_evlist *evlist = &pevlist->evlist; - static char *kwlist[] = {"pages", "overwrite", - NULL, NULL}; + static char *kwlist[] = { "pages", "overwrite", NULL }; int pages = 128, overwrite = false; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", kwlist, @@ -603,7 +717,7 @@ static PyObject *pyrf_evlist__poll(struct pyrf_evlist *pevlist, PyObject *args, PyObject *kwargs) { struct perf_evlist *evlist = &pevlist->evlist; - static char *kwlist[] = {"timeout", NULL, NULL}; + static char *kwlist[] = { "timeout", NULL }; int timeout = -1, n; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", kwlist, &timeout)) @@ -674,7 +788,7 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist, struct perf_evlist *evlist = &pevlist->evlist; union perf_event *event; int sample_id_all = 1, cpu; - static char *kwlist[] = {"cpu", "sample_id_all", NULL, NULL}; + static char *kwlist[] = { "cpu", "sample_id_all", NULL }; int err; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|i", kwlist, @@ -693,7 +807,7 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist, first = list_entry(evlist->entries.next, struct perf_evsel, node); err = perf_event__parse_sample(event, first->attr.sample_type, perf_evsel__sample_size(first), - sample_id_all, &pevent->sample); + sample_id_all, &pevent->sample, false); if (err) return PyErr_Format(PyExc_OSError, "perf: can't parse sample, err=%d", err); @@ -704,6 +818,25 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist, return Py_None; } +static PyObject *pyrf_evlist__open(struct pyrf_evlist *pevlist, + PyObject *args, PyObject *kwargs) +{ + struct perf_evlist *evlist = &pevlist->evlist; + int group = 0; + static char *kwlist[] = { "group", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOii", kwlist, &group)) + return NULL; + + if (perf_evlist__open(evlist, group) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + static PyMethodDef pyrf_evlist__methods[] = { { .ml_name = "mmap", @@ -712,6 +845,12 @@ static PyMethodDef pyrf_evlist__methods[] = { .ml_doc = PyDoc_STR("mmap the file descriptor table.") }, { + .ml_name = "open", + .ml_meth = (PyCFunction)pyrf_evlist__open, + .ml_flags = METH_VARARGS | METH_KEYWORDS, + .ml_doc = PyDoc_STR("open the file descriptors.") + }, + { .ml_name = "poll", .ml_meth = (PyCFunction)pyrf_evlist__poll, .ml_flags = METH_VARARGS | METH_KEYWORDS, diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index f5a8fbdd3f76..85c1e6b76f0a 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -12,6 +12,7 @@ #include "session.h" #include "sort.h" #include "util.h" +#include "cpumap.h" static int perf_session__open(struct perf_session *self, bool force) { @@ -247,9 +248,14 @@ int perf_session__resolve_callchain(struct perf_session *self, callchain_cursor_reset(&self->callchain_cursor); for (i = 0; i < chain->nr; i++) { - u64 ip = chain->ips[i]; + u64 ip; struct addr_location al; + if (callchain_param.order == ORDER_CALLEE) + ip = chain->ips[i]; + else + ip = chain->ips[chain->nr - i - 1]; + if (ip >= PERF_CONTEXT_MAX) { switch (ip) { case PERF_CONTEXT_HV: @@ -407,20 +413,26 @@ static void perf_event__read_swap(union perf_event *event) event->read.id = bswap_64(event->read.id); } -static void perf_event__attr_swap(union perf_event *event) +/* exported for swapping attributes in file header */ +void perf_event__attr_swap(struct perf_event_attr *attr) +{ + attr->type = bswap_32(attr->type); + attr->size = bswap_32(attr->size); + attr->config = bswap_64(attr->config); + attr->sample_period = bswap_64(attr->sample_period); + attr->sample_type = bswap_64(attr->sample_type); + attr->read_format = bswap_64(attr->read_format); + attr->wakeup_events = bswap_32(attr->wakeup_events); + attr->bp_type = bswap_32(attr->bp_type); + attr->bp_addr = bswap_64(attr->bp_addr); + attr->bp_len = bswap_64(attr->bp_len); +} + +static void perf_event__hdr_attr_swap(union perf_event *event) { size_t size; - event->attr.attr.type = bswap_32(event->attr.attr.type); - event->attr.attr.size = bswap_32(event->attr.attr.size); - event->attr.attr.config = bswap_64(event->attr.attr.config); - event->attr.attr.sample_period = bswap_64(event->attr.attr.sample_period); - event->attr.attr.sample_type = bswap_64(event->attr.attr.sample_type); - event->attr.attr.read_format = bswap_64(event->attr.attr.read_format); - event->attr.attr.wakeup_events = bswap_32(event->attr.attr.wakeup_events); - event->attr.attr.bp_type = bswap_32(event->attr.attr.bp_type); - event->attr.attr.bp_addr = bswap_64(event->attr.attr.bp_addr); - event->attr.attr.bp_len = bswap_64(event->attr.attr.bp_len); + perf_event__attr_swap(&event->attr.attr); size = event->header.size; size -= (void *)&event->attr.id - (void *)event; @@ -448,7 +460,7 @@ static perf_event__swap_op perf_event__swap_ops[] = { [PERF_RECORD_LOST] = perf_event__all64_swap, [PERF_RECORD_READ] = perf_event__read_swap, [PERF_RECORD_SAMPLE] = perf_event__all64_swap, - [PERF_RECORD_HEADER_ATTR] = perf_event__attr_swap, + [PERF_RECORD_HEADER_ATTR] = perf_event__hdr_attr_swap, [PERF_RECORD_HEADER_EVENT_TYPE] = perf_event__event_type_swap, [PERF_RECORD_HEADER_TRACING_DATA] = perf_event__tracing_data_swap, [PERF_RECORD_HEADER_BUILD_ID] = NULL, @@ -490,6 +502,7 @@ static void flush_sample_queue(struct perf_session *s, struct perf_sample sample; u64 limit = os->next_flush; u64 last_ts = os->last_sample ? os->last_sample->timestamp : 0ULL; + unsigned idx = 0, progress_next = os->nr_samples / 16; int ret; if (!ops->ordered_samples || !limit) @@ -509,6 +522,11 @@ static void flush_sample_queue(struct perf_session *s, os->last_flush = iter->timestamp; list_del(&iter->list); list_add(&iter->list, &os->sample_cache); + if (++idx >= progress_next) { + progress_next += os->nr_samples / 16; + ui_progress__update(idx, os->nr_samples, + "Processing time ordered events..."); + } } if (list_empty(head)) { @@ -517,6 +535,8 @@ static void flush_sample_queue(struct perf_session *s, os->last_sample = list_entry(head->prev, struct sample_queue, list); } + + os->nr_samples = 0; } /* @@ -576,6 +596,7 @@ static void __queue_event(struct sample_queue *new, struct perf_session *s) u64 timestamp = new->timestamp; struct list_head *p; + ++os->nr_samples; os->last_sample = new; if (!sample) { @@ -708,9 +729,9 @@ static void dump_sample(struct perf_session *session, union perf_event *event, if (!dump_trace) return; - printf("(IP, %d): %d/%d: %#" PRIx64 " period: %" PRIu64 "\n", + printf("(IP, %d): %d/%d: %#" PRIx64 " period: %" PRIu64 " addr: %#" PRIx64 "\n", event->header.misc, sample->pid, sample->tid, sample->ip, - sample->period); + sample->period, sample->addr); if (session->sample_type & PERF_SAMPLE_CALLCHAIN) callchain__printf(sample); @@ -726,10 +747,27 @@ static int perf_session_deliver_event(struct perf_session *session, dump_event(session, event, file_offset, sample); + evsel = perf_evlist__id2evsel(session->evlist, sample->id); + if (evsel != NULL && event->header.type != PERF_RECORD_SAMPLE) { + /* + * XXX We're leaving PERF_RECORD_SAMPLE unnacounted here + * because the tools right now may apply filters, discarding + * some of the samples. For consistency, in the future we + * should have something like nr_filtered_samples and remove + * the sample->period from total_sample_period, etc, KISS for + * now tho. + * + * Also testing against NULL allows us to handle files without + * attr.sample_id_all and/or without PERF_SAMPLE_ID. In the + * future probably it'll be a good idea to restrict event + * processing via perf_session to files with both set. + */ + hists__inc_nr_events(&evsel->hists, event->header.type); + } + switch (event->header.type) { case PERF_RECORD_SAMPLE: dump_sample(session, event, sample); - evsel = perf_evlist__id2evsel(session->evlist, sample->id); if (evsel == NULL) { ++session->hists.stats.nr_unknown_id; return -1; @@ -862,11 +900,11 @@ static void perf_session__warn_about_errors(const struct perf_session *session, const struct perf_event_ops *ops) { if (ops->lost == perf_event__process_lost && - session->hists.stats.total_lost != 0) { - ui__warning("Processed %" PRIu64 " events and LOST %" PRIu64 - "!\n\nCheck IO/CPU overload!\n\n", - session->hists.stats.total_period, - session->hists.stats.total_lost); + session->hists.stats.nr_events[PERF_RECORD_LOST] != 0) { + ui__warning("Processed %d events and lost %d chunks!\n\n" + "Check IO/CPU overload!\n\n", + session->hists.stats.nr_events[0], + session->hists.stats.nr_events[PERF_RECORD_LOST]); } if (session->hists.stats.nr_unknown_events != 0) { @@ -1000,7 +1038,6 @@ int __perf_session__process_events(struct perf_session *session, { u64 head, page_offset, file_offset, file_pos, progress_next; int err, mmap_prot, mmap_flags, map_idx = 0; - struct ui_progress *progress; size_t page_size, mmap_size; char *buf, *mmaps[8]; union perf_event *event; @@ -1018,9 +1055,6 @@ int __perf_session__process_events(struct perf_session *session, file_size = data_offset + data_size; progress_next = file_size / 16; - progress = ui_progress__new("Processing events...", file_size); - if (progress == NULL) - return -1; mmap_size = session->mmap_window; if (mmap_size > file_size) @@ -1083,7 +1117,8 @@ more: if (file_pos >= progress_next) { progress_next += file_size / 16; - ui_progress__update(progress, file_pos); + ui_progress__update(file_pos, file_size, + "Processing events..."); } if (file_pos < file_size) @@ -1094,7 +1129,6 @@ more: session->ordered_samples.next_flush = ULLONG_MAX; flush_sample_queue(session, ops); out_err: - ui_progress__delete(progress); perf_session__warn_about_errors(session, ops); perf_session_free_sample_buffers(session); return err; @@ -1202,9 +1236,10 @@ struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session, return NULL; } -void perf_session__print_symbols(union perf_event *event, - struct perf_sample *sample, - struct perf_session *session) +void perf_session__print_ip(union perf_event *event, + struct perf_sample *sample, + struct perf_session *session, + int print_sym, int print_dso) { struct addr_location al; const char *symname, *dsoname; @@ -1233,32 +1268,102 @@ void perf_session__print_symbols(union perf_event *event, if (!node) break; - if (node->sym && node->sym->name) - symname = node->sym->name; + printf("\t%16" PRIx64, node->ip); + if (print_sym) { + if (node->sym && node->sym->name) + symname = node->sym->name; + else + symname = ""; + + printf(" %s", symname); + } + if (print_dso) { + if (node->map && node->map->dso && node->map->dso->name) + dsoname = node->map->dso->name; + else + dsoname = ""; + + printf(" (%s)", dsoname); + } + printf("\n"); + + callchain_cursor_advance(cursor); + } + + } else { + printf("%16" PRIx64, sample->ip); + if (print_sym) { + if (al.sym && al.sym->name) + symname = al.sym->name; else symname = ""; - if (node->map && node->map->dso && node->map->dso->name) - dsoname = node->map->dso->name; + printf(" %s", symname); + } + + if (print_dso) { + if (al.map && al.map->dso && al.map->dso->name) + dsoname = al.map->dso->name; else dsoname = ""; - printf("\t%16" PRIx64 " %s (%s)\n", node->ip, symname, dsoname); + printf(" (%s)", dsoname); + } + } +} - callchain_cursor_advance(cursor); +int perf_session__cpu_bitmap(struct perf_session *session, + const char *cpu_list, unsigned long *cpu_bitmap) +{ + int i; + struct cpu_map *map; + + for (i = 0; i < PERF_TYPE_MAX; ++i) { + struct perf_evsel *evsel; + + evsel = perf_session__find_first_evtype(session, i); + if (!evsel) + continue; + + if (!(evsel->attr.sample_type & PERF_SAMPLE_CPU)) { + pr_err("File does not contain CPU events. " + "Remove -c option to proceed.\n"); + return -1; } + } - } else { - if (al.sym && al.sym->name) - symname = al.sym->name; - else - symname = ""; + map = cpu_map__new(cpu_list); - if (al.map && al.map->dso && al.map->dso->name) - dsoname = al.map->dso->name; - else - dsoname = ""; + for (i = 0; i < map->nr; i++) { + int cpu = map->map[i]; - printf("%16" PRIx64 " %s (%s)", al.addr, symname, dsoname); + if (cpu >= MAX_NR_CPUS) { + pr_err("Requested CPU %d too large. " + "Consider raising MAX_NR_CPUS\n", cpu); + return -1; + } + + set_bit(cpu, cpu_bitmap); } + + return 0; +} + +void perf_session__fprintf_info(struct perf_session *session, FILE *fp, + bool full) +{ + struct stat st; + int ret; + + if (session == NULL || fp == NULL) + return; + + ret = fstat(session->fd, &st); + if (ret == -1) + return; + + fprintf(fp, "# ========\n"); + fprintf(fp, "# captured on: %s", ctime(&st.st_ctime)); + perf_header__fprintf_info(session, fp, full); + fprintf(fp, "# ========\n#\n"); } diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index 66d4e1490879..6e393c98eb34 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h @@ -23,6 +23,7 @@ struct ordered_samples { struct sample_queue *sample_buffer; struct sample_queue *last_sample; int sample_buffer_idx; + unsigned int nr_samples; }; struct perf_session { @@ -112,6 +113,7 @@ int perf_session__set_kallsyms_ref_reloc_sym(struct map **maps, u64 addr); void mem_bswap_64(void *src, int byte_size); +void perf_event__attr_swap(struct perf_event_attr *attr); int perf_session__create_kernel_maps(struct perf_session *self); @@ -161,14 +163,20 @@ static inline int perf_session__parse_sample(struct perf_session *session, { return perf_event__parse_sample(event, session->sample_type, session->sample_size, - session->sample_id_all, sample); + session->sample_id_all, sample, + session->header.needs_swap); } struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session, unsigned int type); -void perf_session__print_symbols(union perf_event *event, +void perf_session__print_ip(union perf_event *event, struct perf_sample *sample, - struct perf_session *session); + struct perf_session *session, + int print_sym, int print_dso); +int perf_session__cpu_bitmap(struct perf_session *session, + const char *cpu_list, unsigned long *cpu_bitmap); + +void perf_session__fprintf_info(struct perf_session *s, FILE *fp, bool full); #endif /* __PERF_SESSION_H */ diff --git a/tools/perf/util/setup.py b/tools/perf/util/setup.py index bbc982f5dd8b..95d370074928 100644 --- a/tools/perf/util/setup.py +++ b/tools/perf/util/setup.py @@ -3,9 +3,27 @@ from distutils.core import setup, Extension from os import getenv +from distutils.command.build_ext import build_ext as _build_ext +from distutils.command.install_lib import install_lib as _install_lib + +class build_ext(_build_ext): + def finalize_options(self): + _build_ext.finalize_options(self) + self.build_lib = build_lib + self.build_temp = build_tmp + +class install_lib(_install_lib): + def finalize_options(self): + _install_lib.finalize_options(self) + self.build_dir = build_lib + + cflags = ['-fno-strict-aliasing', '-Wno-write-strings'] cflags += getenv('CFLAGS', '').split() +build_lib = getenv('PYTHON_EXTBUILD_LIB') +build_tmp = getenv('PYTHON_EXTBUILD_TMP') + perf = Extension('perf', sources = ['util/python.c', 'util/ctype.c', 'util/evlist.c', 'util/evsel.c', 'util/cpumap.c', 'util/thread_map.c', @@ -21,4 +39,5 @@ setup(name='perf', author_email='acme@redhat.com', license='GPLv2', url='http://perf.wiki.kernel.org', - ext_modules=[perf]) + ext_modules=[perf], + cmdclass={'build_ext': build_ext, 'install_lib': install_lib}) diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index f44fa541d56e..16da30d8d765 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -15,95 +15,6 @@ char * field_sep; LIST_HEAD(hist_entry__sort_list); -static int hist_entry__thread_snprintf(struct hist_entry *self, char *bf, - size_t size, unsigned int width); -static int hist_entry__comm_snprintf(struct hist_entry *self, char *bf, - size_t size, unsigned int width); -static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf, - size_t size, unsigned int width); -static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, - size_t size, unsigned int width); -static int hist_entry__parent_snprintf(struct hist_entry *self, char *bf, - size_t size, unsigned int width); -static int hist_entry__cpu_snprintf(struct hist_entry *self, char *bf, - size_t size, unsigned int width); - -struct sort_entry sort_thread = { - .se_header = "Command: Pid", - .se_cmp = sort__thread_cmp, - .se_snprintf = hist_entry__thread_snprintf, - .se_width_idx = HISTC_THREAD, -}; - -struct sort_entry sort_comm = { - .se_header = "Command", - .se_cmp = sort__comm_cmp, - .se_collapse = sort__comm_collapse, - .se_snprintf = hist_entry__comm_snprintf, - .se_width_idx = HISTC_COMM, -}; - -struct sort_entry sort_dso = { - .se_header = "Shared Object", - .se_cmp = sort__dso_cmp, - .se_snprintf = hist_entry__dso_snprintf, - .se_width_idx = HISTC_DSO, -}; - -struct sort_entry sort_sym = { - .se_header = "Symbol", - .se_cmp = sort__sym_cmp, - .se_snprintf = hist_entry__sym_snprintf, - .se_width_idx = HISTC_SYMBOL, -}; - -struct sort_entry sort_parent = { - .se_header = "Parent symbol", - .se_cmp = sort__parent_cmp, - .se_snprintf = hist_entry__parent_snprintf, - .se_width_idx = HISTC_PARENT, -}; - -struct sort_entry sort_cpu = { - .se_header = "CPU", - .se_cmp = sort__cpu_cmp, - .se_snprintf = hist_entry__cpu_snprintf, - .se_width_idx = HISTC_CPU, -}; - -struct sort_dimension { - const char *name; - struct sort_entry *entry; - int taken; -}; - -static struct sort_dimension sort_dimensions[] = { - { .name = "pid", .entry = &sort_thread, }, - { .name = "comm", .entry = &sort_comm, }, - { .name = "dso", .entry = &sort_dso, }, - { .name = "symbol", .entry = &sort_sym, }, - { .name = "parent", .entry = &sort_parent, }, - { .name = "cpu", .entry = &sort_cpu, }, -}; - -int64_t cmp_null(void *l, void *r) -{ - if (!l && !r) - return 0; - else if (!l) - return -1; - else - return 1; -} - -/* --sort pid */ - -int64_t -sort__thread_cmp(struct hist_entry *left, struct hist_entry *right) -{ - return right->thread->pid - left->thread->pid; -} - static int repsep_snprintf(char *bf, size_t size, const char *fmt, ...) { int n; @@ -125,6 +36,24 @@ static int repsep_snprintf(char *bf, size_t size, const char *fmt, ...) return n; } +static int64_t cmp_null(void *l, void *r) +{ + if (!l && !r) + return 0; + else if (!l) + return -1; + else + return 1; +} + +/* --sort pid */ + +static int64_t +sort__thread_cmp(struct hist_entry *left, struct hist_entry *right) +{ + return right->thread->pid - left->thread->pid; +} + static int hist_entry__thread_snprintf(struct hist_entry *self, char *bf, size_t size, unsigned int width) { @@ -132,15 +61,50 @@ static int hist_entry__thread_snprintf(struct hist_entry *self, char *bf, self->thread->comm ?: "", self->thread->pid); } +struct sort_entry sort_thread = { + .se_header = "Command: Pid", + .se_cmp = sort__thread_cmp, + .se_snprintf = hist_entry__thread_snprintf, + .se_width_idx = HISTC_THREAD, +}; + +/* --sort comm */ + +static int64_t +sort__comm_cmp(struct hist_entry *left, struct hist_entry *right) +{ + return right->thread->pid - left->thread->pid; +} + +static int64_t +sort__comm_collapse(struct hist_entry *left, struct hist_entry *right) +{ + char *comm_l = left->thread->comm; + char *comm_r = right->thread->comm; + + if (!comm_l || !comm_r) + return cmp_null(comm_l, comm_r); + + return strcmp(comm_l, comm_r); +} + static int hist_entry__comm_snprintf(struct hist_entry *self, char *bf, size_t size, unsigned int width) { return repsep_snprintf(bf, size, "%*s", width, self->thread->comm); } +struct sort_entry sort_comm = { + .se_header = "Command", + .se_cmp = sort__comm_cmp, + .se_collapse = sort__comm_collapse, + .se_snprintf = hist_entry__comm_snprintf, + .se_width_idx = HISTC_COMM, +}; + /* --sort dso */ -int64_t +static int64_t sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) { struct dso *dso_l = left->ms.map ? left->ms.map->dso : NULL; @@ -173,18 +137,31 @@ static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf, return repsep_snprintf(bf, size, "%-*s", width, "[unknown]"); } +struct sort_entry sort_dso = { + .se_header = "Shared Object", + .se_cmp = sort__dso_cmp, + .se_snprintf = hist_entry__dso_snprintf, + .se_width_idx = HISTC_DSO, +}; + /* --sort symbol */ -int64_t +static int64_t sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) { u64 ip_l, ip_r; + if (!left->ms.sym && !right->ms.sym) + return right->level - left->level; + + if (!left->ms.sym || !right->ms.sym) + return cmp_null(left->ms.sym, right->ms.sym); + if (left->ms.sym == right->ms.sym) return 0; - ip_l = left->ms.sym ? left->ms.sym->start : left->ip; - ip_r = right->ms.sym ? right->ms.sym->start : right->ip; + ip_l = left->ms.sym->start; + ip_r = right->ms.sym->start; return (int64_t)(ip_r - ip_l); } @@ -200,7 +177,9 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, BITS_PER_LONG / 4, self->ip, o); } - ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level); + if (!sort_dso.elide) + ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level); + if (self->ms.sym) ret += repsep_snprintf(bf + ret, size - ret, "%s", self->ms.sym->name); @@ -211,29 +190,16 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, return ret; } -/* --sort comm */ - -int64_t -sort__comm_cmp(struct hist_entry *left, struct hist_entry *right) -{ - return right->thread->pid - left->thread->pid; -} - -int64_t -sort__comm_collapse(struct hist_entry *left, struct hist_entry *right) -{ - char *comm_l = left->thread->comm; - char *comm_r = right->thread->comm; - - if (!comm_l || !comm_r) - return cmp_null(comm_l, comm_r); - - return strcmp(comm_l, comm_r); -} +struct sort_entry sort_sym = { + .se_header = "Symbol", + .se_cmp = sort__sym_cmp, + .se_snprintf = hist_entry__sym_snprintf, + .se_width_idx = HISTC_SYMBOL, +}; /* --sort parent */ -int64_t +static int64_t sort__parent_cmp(struct hist_entry *left, struct hist_entry *right) { struct symbol *sym_l = left->parent; @@ -252,9 +218,16 @@ static int hist_entry__parent_snprintf(struct hist_entry *self, char *bf, self->parent ? self->parent->name : "[other]"); } +struct sort_entry sort_parent = { + .se_header = "Parent symbol", + .se_cmp = sort__parent_cmp, + .se_snprintf = hist_entry__parent_snprintf, + .se_width_idx = HISTC_PARENT, +}; + /* --sort cpu */ -int64_t +static int64_t sort__cpu_cmp(struct hist_entry *left, struct hist_entry *right) { return right->cpu - left->cpu; @@ -266,6 +239,28 @@ static int hist_entry__cpu_snprintf(struct hist_entry *self, char *bf, return repsep_snprintf(bf, size, "%-*d", width, self->cpu); } +struct sort_entry sort_cpu = { + .se_header = "CPU", + .se_cmp = sort__cpu_cmp, + .se_snprintf = hist_entry__cpu_snprintf, + .se_width_idx = HISTC_CPU, +}; + +struct sort_dimension { + const char *name; + struct sort_entry *entry; + int taken; +}; + +static struct sort_dimension sort_dimensions[] = { + { .name = "pid", .entry = &sort_thread, }, + { .name = "comm", .entry = &sort_comm, }, + { .name = "dso", .entry = &sort_dso, }, + { .name = "symbol", .entry = &sort_sym, }, + { .name = "parent", .entry = &sort_parent, }, + { .name = "cpu", .entry = &sort_cpu, }, +}; + int sort_dimension__add(const char *tok) { unsigned int i; @@ -273,15 +268,9 @@ int sort_dimension__add(const char *tok) for (i = 0; i < ARRAY_SIZE(sort_dimensions); i++) { struct sort_dimension *sd = &sort_dimensions[i]; - if (sd->taken) - continue; - if (strncasecmp(tok, sd->name, strlen(tok))) continue; - if (sd->entry->se_collapse) - sort__need_collapse = 1; - if (sd->entry == &sort_parent) { int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED); if (ret) { @@ -294,6 +283,12 @@ int sort_dimension__add(const char *tok) sort__has_parent = 1; } + if (sd->taken) + return 0; + + if (sd->entry->se_collapse) + sort__need_collapse = 1; + if (list_empty(&hist_entry__sort_list)) { if (!strcmp(sd->name, "pid")) sort__first_dimension = SORT_PID; diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 0b91053a7d11..3f67ae395752 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -45,6 +45,7 @@ extern enum sort_type sort__first_dimension; * @nr_rows - rows expanded in callchain, recalculated on folding/unfolding */ struct hist_entry { + struct rb_node rb_node_in; struct rb_node rb_node; u64 period; u64 period_sys; @@ -63,6 +64,7 @@ struct hist_entry { bool init_have_children; char level; + bool used; u8 filtered; struct symbol *parent; union { @@ -103,20 +105,6 @@ extern struct sort_entry sort_thread; extern struct list_head hist_entry__sort_list; void setup_sorting(const char * const usagestr[], const struct option *opts); - -extern size_t sort__thread_print(FILE *, struct hist_entry *, unsigned int); -extern size_t sort__comm_print(FILE *, struct hist_entry *, unsigned int); -extern size_t sort__dso_print(FILE *, struct hist_entry *, unsigned int); -extern size_t sort__sym_print(FILE *, struct hist_entry *, unsigned int __used); -extern int64_t cmp_null(void *, void *); -extern int64_t sort__thread_cmp(struct hist_entry *, struct hist_entry *); -extern int64_t sort__comm_cmp(struct hist_entry *, struct hist_entry *); -extern int64_t sort__comm_collapse(struct hist_entry *, struct hist_entry *); -extern int64_t sort__dso_cmp(struct hist_entry *, struct hist_entry *); -extern int64_t sort__sym_cmp(struct hist_entry *, struct hist_entry *); -extern int64_t sort__parent_cmp(struct hist_entry *, struct hist_entry *); -int64_t sort__cpu_cmp(struct hist_entry *left, struct hist_entry *right); -extern size_t sort__parent_print(FILE *, struct hist_entry *, unsigned int); extern int sort_dimension__add(const char *); void sort_entry__setup_elide(struct sort_entry *self, struct strlist *list, const char *list_name, FILE *fp); diff --git a/tools/perf/util/string.c b/tools/perf/util/string.c index b9a985dadd08..d5836382ff2c 100644 --- a/tools/perf/util/string.c +++ b/tools/perf/util/string.c @@ -294,3 +294,22 @@ bool strlazymatch(const char *str, const char *pat) { return __match_glob(str, pat, true); } + +/** + * strtailcmp - Compare the tail of two strings + * @s1: 1st string to be compared + * @s2: 2nd string to be compared + * + * Return 0 if whole of either string is same as another's tail part. + */ +int strtailcmp(const char *s1, const char *s2) +{ + int i1 = strlen(s1); + int i2 = strlen(s2); + while (--i1 >= 0 && --i2 >= 0) { + if (s1[i1] != s2[i2]) + return s1[i1] - s2[i2]; + } + return 0; +} + diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index eec196329fd9..632b50c7bc26 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -24,7 +24,7 @@ #include <sys/utsname.h> #ifndef KSYM_NAME_LEN -#define KSYM_NAME_LEN 128 +#define KSYM_NAME_LEN 256 #endif #ifndef NT_GNU_BUILD_ID @@ -46,6 +46,7 @@ struct symbol_conf symbol_conf = { .exclude_other = true, .use_modules = true, .try_vmlinux_path = true, + .annotate_src = true, .symfs = "", }; @@ -74,16 +75,104 @@ static void dso__set_sorted_by_name(struct dso *dso, enum map_type type) bool symbol_type__is_a(char symbol_type, enum map_type map_type) { + symbol_type = toupper(symbol_type); + switch (map_type) { case MAP__FUNCTION: return symbol_type == 'T' || symbol_type == 'W'; case MAP__VARIABLE: - return symbol_type == 'D' || symbol_type == 'd'; + return symbol_type == 'D'; default: return false; } } +static int prefix_underscores_count(const char *str) +{ + const char *tail = str; + + while (*tail == '_') + tail++; + + return tail - str; +} + +#define SYMBOL_A 0 +#define SYMBOL_B 1 + +static int choose_best_symbol(struct symbol *syma, struct symbol *symb) +{ + s64 a; + s64 b; + + /* Prefer a symbol with non zero length */ + a = syma->end - syma->start; + b = symb->end - symb->start; + if ((b == 0) && (a > 0)) + return SYMBOL_A; + else if ((a == 0) && (b > 0)) + return SYMBOL_B; + + /* Prefer a non weak symbol over a weak one */ + a = syma->binding == STB_WEAK; + b = symb->binding == STB_WEAK; + if (b && !a) + return SYMBOL_A; + if (a && !b) + return SYMBOL_B; + + /* Prefer a global symbol over a non global one */ + a = syma->binding == STB_GLOBAL; + b = symb->binding == STB_GLOBAL; + if (a && !b) + return SYMBOL_A; + if (b && !a) + return SYMBOL_B; + + /* Prefer a symbol with less underscores */ + a = prefix_underscores_count(syma->name); + b = prefix_underscores_count(symb->name); + if (b > a) + return SYMBOL_A; + else if (a > b) + return SYMBOL_B; + + /* If all else fails, choose the symbol with the longest name */ + if (strlen(syma->name) >= strlen(symb->name)) + return SYMBOL_A; + else + return SYMBOL_B; +} + +static void symbols__fixup_duplicate(struct rb_root *symbols) +{ + struct rb_node *nd; + struct symbol *curr, *next; + + nd = rb_first(symbols); + + while (nd) { + curr = rb_entry(nd, struct symbol, rb_node); +again: + nd = rb_next(&curr->rb_node); + next = rb_entry(nd, struct symbol, rb_node); + + if (!nd) + break; + + if (curr->start != next->start) + continue; + + if (choose_best_symbol(curr, next) == SYMBOL_A) { + rb_erase(&next->rb_node, symbols); + goto again; + } else { + nd = rb_next(&curr->rb_node); + rb_erase(&curr->rb_node, symbols); + } + } +} + static void symbols__fixup_end(struct rb_root *symbols) { struct rb_node *nd, *prevnd = rb_first(symbols); @@ -438,18 +527,11 @@ int kallsyms__parse(const char *filename, void *arg, char *line = NULL; size_t n; int err = -1; - u64 prev_start = 0; - char prev_symbol_type = 0; - char *prev_symbol_name; FILE *file = fopen(filename, "r"); if (file == NULL) goto out_failure; - prev_symbol_name = malloc(KSYM_NAME_LEN); - if (prev_symbol_name == NULL) - goto out_close; - err = 0; while (!feof(file)) { @@ -470,7 +552,7 @@ int kallsyms__parse(const char *filename, void *arg, if (len + 2 >= line_len) continue; - symbol_type = toupper(line[len]); + symbol_type = line[len]; len += 2; symbol_name = line + len; len = line_len - len; @@ -480,24 +562,18 @@ int kallsyms__parse(const char *filename, void *arg, break; } - if (prev_symbol_type) { - u64 end = start; - if (end != prev_start) - --end; - err = process_symbol(arg, prev_symbol_name, - prev_symbol_type, prev_start, end); - if (err) - break; - } - - memcpy(prev_symbol_name, symbol_name, len + 1); - prev_symbol_type = symbol_type; - prev_start = start; + /* + * module symbols are not sorted so we add all + * symbols with zero length and rely on + * symbols__fixup_end() to fix it up. + */ + err = process_symbol(arg, symbol_name, + symbol_type, start, start); + if (err) + break; } - free(prev_symbol_name); free(line); -out_close: fclose(file); return err; @@ -703,6 +779,9 @@ int dso__load_kallsyms(struct dso *dso, const char *filename, if (dso__load_all_kallsyms(dso, filename, map) < 0) return -1; + symbols__fixup_duplicate(&dso->symbols[map->type]); + symbols__fixup_end(&dso->symbols[map->type]); + if (dso->kernel == DSO_TYPE_GUEST_KERNEL) dso->symtab_type = SYMTAB__GUEST_KALLSYMS; else @@ -1092,8 +1171,7 @@ static int dso__load_sym(struct dso *dso, struct map *map, const char *name, if (dso->has_build_id) { u8 build_id[BUILD_ID_SIZE]; - if (elf_read_build_id(elf, build_id, - BUILD_ID_SIZE) != BUILD_ID_SIZE) + if (elf_read_build_id(elf, build_id, BUILD_ID_SIZE) < 0) goto out_elf_end; if (!dso__build_id_equal(dso, build_id)) @@ -1111,6 +1189,8 @@ static int dso__load_sym(struct dso *dso, struct map *map, const char *name, } opdsec = elf_section_by_name(elf, &ehdr, &opdshdr, ".opd", &opdidx); + if (opdshdr.sh_type != SHT_PROGBITS) + opdsec = NULL; if (opdsec) opddata = elf_rawdata(opdsec, NULL); @@ -1276,6 +1356,7 @@ new_symbol: * For misannotated, zeroed, ASM function sizes. */ if (nr > 0) { + symbols__fixup_duplicate(&dso->symbols[map->type]); symbols__fixup_end(&dso->symbols[map->type]); if (kmap) { /* @@ -1362,8 +1443,8 @@ static int elf_read_build_id(Elf *elf, void *bf, size_t size) ptr = data->d_buf; while (ptr < (data->d_buf + data->d_size)) { GElf_Nhdr *nhdr = ptr; - int namesz = NOTE_ALIGN(nhdr->n_namesz), - descsz = NOTE_ALIGN(nhdr->n_descsz); + size_t namesz = NOTE_ALIGN(nhdr->n_namesz), + descsz = NOTE_ALIGN(nhdr->n_descsz); const char *name; ptr += sizeof(*nhdr); @@ -1372,8 +1453,10 @@ static int elf_read_build_id(Elf *elf, void *bf, size_t size) if (nhdr->n_type == NT_GNU_BUILD_ID && nhdr->n_namesz == sizeof("GNU")) { if (memcmp(name, "GNU", sizeof("GNU")) == 0) { - memcpy(bf, ptr, BUILD_ID_SIZE); - err = BUILD_ID_SIZE; + size_t sz = min(size, descsz); + memcpy(bf, ptr, sz); + memset(bf + sz, 0, size - sz); + err = descsz; break; } } @@ -1425,7 +1508,7 @@ int sysfs__read_build_id(const char *filename, void *build_id, size_t size) while (1) { char bf[BUFSIZ]; GElf_Nhdr nhdr; - int namesz, descsz; + size_t namesz, descsz; if (read(fd, &nhdr, sizeof(nhdr)) != sizeof(nhdr)) break; @@ -1434,15 +1517,16 @@ int sysfs__read_build_id(const char *filename, void *build_id, size_t size) descsz = NOTE_ALIGN(nhdr.n_descsz); if (nhdr.n_type == NT_GNU_BUILD_ID && nhdr.n_namesz == sizeof("GNU")) { - if (read(fd, bf, namesz) != namesz) + if (read(fd, bf, namesz) != (ssize_t)namesz) break; if (memcmp(bf, "GNU", sizeof("GNU")) == 0) { - if (read(fd, build_id, - BUILD_ID_SIZE) == BUILD_ID_SIZE) { + size_t sz = min(descsz, size); + if (read(fd, build_id, sz) == (ssize_t)sz) { + memset(build_id + sz, 0, size - sz); err = 0; break; } - } else if (read(fd, bf, descsz) != descsz) + } else if (read(fd, bf, descsz) != (ssize_t)descsz) break; } else { int n = namesz + descsz; @@ -1504,6 +1588,17 @@ int dso__load(struct dso *dso, struct map *map, symbol_filter_t filter) dso->adjust_symbols = 0; if (strncmp(dso->name, "/tmp/perf-", 10) == 0) { + struct stat st; + + if (lstat(dso->name, &st) < 0) + return -1; + + if (st.st_uid && (st.st_uid != geteuid())) { + pr_warning("File %s not owned by current user or root, " + "ignoring it.\n", dso->name); + return -1; + } + ret = dso__load_perf_map(dso, map, filter); dso->symtab_type = ret > 0 ? SYMTAB__JAVA_JIT : SYMTAB__NOT_FOUND; @@ -2170,27 +2265,22 @@ size_t machines__fprintf_dsos_buildid(struct rb_root *machines, return ret; } -struct dso *dso__new_kernel(const char *name) +static struct dso* +dso__kernel_findnew(struct machine *machine, const char *name, + const char *short_name, int dso_type) { - struct dso *dso = dso__new(name ?: "[kernel.kallsyms]"); - - if (dso != NULL) { - dso__set_short_name(dso, "[kernel]"); - dso->kernel = DSO_TYPE_KERNEL; - } - - return dso; -} + /* + * The kernel dso could be created by build_id processing. + */ + struct dso *dso = __dsos__findnew(&machine->kernel_dsos, name); -static struct dso *dso__new_guest_kernel(struct machine *machine, - const char *name) -{ - char bf[PATH_MAX]; - struct dso *dso = dso__new(name ?: machine__mmap_name(machine, bf, - sizeof(bf))); + /* + * We need to run this in all cases, since during the build_id + * processing we had no idea this was the kernel dso. + */ if (dso != NULL) { - dso__set_short_name(dso, "[guest.kernel]"); - dso->kernel = DSO_TYPE_GUEST_KERNEL; + dso__set_short_name(dso, short_name); + dso->kernel = dso_type; } return dso; @@ -2208,24 +2298,36 @@ void dso__read_running_kernel_build_id(struct dso *dso, struct machine *machine) dso->has_build_id = true; } -static struct dso *machine__create_kernel(struct machine *machine) +static struct dso *machine__get_kernel(struct machine *machine) { const char *vmlinux_name = NULL; struct dso *kernel; if (machine__is_host(machine)) { vmlinux_name = symbol_conf.vmlinux_name; - kernel = dso__new_kernel(vmlinux_name); + if (!vmlinux_name) + vmlinux_name = "[kernel.kallsyms]"; + + kernel = dso__kernel_findnew(machine, vmlinux_name, + "[kernel]", + DSO_TYPE_KERNEL); } else { + char bf[PATH_MAX]; + if (machine__is_default_guest(machine)) vmlinux_name = symbol_conf.default_guest_vmlinux_name; - kernel = dso__new_guest_kernel(machine, vmlinux_name); + if (!vmlinux_name) + vmlinux_name = machine__mmap_name(machine, bf, + sizeof(bf)); + + kernel = dso__kernel_findnew(machine, vmlinux_name, + "[guest.kernel]", + DSO_TYPE_GUEST_KERNEL); } - if (kernel != NULL) { + if (kernel != NULL && (!kernel->has_build_id)) dso__read_running_kernel_build_id(kernel, machine); - dsos__add(&machine->kernel_dsos, kernel); - } + return kernel; } @@ -2329,7 +2431,7 @@ void machine__destroy_kernel_maps(struct machine *machine) int machine__create_kernel_maps(struct machine *machine) { - struct dso *kernel = machine__create_kernel(machine); + struct dso *kernel = machine__get_kernel(machine); if (kernel == NULL || __machine__create_kernel_maps(machine, kernel) < 0) diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 325ee36a9d29..29f8d742e92f 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -72,11 +72,14 @@ struct symbol_conf { use_modules, sort_by_name, show_nr_samples, + show_total_period, use_callchain, exclude_other, show_cpu_utilization, initialized, - kptr_restrict; + kptr_restrict, + annotate_asm_raw, + annotate_src; const char *vmlinux_name, *kallsyms_name, *source_prefix, @@ -155,7 +158,6 @@ struct dso { }; struct dso *dso__new(const char *name); -struct dso *dso__new_kernel(const char *name); void dso__delete(struct dso *dso); int dso__name_len(const struct dso *dso); diff --git a/tools/perf/util/top.c b/tools/perf/util/top.c index a11f60735a18..500471dffa4f 100644 --- a/tools/perf/util/top.c +++ b/tools/perf/util/top.c @@ -15,52 +15,6 @@ #include "top.h" #include <inttypes.h> -/* - * Ordering weight: count-1 * count-2 * ... / count-n - */ -static double sym_weight(const struct sym_entry *sym, struct perf_top *top) -{ - double weight = sym->snap_count; - int counter; - - if (!top->display_weighted) - return weight; - - for (counter = 1; counter < top->evlist->nr_entries - 1; counter++) - weight *= sym->count[counter]; - - weight /= (sym->count[counter] + 1); - - return weight; -} - -static void perf_top__remove_active_sym(struct perf_top *top, struct sym_entry *syme) -{ - pthread_mutex_lock(&top->active_symbols_lock); - list_del_init(&syme->node); - pthread_mutex_unlock(&top->active_symbols_lock); -} - -static void rb_insert_active_sym(struct rb_root *tree, struct sym_entry *se) -{ - struct rb_node **p = &tree->rb_node; - struct rb_node *parent = NULL; - struct sym_entry *iter; - - while (*p != NULL) { - parent = *p; - iter = rb_entry(parent, struct sym_entry, rb_node); - - if (se->weight > iter->weight) - p = &(*p)->rb_left; - else - p = &(*p)->rb_right; - } - - rb_link_node(&se->rb_node, parent, p); - rb_insert_color(&se->rb_node, tree); -} - #define SNPRINTF(buf, size, fmt, args...) \ ({ \ size_t r = snprintf(buf, size, fmt, ## args); \ @@ -69,7 +23,6 @@ static void rb_insert_active_sym(struct rb_root *tree, struct sym_entry *se) size_t perf_top__header_snprintf(struct perf_top *top, char *bf, size_t size) { - struct perf_evsel *counter; float samples_per_sec = top->samples / top->delay_secs; float ksamples_per_sec = top->kernel_samples / top->delay_secs; float esamples_percent = (100.0 * top->exact_samples) / top->samples; @@ -104,7 +57,7 @@ size_t perf_top__header_snprintf(struct perf_top *top, char *bf, size_t size) esamples_percent); } - if (top->evlist->nr_entries == 1 || !top->display_weighted) { + if (top->evlist->nr_entries == 1) { struct perf_evsel *first; first = list_entry(top->evlist->entries.next, struct perf_evsel, node); ret += SNPRINTF(bf + ret, size - ret, "%" PRIu64 "%s ", @@ -112,27 +65,7 @@ size_t perf_top__header_snprintf(struct perf_top *top, char *bf, size_t size) top->freq ? "Hz" : ""); } - if (!top->display_weighted) { - ret += SNPRINTF(bf + ret, size - ret, "%s", - event_name(top->sym_evsel)); - } else { - /* - * Don't let events eat all the space. Leaving 30 bytes - * for the rest should be enough. - */ - size_t last_pos = size - 30; - - list_for_each_entry(counter, &top->evlist->entries, node) { - ret += SNPRINTF(bf + ret, size - ret, "%s%s", - counter->idx ? "/" : "", - event_name(counter)); - if (ret > last_pos) { - sprintf(bf + last_pos - 3, ".."); - ret = last_pos - 1; - break; - } - } - } + ret += SNPRINTF(bf + ret, size - ret, "%s", event_name(top->sym_evsel)); ret += SNPRINTF(bf + ret, size - ret, "], "); @@ -166,73 +99,3 @@ void perf_top__reset_sample_counters(struct perf_top *top) top->exact_samples = top->guest_kernel_samples = top->guest_us_samples = 0; } - -float perf_top__decay_samples(struct perf_top *top, struct rb_root *root) -{ - struct sym_entry *syme, *n; - float sum_ksamples = 0.0; - int snap = !top->display_weighted ? top->sym_evsel->idx : 0, j; - - /* Sort the active symbols */ - pthread_mutex_lock(&top->active_symbols_lock); - syme = list_entry(top->active_symbols.next, struct sym_entry, node); - pthread_mutex_unlock(&top->active_symbols_lock); - - top->rb_entries = 0; - list_for_each_entry_safe_from(syme, n, &top->active_symbols, node) { - syme->snap_count = syme->count[snap]; - if (syme->snap_count != 0) { - - if ((top->hide_user_symbols && - syme->map->dso->kernel == DSO_TYPE_USER) || - (top->hide_kernel_symbols && - syme->map->dso->kernel == DSO_TYPE_KERNEL)) { - perf_top__remove_active_sym(top, syme); - continue; - } - syme->weight = sym_weight(syme, top); - - if ((int)syme->snap_count >= top->count_filter) { - rb_insert_active_sym(root, syme); - ++top->rb_entries; - } - sum_ksamples += syme->snap_count; - - for (j = 0; j < top->evlist->nr_entries; j++) - syme->count[j] = top->zero ? 0 : syme->count[j] * 7 / 8; - } else - perf_top__remove_active_sym(top, syme); - } - - return sum_ksamples; -} - -/* - * Find the longest symbol name that will be displayed - */ -void perf_top__find_widths(struct perf_top *top, struct rb_root *root, - int *dso_width, int *dso_short_width, int *sym_width) -{ - struct rb_node *nd; - int printed = 0; - - *sym_width = *dso_width = *dso_short_width = 0; - - for (nd = rb_first(root); nd; nd = rb_next(nd)) { - struct sym_entry *syme = rb_entry(nd, struct sym_entry, rb_node); - struct symbol *sym = sym_entry__symbol(syme); - - if (++printed > top->print_entries || - (int)syme->snap_count < top->count_filter) - continue; - - if (syme->map->dso->long_name_len > *dso_width) - *dso_width = syme->map->dso->long_name_len; - - if (syme->map->dso->short_name_len > *dso_short_width) - *dso_short_width = syme->map->dso->short_name_len; - - if (sym->namelen > *sym_width) - *sym_width = sym->namelen; - } -} diff --git a/tools/perf/util/top.h b/tools/perf/util/top.h index bfbf95bcc603..399650967958 100644 --- a/tools/perf/util/top.h +++ b/tools/perf/util/top.h @@ -4,26 +4,10 @@ #include "types.h" #include "../perf.h" #include <stddef.h> -#include <pthread.h> -#include <linux/list.h> -#include <linux/rbtree.h> struct perf_evlist; struct perf_evsel; - -struct sym_entry { - struct rb_node rb_node; - struct list_head node; - unsigned long snap_count; - double weight; - struct map *map; - unsigned long count[0]; -}; - -static inline struct symbol *sym_entry__symbol(struct sym_entry *self) -{ - return ((void *)self) + symbol_conf.priv_size; -} +struct perf_session; struct perf_top { struct perf_evlist *evlist; @@ -31,34 +15,20 @@ struct perf_top { * Symbols will be added here in perf_event__process_sample and will * get out after decayed. */ - struct list_head active_symbols; - pthread_mutex_t active_symbols_lock; - pthread_cond_t active_symbols_cond; u64 samples; u64 kernel_samples, us_samples; u64 exact_samples; u64 guest_us_samples, guest_kernel_samples; int print_entries, count_filter, delay_secs; - int display_weighted, freq, rb_entries; + int freq; pid_t target_pid, target_tid; bool hide_kernel_symbols, hide_user_symbols, zero; const char *cpu_list; - struct sym_entry *sym_filter_entry; + struct hist_entry *sym_filter_entry; struct perf_evsel *sym_evsel; + struct perf_session *session; }; size_t perf_top__header_snprintf(struct perf_top *top, char *bf, size_t size); void perf_top__reset_sample_counters(struct perf_top *top); -float perf_top__decay_samples(struct perf_top *top, struct rb_root *root); -void perf_top__find_widths(struct perf_top *top, struct rb_root *root, - int *dso_width, int *dso_short_width, int *sym_width); - -#ifdef NO_NEWT_SUPPORT -static inline int perf_top__tui_browser(struct perf_top *top __used) -{ - return 0; -} -#else -int perf_top__tui_browser(struct perf_top *top); -#endif #endif /* __PERF_TOP_H */ diff --git a/tools/perf/util/trace-event-info.c b/tools/perf/util/trace-event-info.c index 35729f4c40cb..d2655f08bcc0 100644 --- a/tools/perf/util/trace-event-info.c +++ b/tools/perf/util/trace-event-info.c @@ -80,7 +80,7 @@ static void die(const char *fmt, ...) int ret = errno; if (errno) - perror("trace-cmd"); + perror("perf"); else ret = -1; @@ -183,106 +183,60 @@ int bigendian(void) return *ptr == 0x01020304; } -static unsigned long long copy_file_fd(int fd) +/* unfortunately, you can not stat debugfs or proc files for size */ +static void record_file(const char *file, size_t hdr_sz) { unsigned long long size = 0; - char buf[BUFSIZ]; - int r; - - do { - r = read(fd, buf, BUFSIZ); - if (r > 0) { - size += r; - write_or_die(buf, r); - } - } while (r > 0); - - return size; -} - -static unsigned long long copy_file(const char *file) -{ - unsigned long long size = 0; - int fd; + char buf[BUFSIZ], *sizep; + off_t hdr_pos = lseek(output_fd, 0, SEEK_CUR); + int r, fd; fd = open(file, O_RDONLY); if (fd < 0) die("Can't read '%s'", file); - size = copy_file_fd(fd); - close(fd); - - return size; -} -static unsigned long get_size_fd(int fd) -{ - unsigned long long size = 0; - char buf[BUFSIZ]; - int r; + /* put in zeros for file size, then fill true size later */ + if (hdr_sz) + write_or_die(&size, hdr_sz); do { r = read(fd, buf, BUFSIZ); - if (r > 0) + if (r > 0) { size += r; + write_or_die(buf, r); + } } while (r > 0); - - lseek(fd, 0, SEEK_SET); - - return size; -} - -static unsigned long get_size(const char *file) -{ - unsigned long long size = 0; - int fd; - - fd = open(file, O_RDONLY); - if (fd < 0) - die("Can't read '%s'", file); - size = get_size_fd(fd); close(fd); - return size; + /* ugh, handle big-endian hdr_size == 4 */ + sizep = (char*)&size; + if (bigendian()) + sizep += sizeof(u64) - hdr_sz; + + if (hdr_sz && pwrite(output_fd, sizep, hdr_sz, hdr_pos) < 0) + die("writing to %s", output_file); } static void read_header_files(void) { - unsigned long long size, check_size; char *path; - int fd; + struct stat st; path = get_tracing_file("events/header_page"); - fd = open(path, O_RDONLY); - if (fd < 0) + if (stat(path, &st) < 0) die("can't read '%s'", path); - /* unfortunately, you can not stat debugfs files for size */ - size = get_size_fd(fd); - write_or_die("header_page", 12); - write_or_die(&size, 8); - check_size = copy_file_fd(fd); - close(fd); - - if (size != check_size) - die("wrong size for '%s' size=%lld read=%lld", - path, size, check_size); + record_file(path, 8); put_tracing_file(path); path = get_tracing_file("events/header_event"); - fd = open(path, O_RDONLY); - if (fd < 0) + if (stat(path, &st) < 0) die("can't read '%s'", path); - size = get_size_fd(fd); - write_or_die("header_event", 13); - write_or_die(&size, 8); - check_size = copy_file_fd(fd); - if (size != check_size) - die("wrong size for '%s'", path); + record_file(path, 8); put_tracing_file(path); - close(fd); } static bool name_in_tp_list(char *sys, struct tracepoint_path *tps) @@ -298,7 +252,6 @@ static bool name_in_tp_list(char *sys, struct tracepoint_path *tps) static void copy_event_system(const char *sys, struct tracepoint_path *tps) { - unsigned long long size, check_size; struct dirent *dent; struct stat st; char *format; @@ -338,14 +291,8 @@ static void copy_event_system(const char *sys, struct tracepoint_path *tps) sprintf(format, "%s/%s/format", sys, dent->d_name); ret = stat(format, &st); - if (ret >= 0) { - /* unfortunately, you can not stat debugfs files for size */ - size = get_size(format); - write_or_die(&size, 8); - check_size = copy_file(format); - if (size != check_size) - die("error in size of file '%s'", format); - } + if (ret >= 0) + record_file(format, 8); free(format); } @@ -426,7 +373,7 @@ static void read_event_files(struct tracepoint_path *tps) static void read_proc_kallsyms(void) { - unsigned int size, check_size; + unsigned int size; const char *path = "/proc/kallsyms"; struct stat st; int ret; @@ -438,17 +385,12 @@ static void read_proc_kallsyms(void) write_or_die(&size, 4); return; } - size = get_size(path); - write_or_die(&size, 4); - check_size = copy_file(path); - if (size != check_size) - die("error in size of file '%s'", path); - + record_file(path, 4); } static void read_ftrace_printk(void) { - unsigned int size, check_size; + unsigned int size; char *path; struct stat st; int ret; @@ -461,11 +403,8 @@ static void read_ftrace_printk(void) write_or_die(&size, 4); goto out; } - size = get_size(path); - write_or_die(&size, 4); - check_size = copy_file(path); - if (size != check_size) - die("error in size of file '%s'", path); + record_file(path, 4); + out: put_tracing_file(path); } @@ -490,6 +429,19 @@ get_tracepoints_path(struct list_head *pattrs) return nr_tracepoints > 0 ? path.next : NULL; } +static void +put_tracepoints_path(struct tracepoint_path *tps) +{ + while (tps) { + struct tracepoint_path *t = tps; + + tps = tps->next; + free(t->name); + free(t->system); + free(t); + } +} + bool have_tracepoints(struct list_head *pattrs) { struct perf_evsel *pos; @@ -501,19 +453,11 @@ bool have_tracepoints(struct list_head *pattrs) return false; } -int read_tracing_data(int fd, struct list_head *pattrs) +static void tracing_data_header(void) { - char buf[BUFSIZ]; - struct tracepoint_path *tps = get_tracepoints_path(pattrs); - - /* - * What? No tracepoints? No sense writing anything here, bail out. - */ - if (tps == NULL) - return -1; - - output_fd = fd; + char buf[20]; + /* just guessing this is someone's birthday.. ;) */ buf[0] = 23; buf[1] = 8; buf[2] = 68; @@ -538,28 +482,86 @@ int read_tracing_data(int fd, struct list_head *pattrs) /* save page_size */ page_size = sysconf(_SC_PAGESIZE); write_or_die(&page_size, 4); +} + +struct tracing_data *tracing_data_get(struct list_head *pattrs, + int fd, bool temp) +{ + struct tracepoint_path *tps; + struct tracing_data *tdata; + + output_fd = fd; + tps = get_tracepoints_path(pattrs); + if (!tps) + return NULL; + + tdata = malloc_or_die(sizeof(*tdata)); + tdata->temp = temp; + tdata->size = 0; + + if (temp) { + int temp_fd; + + snprintf(tdata->temp_file, sizeof(tdata->temp_file), + "/tmp/perf-XXXXXX"); + if (!mkstemp(tdata->temp_file)) + die("Can't make temp file"); + + temp_fd = open(tdata->temp_file, O_RDWR); + if (temp_fd < 0) + die("Can't read '%s'", tdata->temp_file); + + /* + * Set the temp file the default output, so all the + * tracing data are stored into it. + */ + output_fd = temp_fd; + } + + tracing_data_header(); read_header_files(); read_ftrace_files(tps); read_event_files(tps); read_proc_kallsyms(); read_ftrace_printk(); - return 0; + /* + * All tracing data are stored by now, we can restore + * the default output file in case we used temp file. + */ + if (temp) { + tdata->size = lseek(output_fd, 0, SEEK_CUR); + close(output_fd); + output_fd = fd; + } + + put_tracepoints_path(tps); + return tdata; } -ssize_t read_tracing_data_size(int fd, struct list_head *pattrs) +void tracing_data_put(struct tracing_data *tdata) { - ssize_t size; - int err = 0; + if (tdata->temp) { + record_file(tdata->temp_file, 0); + unlink(tdata->temp_file); + } + + free(tdata); +} - calc_data_size = 1; - err = read_tracing_data(fd, pattrs); - size = calc_data_size - 1; - calc_data_size = 0; +int read_tracing_data(int fd, struct list_head *pattrs) +{ + struct tracing_data *tdata; - if (err < 0) - return err; + /* + * We work over the real file, so we can write data + * directly, no temp file is needed. + */ + tdata = tracing_data_get(pattrs, fd, false); + if (!tdata) + return -ENOMEM; - return size; + tracing_data_put(tdata); + return 0; } diff --git a/tools/perf/util/trace-event.h b/tools/perf/util/trace-event.h index f674dda3363b..a84100817649 100644 --- a/tools/perf/util/trace-event.h +++ b/tools/perf/util/trace-event.h @@ -263,7 +263,18 @@ void *raw_field_ptr(struct event *event, const char *name, void *data); unsigned long long eval_flag(const char *flag); int read_tracing_data(int fd, struct list_head *pattrs); -ssize_t read_tracing_data_size(int fd, struct list_head *pattrs); + +struct tracing_data { + /* size is only valid if temp is 'true' */ + ssize_t size; + bool temp; + char temp_file[50]; +}; + +struct tracing_data *tracing_data_get(struct list_head *pattrs, + int fd, bool temp); +void tracing_data_put(struct tracing_data *tdata); + /* taken from kernel/trace/trace.h */ enum trace_flag_type { diff --git a/tools/perf/util/ui/browser.c b/tools/perf/util/ui/browser.c index 611219f80680..556829124b02 100644 --- a/tools/perf/util/ui/browser.c +++ b/tools/perf/util/ui/browser.c @@ -1,5 +1,10 @@ +#include "../util.h" +#include "../cache.h" +#include "../../perf.h" #include "libslang.h" +#include <newt.h> #include "ui.h" +#include "util.h" #include <linux/compiler.h> #include <linux/list.h> #include <linux/rbtree.h> @@ -7,13 +12,13 @@ #include <sys/ttydefaults.h> #include "browser.h" #include "helpline.h" +#include "keysyms.h" #include "../color.h" -#include "../util.h" -#include <stdio.h> -static int ui_browser__percent_color(double percent, bool current) +static int ui_browser__percent_color(struct ui_browser *browser, + double percent, bool current) { - if (current) + if (current && (!browser->use_navkeypressed || browser->navkeypressed)) return HE_COLORSET_SELECTED; if (percent >= MIN_RED) return HE_COLORSET_TOP; @@ -30,7 +35,7 @@ void ui_browser__set_color(struct ui_browser *self __used, int color) void ui_browser__set_percent_color(struct ui_browser *self, double percent, bool current) { - int color = ui_browser__percent_color(percent, current); + int color = ui_browser__percent_color(self, percent, current); ui_browser__set_color(self, color); } @@ -39,31 +44,62 @@ void ui_browser__gotorc(struct ui_browser *self, int y, int x) SLsmg_gotorc(self->y + y, self->x + x); } +static struct list_head * +ui_browser__list_head_filter_entries(struct ui_browser *browser, + struct list_head *pos) +{ + do { + if (!browser->filter || !browser->filter(browser, pos)) + return pos; + pos = pos->next; + } while (pos != browser->entries); + + return NULL; +} + +static struct list_head * +ui_browser__list_head_filter_prev_entries(struct ui_browser *browser, + struct list_head *pos) +{ + do { + if (!browser->filter || !browser->filter(browser, pos)) + return pos; + pos = pos->prev; + } while (pos != browser->entries); + + return NULL; +} + void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whence) { struct list_head *head = self->entries; struct list_head *pos; + if (self->nr_entries == 0) + return; + switch (whence) { case SEEK_SET: - pos = head->next; + pos = ui_browser__list_head_filter_entries(self, head->next); break; case SEEK_CUR: pos = self->top; break; case SEEK_END: - pos = head->prev; + pos = ui_browser__list_head_filter_prev_entries(self, head->prev); break; default: return; } + assert(pos != NULL); + if (offset > 0) { while (offset-- != 0) - pos = pos->next; + pos = ui_browser__list_head_filter_entries(self, pos->next); } else { while (offset++ != 0) - pos = pos->prev; + pos = ui_browser__list_head_filter_prev_entries(self, pos->prev); } self->top = pos; @@ -127,41 +163,76 @@ bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row) void ui_browser__refresh_dimensions(struct ui_browser *self) { - int cols, rows; - newtGetScreenSize(&cols, &rows); - - self->width = cols - 1; - self->height = rows - 2; + self->width = SLtt_Screen_Cols - 1; + self->height = SLtt_Screen_Rows - 2; self->y = 1; self->x = 0; } -void ui_browser__reset_index(struct ui_browser *self) +void ui_browser__handle_resize(struct ui_browser *browser) { - self->index = self->top_idx = 0; - self->seek(self, 0, SEEK_SET); + ui__refresh_dimensions(false); + ui_browser__show(browser, browser->title, ui_helpline__current); + ui_browser__refresh(browser); } -void ui_browser__add_exit_key(struct ui_browser *self, int key) +int ui_browser__warning(struct ui_browser *browser, int timeout, + const char *format, ...) { - newtFormAddHotKey(self->form, key); + va_list args; + char *text; + int key = 0, err; + + va_start(args, format); + err = vasprintf(&text, format, args); + va_end(args); + + if (err < 0) { + va_start(args, format); + ui_helpline__vpush(format, args); + va_end(args); + } else { + while ((key == ui__question_window("Warning!", text, + "Press any key...", + timeout)) == K_RESIZE) + ui_browser__handle_resize(browser); + free(text); + } + + return key; } -void ui_browser__add_exit_keys(struct ui_browser *self, int keys[]) +int ui_browser__help_window(struct ui_browser *browser, const char *text) { - int i = 0; + int key; - while (keys[i] && i < 64) { - ui_browser__add_exit_key(self, keys[i]); - ++i; - } + while ((key = ui__help_window(text)) == K_RESIZE) + ui_browser__handle_resize(browser); + + return key; +} + +bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text) +{ + int key; + + while ((key = ui__dialog_yesno(text)) == K_RESIZE) + ui_browser__handle_resize(browser); + + return key == K_ENTER || toupper(key) == 'Y'; +} + +void ui_browser__reset_index(struct ui_browser *self) +{ + self->index = self->top_idx = 0; + self->seek(self, 0, SEEK_SET); } void __ui_browser__show_title(struct ui_browser *browser, const char *title) { SLsmg_gotorc(0, 0); ui_browser__set_color(browser, NEWT_COLORSET_ROOT); - slsmg_write_nstring(title, browser->width); + slsmg_write_nstring(title, browser->width + 1); } void ui_browser__show_title(struct ui_browser *browser, const char *title) @@ -174,78 +245,145 @@ void ui_browser__show_title(struct ui_browser *browser, const char *title) int ui_browser__show(struct ui_browser *self, const char *title, const char *helpline, ...) { + int err; va_list ap; - int keys[] = { NEWT_KEY_UP, NEWT_KEY_DOWN, NEWT_KEY_PGUP, - NEWT_KEY_PGDN, NEWT_KEY_HOME, NEWT_KEY_END, ' ', - NEWT_KEY_LEFT, NEWT_KEY_ESCAPE, 'q', CTRL('c'), 0 }; - - if (self->form != NULL) - newtFormDestroy(self->form); ui_browser__refresh_dimensions(self); - self->form = newtForm(NULL, NULL, 0); - if (self->form == NULL) - return -1; - - self->sb = newtVerticalScrollbar(self->width, 1, self->height, - HE_COLORSET_NORMAL, - HE_COLORSET_SELECTED); - if (self->sb == NULL) - return -1; pthread_mutex_lock(&ui__lock); __ui_browser__show_title(self, title); - ui_browser__add_exit_keys(self, keys); - newtFormAddComponent(self->form, self->sb); + self->title = title; + free(self->helpline); + self->helpline = NULL; va_start(ap, helpline); - ui_helpline__vpush(helpline, ap); + err = vasprintf(&self->helpline, helpline, ap); va_end(ap); + if (err > 0) + ui_helpline__push(self->helpline); pthread_mutex_unlock(&ui__lock); - return 0; + return err ? 0 : -1; } -void ui_browser__hide(struct ui_browser *self) +void ui_browser__hide(struct ui_browser *browser __used) { pthread_mutex_lock(&ui__lock); - newtFormDestroy(self->form); - self->form = NULL; ui_helpline__pop(); pthread_mutex_unlock(&ui__lock); } -int ui_browser__refresh(struct ui_browser *self) +static void ui_browser__scrollbar_set(struct ui_browser *browser) +{ + int height = browser->height, h = 0, pct = 0, + col = browser->width, + row = browser->y - 1; + + if (browser->nr_entries > 1) { + pct = ((browser->index * (browser->height - 1)) / + (browser->nr_entries - 1)); + } + + SLsmg_set_char_set(1); + + while (h < height) { + ui_browser__gotorc(browser, row++, col); + SLsmg_write_char(h == pct ? SLSMG_DIAMOND_CHAR : SLSMG_CKBRD_CHAR); + ++h; + } + + SLsmg_set_char_set(0); +} + +static int __ui_browser__refresh(struct ui_browser *browser) { int row; + int width = browser->width; + + row = browser->refresh(browser); + ui_browser__set_color(browser, HE_COLORSET_NORMAL); + + if (!browser->use_navkeypressed || browser->navkeypressed) + ui_browser__scrollbar_set(browser); + else + width += 1; + + SLsmg_fill_region(browser->y + row, browser->x, + browser->height - row, width, ' '); + + return 0; +} +int ui_browser__refresh(struct ui_browser *browser) +{ pthread_mutex_lock(&ui__lock); - newtScrollbarSet(self->sb, self->index, self->nr_entries - 1); - row = self->refresh(self); - ui_browser__set_color(self, HE_COLORSET_NORMAL); - SLsmg_fill_region(self->y + row, self->x, - self->height - row, self->width, ' '); + __ui_browser__refresh(browser); pthread_mutex_unlock(&ui__lock); return 0; } -int ui_browser__run(struct ui_browser *self) +/* + * Here we're updating nr_entries _after_ we started browsing, i.e. we have to + * forget about any reference to any entry in the underlying data structure, + * that is why we do a SEEK_SET. Think about 'perf top' in the hists browser + * after an output_resort and hist decay. + */ +void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries) { - struct newtExitStruct es; + off_t offset = nr_entries - browser->nr_entries; + + browser->nr_entries = nr_entries; - if (ui_browser__refresh(self) < 0) - return -1; + if (offset < 0) { + if (browser->top_idx < (u64)-offset) + offset = -browser->top_idx; + + browser->index += offset; + browser->top_idx += offset; + } + + browser->top = NULL; + browser->seek(browser, browser->top_idx, SEEK_SET); +} + +int ui_browser__run(struct ui_browser *self, int delay_secs) +{ + int err, key; while (1) { off_t offset; - newtFormRun(self->form, &es); - - if (es.reason != NEWT_EXIT_HOTKEY) + pthread_mutex_lock(&ui__lock); + err = __ui_browser__refresh(self); + SLsmg_refresh(); + pthread_mutex_unlock(&ui__lock); + if (err < 0) break; - switch (es.u.key) { - case NEWT_KEY_DOWN: + + key = ui__getch(delay_secs); + + if (key == K_RESIZE) { + ui__refresh_dimensions(false); + ui_browser__refresh_dimensions(self); + __ui_browser__show_title(self, self->title); + ui_helpline__puts(self->helpline); + continue; + } + + if (self->use_navkeypressed && !self->navkeypressed) { + if (key == K_DOWN || key == K_UP || + key == K_PGDN || key == K_PGUP || + key == K_HOME || key == K_END || + key == ' ') { + self->navkeypressed = true; + continue; + } else + return key; + } + + switch (key) { + case K_DOWN: if (self->index == self->nr_entries - 1) break; ++self->index; @@ -254,7 +392,7 @@ int ui_browser__run(struct ui_browser *self) self->seek(self, +1, SEEK_CUR); } break; - case NEWT_KEY_UP: + case K_UP: if (self->index == 0) break; --self->index; @@ -263,7 +401,7 @@ int ui_browser__run(struct ui_browser *self) self->seek(self, -1, SEEK_CUR); } break; - case NEWT_KEY_PGDN: + case K_PGDN: case ' ': if (self->top_idx + self->height > self->nr_entries - 1) break; @@ -275,7 +413,7 @@ int ui_browser__run(struct ui_browser *self) self->top_idx += offset; self->seek(self, +offset, SEEK_CUR); break; - case NEWT_KEY_PGUP: + case K_PGUP: if (self->top_idx == 0) break; @@ -288,10 +426,10 @@ int ui_browser__run(struct ui_browser *self) self->top_idx -= offset; self->seek(self, -offset, SEEK_CUR); break; - case NEWT_KEY_HOME: + case K_HOME: ui_browser__reset_index(self); break; - case NEWT_KEY_END: + case K_END: offset = self->height - 1; if (offset >= self->nr_entries) offset = self->nr_entries - 1; @@ -301,10 +439,8 @@ int ui_browser__run(struct ui_browser *self) self->seek(self, -offset, SEEK_END); break; default: - return es.u.key; + return key; } - if (ui_browser__refresh(self) < 0) - return -1; } return -1; } @@ -316,41 +452,146 @@ unsigned int ui_browser__list_head_refresh(struct ui_browser *self) int row = 0; if (self->top == NULL || self->top == self->entries) - self->top = head->next; + self->top = ui_browser__list_head_filter_entries(self, head->next); pos = self->top; list_for_each_from(pos, head) { - ui_browser__gotorc(self, row, 0); - self->write(self, pos, row); - if (++row == self->height) - break; + if (!self->filter || !self->filter(self, pos)) { + ui_browser__gotorc(self, row, 0); + self->write(self, pos, row); + if (++row == self->height) + break; + } } return row; } -static struct newtPercentTreeColors { - const char *topColorFg, *topColorBg; - const char *mediumColorFg, *mediumColorBg; - const char *normalColorFg, *normalColorBg; - const char *selColorFg, *selColorBg; - const char *codeColorFg, *codeColorBg; -} defaultPercentTreeColors = { - "red", "lightgray", - "green", "lightgray", - "black", "lightgray", - "lightgray", "magenta", - "blue", "lightgray", +static struct ui_browser__colorset { + const char *name, *fg, *bg; + int colorset; +} ui_browser__colorsets[] = { + { + .colorset = HE_COLORSET_TOP, + .name = "top", + .fg = "red", + .bg = "default", + }, + { + .colorset = HE_COLORSET_MEDIUM, + .name = "medium", + .fg = "green", + .bg = "default", + }, + { + .colorset = HE_COLORSET_NORMAL, + .name = "normal", + .fg = "default", + .bg = "default", + }, + { + .colorset = HE_COLORSET_SELECTED, + .name = "selected", + .fg = "black", + .bg = "lightgray", + }, + { + .colorset = HE_COLORSET_CODE, + .name = "code", + .fg = "blue", + .bg = "default", + }, + { + .name = NULL, + } }; + +static int ui_browser__color_config(const char *var, const char *value, + void *data __used) +{ + char *fg = NULL, *bg; + int i; + + /* same dir for all commands */ + if (prefixcmp(var, "colors.") != 0) + return 0; + + for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) { + const char *name = var + 7; + + if (strcmp(ui_browser__colorsets[i].name, name) != 0) + continue; + + fg = strdup(value); + if (fg == NULL) + break; + + bg = strchr(fg, ','); + if (bg == NULL) + break; + + *bg = '\0'; + while (isspace(*++bg)); + ui_browser__colorsets[i].bg = bg; + ui_browser__colorsets[i].fg = fg; + return 0; + } + + free(fg); + return -1; +} + +void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence) +{ + switch (whence) { + case SEEK_SET: + browser->top = browser->entries; + break; + case SEEK_CUR: + browser->top = browser->top + browser->top_idx + offset; + break; + case SEEK_END: + browser->top = browser->top + browser->nr_entries + offset; + break; + default: + return; + } +} + +unsigned int ui_browser__argv_refresh(struct ui_browser *browser) +{ + unsigned int row = 0, idx = browser->top_idx; + char **pos; + + if (browser->top == NULL) + browser->top = browser->entries; + + pos = (char **)browser->top; + while (idx < browser->nr_entries) { + if (!browser->filter || !browser->filter(browser, *pos)) { + ui_browser__gotorc(browser, row, 0); + browser->write(browser, pos, row); + if (++row == browser->height) + break; + } + + ++idx; + ++pos; + } + + return row; +} + void ui_browser__init(void) { - struct newtPercentTreeColors *c = &defaultPercentTreeColors; + int i = 0; + + perf_config(ui_browser__color_config, NULL); - sltt_set_color(HE_COLORSET_TOP, NULL, c->topColorFg, c->topColorBg); - sltt_set_color(HE_COLORSET_MEDIUM, NULL, c->mediumColorFg, c->mediumColorBg); - sltt_set_color(HE_COLORSET_NORMAL, NULL, c->normalColorFg, c->normalColorBg); - sltt_set_color(HE_COLORSET_SELECTED, NULL, c->selColorFg, c->selColorBg); - sltt_set_color(HE_COLORSET_CODE, NULL, c->codeColorFg, c->codeColorBg); + while (ui_browser__colorsets[i].name) { + struct ui_browser__colorset *c = &ui_browser__colorsets[i++]; + sltt_set_color(c->colorset, c->name, c->fg, c->bg); + } } diff --git a/tools/perf/util/ui/browser.h b/tools/perf/util/ui/browser.h index fc63dda10910..84d761b730c1 100644 --- a/tools/perf/util/ui/browser.h +++ b/tools/perf/util/ui/browser.h @@ -2,7 +2,6 @@ #define _PERF_UI_BROWSER_H_ 1 #include <stdbool.h> -#include <newt.h> #include <sys/types.h> #include "../types.h" @@ -13,15 +12,19 @@ #define HE_COLORSET_CODE 54 struct ui_browser { - newtComponent form, sb; u64 index, top_idx; void *top, *entries; u16 y, x, width, height; void *priv; + const char *title; + char *helpline; unsigned int (*refresh)(struct ui_browser *self); void (*write)(struct ui_browser *self, void *entry, int row); void (*seek)(struct ui_browser *self, off_t offset, int whence); + bool (*filter)(struct ui_browser *self, void *entry); u32 nr_entries; + bool navkeypressed; + bool use_navkeypressed; }; void ui_browser__set_color(struct ui_browser *self, int color); @@ -32,15 +35,23 @@ void ui_browser__refresh_dimensions(struct ui_browser *self); void ui_browser__reset_index(struct ui_browser *self); void ui_browser__gotorc(struct ui_browser *self, int y, int x); -void ui_browser__add_exit_key(struct ui_browser *self, int key); -void ui_browser__add_exit_keys(struct ui_browser *self, int keys[]); void __ui_browser__show_title(struct ui_browser *browser, const char *title); void ui_browser__show_title(struct ui_browser *browser, const char *title); int ui_browser__show(struct ui_browser *self, const char *title, const char *helpline, ...); void ui_browser__hide(struct ui_browser *self); int ui_browser__refresh(struct ui_browser *self); -int ui_browser__run(struct ui_browser *self); +int ui_browser__run(struct ui_browser *browser, int delay_secs); +void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries); +void ui_browser__handle_resize(struct ui_browser *browser); + +int ui_browser__warning(struct ui_browser *browser, int timeout, + const char *format, ...); +int ui_browser__help_window(struct ui_browser *browser, const char *text); +bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text); + +void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence); +unsigned int ui_browser__argv_refresh(struct ui_browser *browser); void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence); unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self); diff --git a/tools/perf/util/ui/browsers/annotate.c b/tools/perf/util/ui/browsers/annotate.c index 0229723aceb3..0575905d1205 100644 --- a/tools/perf/util/ui/browsers/annotate.c +++ b/tools/perf/util/ui/browsers/annotate.c @@ -1,31 +1,31 @@ +#include "../../util.h" #include "../browser.h" #include "../helpline.h" #include "../libslang.h" +#include "../ui.h" +#include "../util.h" #include "../../annotate.h" #include "../../hist.h" #include "../../sort.h" #include "../../symbol.h" #include <pthread.h> - -static void ui__error_window(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - newtWinMessagev((char *)"Error", (char *)"Ok", (char *)fmt, ap); - va_end(ap); -} +#include <newt.h> struct annotate_browser { struct ui_browser b; struct rb_root entries; struct rb_node *curr_hot; + struct objdump_line *selection; + int nr_asm_entries; + int nr_entries; + bool hide_src_code; }; struct objdump_line_rb_node { struct rb_node rb_node; double percent; u32 idx; + int idx_asm; }; static inline @@ -34,9 +34,22 @@ struct objdump_line_rb_node *objdump_line__rb(struct objdump_line *self) return (struct objdump_line_rb_node *)(self + 1); } +static bool objdump_line__filter(struct ui_browser *browser, void *entry) +{ + struct annotate_browser *ab = container_of(browser, struct annotate_browser, b); + + if (ab->hide_src_code) { + struct objdump_line *ol = list_entry(entry, struct objdump_line, node); + return ol->offset == -1; + } + + return false; +} + static void annotate_browser__write(struct ui_browser *self, void *entry, int row) { - struct objdump_line *ol = rb_entry(entry, struct objdump_line, node); + struct annotate_browser *ab = container_of(self, struct annotate_browser, b); + struct objdump_line *ol = list_entry(entry, struct objdump_line, node); bool current_entry = ui_browser__is_current_entry(self, row); int width = self->width; @@ -51,6 +64,11 @@ static void annotate_browser__write(struct ui_browser *self, void *entry, int ro SLsmg_write_char(':'); slsmg_write_nstring(" ", 8); + + /* The scroll bar isn't being used */ + if (!self->navkeypressed) + width += 1; + if (!*ol->line) slsmg_write_nstring(" ", width - 18); else @@ -58,6 +76,8 @@ static void annotate_browser__write(struct ui_browser *self, void *entry, int ro if (!current_entry) ui_browser__set_color(self, HE_COLORSET_CODE); + else + ab->selection = ol; } static double objdump_line__calc_percent(struct objdump_line *self, @@ -141,7 +161,8 @@ static void annotate_browser__set_top(struct annotate_browser *self, static void annotate_browser__calc_percent(struct annotate_browser *browser, int evidx) { - struct symbol *sym = browser->b.priv; + struct map_symbol *ms = browser->b.priv; + struct symbol *sym = ms->sym; struct annotation *notes = symbol__annotation(sym); struct objdump_line *pos; @@ -163,25 +184,60 @@ static void annotate_browser__calc_percent(struct annotate_browser *browser, browser->curr_hot = rb_last(&browser->entries); } +static bool annotate_browser__toggle_source(struct annotate_browser *browser) +{ + struct objdump_line *ol; + struct objdump_line_rb_node *olrb; + off_t offset = browser->b.index - browser->b.top_idx; + + browser->b.seek(&browser->b, offset, SEEK_CUR); + ol = list_entry(browser->b.top, struct objdump_line, node); + olrb = objdump_line__rb(ol); + + if (browser->hide_src_code) { + if (olrb->idx_asm < offset) + offset = olrb->idx; + + browser->b.nr_entries = browser->nr_entries; + browser->hide_src_code = false; + browser->b.seek(&browser->b, -offset, SEEK_CUR); + browser->b.top_idx = olrb->idx - offset; + browser->b.index = olrb->idx; + } else { + if (olrb->idx_asm < 0) { + ui_helpline__puts("Only available for assembly lines."); + browser->b.seek(&browser->b, -offset, SEEK_CUR); + return false; + } + + if (olrb->idx_asm < offset) + offset = olrb->idx_asm; + + browser->b.nr_entries = browser->nr_asm_entries; + browser->hide_src_code = true; + browser->b.seek(&browser->b, -offset, SEEK_CUR); + browser->b.top_idx = olrb->idx_asm - offset; + browser->b.index = olrb->idx_asm; + } + + return true; +} + static int annotate_browser__run(struct annotate_browser *self, int evidx, - int refresh) + int nr_events, void(*timer)(void *arg), + void *arg, int delay_secs) { struct rb_node *nd = NULL; - struct symbol *sym = self->b.priv; - /* - * RIGHT To allow builtin-annotate to cycle thru multiple symbols by - * examining the exit key for this function. - */ - int exit_keys[] = { 'H', NEWT_KEY_TAB, NEWT_KEY_UNTAB, - NEWT_KEY_RIGHT, 0 }; + struct map_symbol *ms = self->b.priv; + struct symbol *sym = ms->sym; + const char *help = "<-, ESC: exit, TAB/shift+TAB: cycle hottest lines, " + "H: Hottest, -> Line action, S -> Toggle source " + "code view"; int key; - if (ui_browser__show(&self->b, sym->name, - "<-, -> or ESC: exit, TAB/shift+TAB: " - "cycle hottest lines, H: Hottest") < 0) + if (ui_browser__show(&self->b, sym->name, help) < 0) return -1; - ui_browser__add_exit_keys(&self->b, exit_keys); annotate_browser__calc_percent(self, evidx); if (self->curr_hot) @@ -189,13 +245,10 @@ static int annotate_browser__run(struct annotate_browser *self, int evidx, nd = self->curr_hot; - if (refresh != 0) - newtFormSetTimer(self->b.form, refresh); - while (1) { - key = ui_browser__run(&self->b); + key = ui_browser__run(&self->b, delay_secs); - if (refresh != 0) { + if (delay_secs != 0) { annotate_browser__calc_percent(self, evidx); /* * Current line focus got out of the list of most active @@ -207,15 +260,14 @@ static int annotate_browser__run(struct annotate_browser *self, int evidx, } switch (key) { - case -1: - /* - * FIXME we need to check if it was - * es.reason == NEWT_EXIT_TIMER - */ - if (refresh != 0) + case K_TIMER: + if (timer != NULL) + timer(arg); + + if (delay_secs != 0) symbol__annotate_decay_histogram(sym, evidx); continue; - case NEWT_KEY_TAB: + case K_TAB: if (nd != NULL) { nd = rb_prev(nd); if (nd == NULL) @@ -223,7 +275,7 @@ static int annotate_browser__run(struct annotate_browser *self, int evidx, } else nd = self->curr_hot; break; - case NEWT_KEY_UNTAB: + case K_UNTAB: if (nd != NULL) nd = rb_next(nd); if (nd == NULL) @@ -234,8 +286,68 @@ static int annotate_browser__run(struct annotate_browser *self, int evidx, case 'H': nd = self->curr_hot; break; - default: + case 'S': + if (annotate_browser__toggle_source(self)) + ui_helpline__puts(help); + continue; + case K_ENTER: + case K_RIGHT: + if (self->selection == NULL) { + ui_helpline__puts("Huh? No selection. Report to linux-kernel@vger.kernel.org"); + continue; + } + + if (self->selection->offset == -1) { + ui_helpline__puts("Actions are only available for assembly lines."); + continue; + } else { + char *s = strstr(self->selection->line, "callq "); + struct annotation *notes; + struct symbol *target; + u64 ip; + + if (s == NULL) { + ui_helpline__puts("Actions are only available for the 'callq' instruction."); + continue; + } + + s = strchr(s, ' '); + if (s++ == NULL) { + ui_helpline__puts("Invallid callq instruction."); + continue; + } + + ip = strtoull(s, NULL, 16); + ip = ms->map->map_ip(ms->map, ip); + target = map__find_symbol(ms->map, ip, NULL); + if (target == NULL) { + ui_helpline__puts("The called function was not found."); + continue; + } + + notes = symbol__annotation(target); + pthread_mutex_lock(¬es->lock); + + if (notes->src == NULL && + symbol__alloc_hist(target, nr_events) < 0) { + pthread_mutex_unlock(¬es->lock); + ui__warning("Not enough memory for annotating '%s' symbol!\n", + target->name); + continue; + } + + pthread_mutex_unlock(¬es->lock); + symbol__tui_annotate(target, ms->map, evidx, nr_events, + timer, arg, delay_secs); + } + continue; + case K_LEFT: + case K_ESC: + case 'q': + case CTRL('c'): goto out; + default: + continue; } if (nd != NULL) @@ -246,22 +358,31 @@ out: return key; } -int hist_entry__tui_annotate(struct hist_entry *he, int evidx) +int hist_entry__tui_annotate(struct hist_entry *he, int evidx, int nr_events, + void(*timer)(void *arg), void *arg, int delay_secs) { - return symbol__tui_annotate(he->ms.sym, he->ms.map, evidx, 0); + return symbol__tui_annotate(he->ms.sym, he->ms.map, evidx, nr_events, + timer, arg, delay_secs); } int symbol__tui_annotate(struct symbol *sym, struct map *map, int evidx, - int refresh) + int nr_events, void(*timer)(void *arg), void *arg, + int delay_secs) { struct objdump_line *pos, *n; struct annotation *notes; + struct map_symbol ms = { + .map = map, + .sym = sym, + }; struct annotate_browser browser = { .b = { .refresh = ui_browser__list_head_refresh, .seek = ui_browser__list_head_seek, .write = annotate_browser__write, - .priv = sym, + .filter = objdump_line__filter, + .priv = &ms, + .use_navkeypressed = true, }, }; int ret; @@ -273,7 +394,7 @@ int symbol__tui_annotate(struct symbol *sym, struct map *map, int evidx, return -1; if (symbol__annotate(sym, map, sizeof(struct objdump_line_rb_node)) < 0) { - ui__error_window(ui_helpline__last_msg); + ui__error("%s", ui_helpline__last_msg); return -1; } @@ -288,12 +409,18 @@ int symbol__tui_annotate(struct symbol *sym, struct map *map, int evidx, if (browser.b.width < line_len) browser.b.width = line_len; rbpos = objdump_line__rb(pos); - rbpos->idx = browser.b.nr_entries++; + rbpos->idx = browser.nr_entries++; + if (pos->offset != -1) + rbpos->idx_asm = browser.nr_asm_entries++; + else + rbpos->idx_asm = -1; } + browser.b.nr_entries = browser.nr_entries; browser.b.entries = ¬es->src->source, browser.b.width += 18; /* Percentage */ - ret = annotate_browser__run(&browser, evidx, refresh); + ret = annotate_browser__run(&browser, evidx, nr_events, + timer, arg, delay_secs); list_for_each_entry_safe(pos, n, ¬es->src->source, node) { list_del(&pos->node); objdump_line__free(pos); diff --git a/tools/perf/util/ui/browsers/hists.c b/tools/perf/util/ui/browsers/hists.c index 5d767c622dfc..d0c94b459685 100644 --- a/tools/perf/util/ui/browsers/hists.c +++ b/tools/perf/util/ui/browsers/hists.c @@ -17,6 +17,7 @@ #include "../browser.h" #include "../helpline.h" #include "../util.h" +#include "../ui.h" #include "map.h" struct hist_browser { @@ -24,8 +25,12 @@ struct hist_browser { struct hists *hists; struct hist_entry *he_selection; struct map_symbol *selection; + bool has_symbols; }; +static int hists__browser_title(struct hists *self, char *bf, size_t size, + const char *ev_name); + static void hist_browser__refresh_dimensions(struct hist_browser *self) { /* 3 == +/- toggle symbol before actual hist_entry rendering */ @@ -290,28 +295,49 @@ static void hist_browser__set_folding(struct hist_browser *self, bool unfold) ui_browser__reset_index(&self->b); } -static int hist_browser__run(struct hist_browser *self, const char *title) +static void ui_browser__warn_lost_events(struct ui_browser *browser) +{ + ui_browser__warning(browser, 4, + "Events are being lost, check IO/CPU overload!\n\n" + "You may want to run 'perf' using a RT scheduler policy:\n\n" + " perf top -r 80\n\n" + "Or reduce the sampling frequency."); +} + +static int hist_browser__run(struct hist_browser *self, const char *ev_name, + void(*timer)(void *arg), void *arg, int delay_secs) { int key; - int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't', - NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT, - NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0, }; + char title[160]; self->b.entries = &self->hists->entries; self->b.nr_entries = self->hists->nr_entries; hist_browser__refresh_dimensions(self); + hists__browser_title(self->hists, title, sizeof(title), ev_name); if (ui_browser__show(&self->b, title, "Press '?' for help on key bindings") < 0) return -1; - ui_browser__add_exit_keys(&self->b, exit_keys); - while (1) { - key = ui_browser__run(&self->b); + key = ui_browser__run(&self->b, delay_secs); switch (key) { + case K_TIMER: + timer(arg); + ui_browser__update_nr_entries(&self->b, self->hists->nr_entries); + + if (self->hists->stats.nr_lost_warned != + self->hists->stats.nr_events[PERF_RECORD_LOST]) { + self->hists->stats.nr_lost_warned = + self->hists->stats.nr_events[PERF_RECORD_LOST]; + ui_browser__warn_lost_events(&self->b); + } + + hists__browser_title(self->hists, title, sizeof(title), ev_name); + ui_browser__show_title(&self->b, title); + continue; case 'D': { /* Debug */ static int seq; struct hist_entry *h = rb_entry(self->b.top, @@ -334,7 +360,7 @@ static int hist_browser__run(struct hist_browser *self, const char *title) /* Expand the whole world. */ hist_browser__set_folding(self, true); break; - case NEWT_KEY_ENTER: + case K_ENTER: if (hist_browser__toggle_fold(self)) break; /* fall thru */ @@ -532,7 +558,7 @@ static int hist_browser__show_entry(struct hist_browser *self, char s[256]; double percent; int printed = 0; - int color, width = self->b.width; + int width = self->b.width - 6; /* The percentage */ char folded_sign = ' '; bool current_entry = ui_browser__is_current_entry(&self->b, row); off_t row_offset = entry->row_offset; @@ -548,26 +574,35 @@ static int hist_browser__show_entry(struct hist_browser *self, } if (row_offset == 0) { - hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false, - 0, false, self->hists->stats.total_period); + hist_entry__snprintf(entry, s, sizeof(s), self->hists); 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; - } - - ui_browser__set_color(&self->b, color); + ui_browser__set_percent_color(&self->b, percent, current_entry); ui_browser__gotorc(&self->b, row, 0); if (symbol_conf.use_callchain) { slsmg_printf("%c ", folded_sign); width -= 2; } + + slsmg_printf(" %5.2f%%", percent); + + /* The scroll bar isn't being used */ + if (!self->b.navkeypressed) + width += 1; + + if (!current_entry || !self->b.navkeypressed) + ui_browser__set_color(&self->b, HE_COLORSET_NORMAL); + + if (symbol_conf.show_nr_samples) { + slsmg_printf(" %11u", entry->nr_events); + width -= 12; + } + + if (symbol_conf.show_total_period) { + slsmg_printf(" %12" PRIu64, entry->period); + width -= 13; + } + slsmg_write_nstring(s, width); ++row; ++printed; @@ -585,14 +620,23 @@ static int hist_browser__show_entry(struct hist_browser *self, return printed; } +static void ui_browser__hists_init_top(struct ui_browser *browser) +{ + if (browser->top == NULL) { + struct hist_browser *hb; + + hb = container_of(browser, struct hist_browser, b); + browser->top = rb_first(&hb->hists->entries); + } +} + static unsigned int hist_browser__refresh(struct ui_browser *self) { unsigned row = 0; struct rb_node *nd; struct hist_browser *hb = container_of(self, struct hist_browser, b); - if (self->top == NULL) - self->top = rb_first(&hb->hists->entries); + ui_browser__hists_init_top(self); for (nd = self->top; nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); @@ -644,6 +688,8 @@ static void ui_browser__hists_seek(struct ui_browser *self, if (self->nr_entries == 0) return; + ui_browser__hists_init_top(self); + switch (whence) { case SEEK_SET: nd = hists__filter_entries(rb_first(self->entries)); @@ -761,6 +807,8 @@ static struct hist_browser *hist_browser__new(struct hists *hists) self->hists = hists; self->b.refresh = hist_browser__refresh; self->b.seek = ui_browser__hists_seek; + self->b.use_navkeypressed = true, + self->has_symbols = sort_sym.list.next != NULL; } return self; @@ -782,11 +830,12 @@ static struct thread *hist_browser__selected_thread(struct hist_browser *self) } static int hists__browser_title(struct hists *self, char *bf, size_t size, - const char *ev_name, const struct dso *dso, - const struct thread *thread) + const char *ev_name) { char unit; int printed; + const struct dso *dso = self->dso_filter; + const struct thread *thread = self->thread_filter; unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE]; nr_events = convert_unit(nr_events, &unit); @@ -803,16 +852,15 @@ static int hists__browser_title(struct hists *self, char *bf, size_t size, return printed; } -static int perf_evsel__hists_browse(struct perf_evsel *evsel, +static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, const char *helpline, const char *ev_name, - bool left_exits) + bool left_exits, + void(*timer)(void *arg), void *arg, + int delay_secs) { struct hists *self = &evsel->hists; struct hist_browser *browser = hist_browser__new(self); struct pstack *fstack; - const struct thread *thread_filter = NULL; - const struct dso *dso_filter = NULL; - char msg[160]; int key = -1; if (browser == NULL) @@ -824,8 +872,6 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, ui_helpline__push(helpline); - hists__browser_title(self, msg, sizeof(msg), ev_name, - dso_filter, thread_filter); while (1) { const struct thread *thread = NULL; const struct dso *dso = NULL; @@ -834,7 +880,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, annotate = -2, zoom_dso = -2, zoom_thread = -2, browse_map = -2; - key = hist_browser__run(browser, msg); + key = hist_browser__run(browser, ev_name, timer, arg, delay_secs); if (browser->he_selection != NULL) { thread = hist_browser__selected_thread(browser); @@ -842,14 +888,23 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, } switch (key) { - case NEWT_KEY_TAB: - case NEWT_KEY_UNTAB: + case K_TAB: + case K_UNTAB: + if (nr_events == 1) + continue; /* * Exit the browser, let hists__browser_tree * go to the next or previous */ goto out_free_stack; case 'a': + if (!browser->has_symbols) { + ui_browser__warning(&browser->b, delay_secs * 2, + "Annotation is only available for symbolic views, " + "include \"sym\" in --sort to use it."); + continue; + } + if (browser->selection == NULL || browser->selection->sym == NULL || browser->selection->map->dso->annotate_warned) @@ -859,25 +914,30 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, goto zoom_dso; case 't': goto zoom_thread; - case NEWT_KEY_F1: + case K_F1: case 'h': case '?': - 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" - "C Collapse all callchains\n" - "E Expand all callchains\n" - "d Zoom into current DSO\n" - "t Zoom into current Thread\n" - "TAB/UNTAB Switch events\n" - "q/CTRL+C Exit browser"); + ui_browser__help_window(&browser->b, + "h/?/F1 Show this window\n" + "UP/DOWN/PGUP\n" + "PGDN/SPACE Navigate\n" + "q/ESC/CTRL+C Exit browser\n\n" + "For multiple event sessions:\n\n" + "TAB/UNTAB Switch events\n\n" + "For symbolic views (--sort has sym):\n\n" + "-> Zoom into DSO/Threads & Annotate current symbol\n" + "<- Zoom out\n" + "a Annotate current symbol\n" + "C Collapse all callchains\n" + "E Expand all callchains\n" + "d Zoom into current DSO\n" + "t Zoom into current Thread"); continue; - case NEWT_KEY_ENTER: - case NEWT_KEY_RIGHT: + case K_ENTER: + case K_RIGHT: /* menu */ break; - case NEWT_KEY_LEFT: { + case K_LEFT: { const void *top; if (pstack__empty(fstack)) { @@ -889,21 +949,28 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, continue; } top = pstack__pop(fstack); - if (top == &dso_filter) + if (top == &browser->hists->dso_filter) goto zoom_out_dso; - if (top == &thread_filter) + if (top == &browser->hists->thread_filter) goto zoom_out_thread; continue; } - case NEWT_KEY_ESCAPE: + case K_ESC: if (!left_exits && - !ui__dialog_yesno("Do you really want to exit?")) + !ui_browser__dialog_yesno(&browser->b, + "Do you really want to exit?")) continue; /* Fall thru */ - default: + case 'q': + case CTRL('c'): goto out_free_stack; + default: + continue; } + if (!browser->has_symbols) + goto add_exit_option; + if (browser->selection != NULL && browser->selection->sym != NULL && !browser->selection->map->dso->annotate_warned && @@ -913,14 +980,14 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, if (thread != NULL && asprintf(&options[nr_options], "Zoom %s %s(%d) thread", - (thread_filter ? "out of" : "into"), + (browser->hists->thread_filter ? "out of" : "into"), (thread->comm_set ? thread->comm : ""), thread->pid) > 0) zoom_thread = nr_options++; if (dso != NULL && asprintf(&options[nr_options], "Zoom %s %s DSO", - (dso_filter ? "out of" : "into"), + (browser->hists->dso_filter ? "out of" : "into"), (dso->kernel ? "the Kernel" : dso->short_name)) > 0) zoom_dso = nr_options++; @@ -928,7 +995,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, browser->selection->map != NULL && asprintf(&options[nr_options], "Browse map details") > 0) browse_map = nr_options++; - +add_exit_option: options[nr_options++] = (char *)"Exit"; choice = ui__popup_menu(nr_options, options); @@ -944,50 +1011,59 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, if (choice == annotate) { struct hist_entry *he; + int err; do_annotate: he = hist_browser__selected_entry(browser); if (he == NULL) continue; - - hist_entry__tui_annotate(he, evsel->idx); + /* + * Don't let this be freed, say, by hists__decay_entry. + */ + he->used = true; + err = hist_entry__tui_annotate(he, evsel->idx, nr_events, + timer, arg, delay_secs); + he->used = false; + ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); + if (err) + ui_browser__handle_resize(&browser->b); } else if (choice == browse_map) map__browse(browser->selection->map); else if (choice == zoom_dso) { zoom_dso: - if (dso_filter) { - pstack__remove(fstack, &dso_filter); + if (browser->hists->dso_filter) { + pstack__remove(fstack, &browser->hists->dso_filter); zoom_out_dso: ui_helpline__pop(); - dso_filter = NULL; + browser->hists->dso_filter = NULL; + sort_dso.elide = false; } else { if (dso == NULL) continue; ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", dso->kernel ? "the Kernel" : dso->short_name); - dso_filter = dso; - pstack__push(fstack, &dso_filter); + browser->hists->dso_filter = dso; + sort_dso.elide = true; + pstack__push(fstack, &browser->hists->dso_filter); } - hists__filter_by_dso(self, dso_filter); - hists__browser_title(self, msg, sizeof(msg), ev_name, - dso_filter, thread_filter); + hists__filter_by_dso(self); hist_browser__reset(browser); } else if (choice == zoom_thread) { zoom_thread: - if (thread_filter) { - pstack__remove(fstack, &thread_filter); + if (browser->hists->thread_filter) { + pstack__remove(fstack, &browser->hists->thread_filter); zoom_out_thread: ui_helpline__pop(); - thread_filter = NULL; + browser->hists->thread_filter = NULL; + sort_thread.elide = false; } else { ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", thread->comm_set ? thread->comm : "", thread->pid); - thread_filter = thread; - pstack__push(fstack, &thread_filter); + browser->hists->thread_filter = thread; + sort_thread.elide = true; + pstack__push(fstack, &browser->hists->thread_filter); } - hists__filter_by_thread(self, thread_filter); - hists__browser_title(self, msg, sizeof(msg), ev_name, - dso_filter, thread_filter); + hists__filter_by_thread(self); hist_browser__reset(browser); } } @@ -1001,6 +1077,7 @@ out: struct perf_evsel_menu { struct ui_browser b; struct perf_evsel *selection; + bool lost_events, lost_events_warned; }; static void perf_evsel_menu__write(struct ui_browser *browser, @@ -1013,22 +1090,38 @@ static void perf_evsel_menu__write(struct ui_browser *browser, unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE]; const char *ev_name = event_name(evsel); char bf[256], unit; + const char *warn = " "; + size_t printed; ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : HE_COLORSET_NORMAL); nr_events = convert_unit(nr_events, &unit); - snprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, - unit, unit == ' ' ? "" : " ", ev_name); - slsmg_write_nstring(bf, browser->width); + printed = snprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, + unit, unit == ' ' ? "" : " ", ev_name); + slsmg_printf("%s", bf); + + nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST]; + if (nr_events != 0) { + menu->lost_events = true; + if (!current_entry) + ui_browser__set_color(browser, HE_COLORSET_TOP); + nr_events = convert_unit(nr_events, &unit); + snprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!", nr_events, + unit, unit == ' ' ? "" : " "); + warn = bf; + } + + slsmg_write_nstring(warn, browser->width - printed); if (current_entry) menu->selection = evsel; } -static int perf_evsel_menu__run(struct perf_evsel_menu *menu, const char *help) +static int perf_evsel_menu__run(struct perf_evsel_menu *menu, + int nr_events, const char *help, + void(*timer)(void *arg), void *arg, int delay_secs) { - int exit_keys[] = { NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, }; struct perf_evlist *evlist = menu->b.priv; struct perf_evsel *pos; const char *ev_name, *title = "Available samples"; @@ -1038,50 +1131,72 @@ static int perf_evsel_menu__run(struct perf_evsel_menu *menu, const char *help) "ESC: exit, ENTER|->: Browse histograms") < 0) return -1; - ui_browser__add_exit_keys(&menu->b, exit_keys); - while (1) { - key = ui_browser__run(&menu->b); + key = ui_browser__run(&menu->b, delay_secs); switch (key) { - case NEWT_KEY_RIGHT: - case NEWT_KEY_ENTER: + case K_TIMER: + timer(arg); + + if (!menu->lost_events_warned && menu->lost_events) { + ui_browser__warn_lost_events(&menu->b); + menu->lost_events_warned = true; + } + continue; + case K_RIGHT: + case K_ENTER: if (!menu->selection) continue; pos = menu->selection; browse_hists: + perf_evlist__set_selected(evlist, pos); + /* + * Give the calling tool a chance to populate the non + * default evsel resorted hists tree. + */ + if (timer) + timer(arg); ev_name = event_name(pos); - key = perf_evsel__hists_browse(pos, help, ev_name, true); + key = perf_evsel__hists_browse(pos, nr_events, help, + ev_name, true, timer, + arg, delay_secs); ui_browser__show_title(&menu->b, title); - break; - case NEWT_KEY_LEFT: + switch (key) { + case K_TAB: + if (pos->node.next == &evlist->entries) + pos = list_entry(evlist->entries.next, struct perf_evsel, node); + else + pos = list_entry(pos->node.next, struct perf_evsel, node); + goto browse_hists; + case K_UNTAB: + if (pos->node.prev == &evlist->entries) + pos = list_entry(evlist->entries.prev, struct perf_evsel, node); + else + pos = list_entry(pos->node.prev, struct perf_evsel, node); + goto browse_hists; + case K_ESC: + if (!ui_browser__dialog_yesno(&menu->b, + "Do you really want to exit?")) + continue; + /* Fall thru */ + case 'q': + case CTRL('c'): + goto out; + default: + continue; + } + case K_LEFT: continue; - case NEWT_KEY_ESCAPE: - if (!ui__dialog_yesno("Do you really want to exit?")) + case K_ESC: + if (!ui_browser__dialog_yesno(&menu->b, + "Do you really want to exit?")) continue; /* Fall thru */ - default: - goto out; - } - - switch (key) { - case NEWT_KEY_TAB: - if (pos->node.next == &evlist->entries) - pos = list_entry(evlist->entries.next, struct perf_evsel, node); - else - pos = list_entry(pos->node.next, struct perf_evsel, node); - goto browse_hists; - case NEWT_KEY_UNTAB: - if (pos->node.prev == &evlist->entries) - pos = list_entry(evlist->entries.prev, struct perf_evsel, node); - else - pos = list_entry(pos->node.prev, struct perf_evsel, node); - goto browse_hists; case 'q': case CTRL('c'): goto out; default: - break; + continue; } } @@ -1091,7 +1206,9 @@ out: } static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist, - const char *help) + const char *help, + void(*timer)(void *arg), void *arg, + int delay_secs) { struct perf_evsel *pos; struct perf_evsel_menu menu = { @@ -1121,18 +1238,24 @@ static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist, pos->name = strdup(ev_name); } - return perf_evsel_menu__run(&menu, help); + return perf_evsel_menu__run(&menu, evlist->nr_entries, help, timer, + arg, delay_secs); } -int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help) +int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help, + void(*timer)(void *arg), void *arg, + int delay_secs) { if (evlist->nr_entries == 1) { struct perf_evsel *first = list_entry(evlist->entries.next, struct perf_evsel, node); const char *ev_name = event_name(first); - return perf_evsel__hists_browse(first, help, ev_name, false); + return perf_evsel__hists_browse(first, evlist->nr_entries, help, + ev_name, false, timer, arg, + delay_secs); } - return __perf_evlist__tui_browse_hists(evlist, help); + return __perf_evlist__tui_browse_hists(evlist, help, + timer, arg, delay_secs); } diff --git a/tools/perf/util/ui/browsers/map.c b/tools/perf/util/ui/browsers/map.c index 8462bffe20bc..6905bcc8be2d 100644 --- a/tools/perf/util/ui/browsers/map.c +++ b/tools/perf/util/ui/browsers/map.c @@ -1,5 +1,6 @@ #include "../libslang.h" #include <elf.h> +#include <newt.h> #include <inttypes.h> #include <sys/ttydefaults.h> #include <ctype.h> @@ -108,11 +109,8 @@ static int map_browser__run(struct map_browser *self) verbose ? "" : "restart with -v to use") < 0) return -1; - if (verbose) - ui_browser__add_exit_key(&self->b, '/'); - while (1) { - key = ui_browser__run(&self->b); + key = ui_browser__run(&self->b, 0); if (verbose && key == '/') map_browser__search(self); diff --git a/tools/perf/util/ui/browsers/top.c b/tools/perf/util/ui/browsers/top.c deleted file mode 100644 index 5a06538532af..000000000000 --- a/tools/perf/util/ui/browsers/top.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com> - * - * Parts came from builtin-{top,stat,record}.c, see those files for further - * copyright notes. - * - * Released under the GPL v2. (and only v2, not any later version) - */ -#include "../browser.h" -#include "../../annotate.h" -#include "../helpline.h" -#include "../libslang.h" -#include "../util.h" -#include "../../evlist.h" -#include "../../hist.h" -#include "../../sort.h" -#include "../../symbol.h" -#include "../../top.h" - -struct perf_top_browser { - struct ui_browser b; - struct rb_root root; - struct sym_entry *selection; - float sum_ksamples; - int dso_width; - int dso_short_width; - int sym_width; -}; - -static void perf_top_browser__write(struct ui_browser *browser, void *entry, int row) -{ - struct perf_top_browser *top_browser = container_of(browser, struct perf_top_browser, b); - struct sym_entry *syme = rb_entry(entry, struct sym_entry, rb_node); - bool current_entry = ui_browser__is_current_entry(browser, row); - struct symbol *symbol = sym_entry__symbol(syme); - struct perf_top *top = browser->priv; - int width = browser->width; - double pcnt; - - pcnt = 100.0 - (100.0 * ((top_browser->sum_ksamples - syme->snap_count) / - top_browser->sum_ksamples)); - ui_browser__set_percent_color(browser, pcnt, current_entry); - - if (top->evlist->nr_entries == 1 || !top->display_weighted) { - slsmg_printf("%20.2f ", syme->weight); - width -= 24; - } else { - slsmg_printf("%9.1f %10ld ", syme->weight, syme->snap_count); - width -= 23; - } - - slsmg_printf("%4.1f%%", pcnt); - width -= 7; - - if (verbose) { - slsmg_printf(" %016" PRIx64, symbol->start); - width -= 17; - } - - slsmg_printf(" %-*.*s ", top_browser->sym_width, top_browser->sym_width, - symbol->name); - width -= top_browser->sym_width; - slsmg_write_nstring(width >= syme->map->dso->long_name_len ? - syme->map->dso->long_name : - syme->map->dso->short_name, width); - - if (current_entry) - top_browser->selection = syme; -} - -static void perf_top_browser__update_rb_tree(struct perf_top_browser *browser) -{ - struct perf_top *top = browser->b.priv; - u64 top_idx = browser->b.top_idx; - - browser->root = RB_ROOT; - browser->b.top = NULL; - browser->sum_ksamples = perf_top__decay_samples(top, &browser->root); - /* - * No active symbols - */ - if (top->rb_entries == 0) - return; - - perf_top__find_widths(top, &browser->root, &browser->dso_width, - &browser->dso_short_width, - &browser->sym_width); - if (browser->sym_width + browser->dso_width > browser->b.width - 29) { - browser->dso_width = browser->dso_short_width; - if (browser->sym_width + browser->dso_width > browser->b.width - 29) - browser->sym_width = browser->b.width - browser->dso_width - 29; - } - - /* - * Adjust the ui_browser indexes since the entries in the browser->root - * rb_tree may have changed, then seek it from start, so that we get a - * possible new top of the screen. - */ - browser->b.nr_entries = top->rb_entries; - - if (top_idx >= browser->b.nr_entries) { - if (browser->b.height >= browser->b.nr_entries) - top_idx = browser->b.nr_entries - browser->b.height; - else - top_idx = 0; - } - - if (browser->b.index >= top_idx + browser->b.height) - browser->b.index = top_idx + browser->b.index - browser->b.top_idx; - - if (browser->b.index >= browser->b.nr_entries) - browser->b.index = browser->b.nr_entries - 1; - - browser->b.top_idx = top_idx; - browser->b.seek(&browser->b, top_idx, SEEK_SET); -} - -static void perf_top_browser__annotate(struct perf_top_browser *browser) -{ - struct sym_entry *syme = browser->selection; - struct symbol *sym = sym_entry__symbol(syme); - struct annotation *notes = symbol__annotation(sym); - struct perf_top *top = browser->b.priv; - - if (notes->src != NULL) - goto do_annotation; - - pthread_mutex_lock(¬es->lock); - - top->sym_filter_entry = NULL; - - if (symbol__alloc_hist(sym, top->evlist->nr_entries) < 0) { - pr_err("Not enough memory for annotating '%s' symbol!\n", - sym->name); - pthread_mutex_unlock(¬es->lock); - return; - } - - top->sym_filter_entry = syme; - - pthread_mutex_unlock(¬es->lock); -do_annotation: - symbol__tui_annotate(sym, syme->map, 0, top->delay_secs * 1000); -} - -static int perf_top_browser__run(struct perf_top_browser *browser) -{ - int key; - char title[160]; - struct perf_top *top = browser->b.priv; - int delay_msecs = top->delay_secs * 1000; - int exit_keys[] = { 'a', NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, }; - - perf_top_browser__update_rb_tree(browser); - perf_top__header_snprintf(top, title, sizeof(title)); - perf_top__reset_sample_counters(top); - - if (ui_browser__show(&browser->b, title, - "ESC: exit, ENTER|->|a: Live Annotate") < 0) - return -1; - - newtFormSetTimer(browser->b.form, delay_msecs); - ui_browser__add_exit_keys(&browser->b, exit_keys); - - while (1) { - key = ui_browser__run(&browser->b); - - switch (key) { - case -1: - /* FIXME we need to check if it was es.reason == NEWT_EXIT_TIMER */ - perf_top_browser__update_rb_tree(browser); - perf_top__header_snprintf(top, title, sizeof(title)); - perf_top__reset_sample_counters(top); - ui_browser__set_color(&browser->b, NEWT_COLORSET_ROOT); - SLsmg_gotorc(0, 0); - slsmg_write_nstring(title, browser->b.width); - break; - case 'a': - case NEWT_KEY_RIGHT: - case NEWT_KEY_ENTER: - if (browser->selection) - perf_top_browser__annotate(browser); - break; - case NEWT_KEY_LEFT: - continue; - case NEWT_KEY_ESCAPE: - if (!ui__dialog_yesno("Do you really want to exit?")) - continue; - /* Fall thru */ - default: - goto out; - } - } -out: - ui_browser__hide(&browser->b); - return key; -} - -int perf_top__tui_browser(struct perf_top *top) -{ - struct perf_top_browser browser = { - .b = { - .entries = &browser.root, - .refresh = ui_browser__rb_tree_refresh, - .seek = ui_browser__rb_tree_seek, - .write = perf_top_browser__write, - .priv = top, - }, - }; - - ui_helpline__push("Press <- or ESC to exit"); - return perf_top_browser__run(&browser); -} diff --git a/tools/perf/util/ui/helpline.c b/tools/perf/util/ui/helpline.c index f36d2ff509ed..6ef3c5691762 100644 --- a/tools/perf/util/ui/helpline.c +++ b/tools/perf/util/ui/helpline.c @@ -1,20 +1,28 @@ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> -#include <newt.h> +#include <string.h> #include "../debug.h" #include "helpline.h" #include "ui.h" +#include "libslang.h" void ui_helpline__pop(void) { - newtPopHelpLine(); } +char ui_helpline__current[512]; + void ui_helpline__push(const char *msg) { - newtPushHelpLine(msg); + const size_t sz = sizeof(ui_helpline__current); + + SLsmg_gotorc(SLtt_Screen_Rows - 1, 0); + SLsmg_set_color(0); + SLsmg_write_nstring((char *)msg, SLtt_Screen_Cols); + SLsmg_refresh(); + strncpy(ui_helpline__current, msg, sz)[sz - 1] = '\0'; } void ui_helpline__vpush(const char *fmt, va_list ap) @@ -63,7 +71,7 @@ int ui_helpline__show_help(const char *format, va_list ap) if (ui_helpline__last_msg[backlog - 1] == '\n') { ui_helpline__puts(ui_helpline__last_msg); - newtRefresh(); + SLsmg_refresh(); backlog = 0; } pthread_mutex_unlock(&ui__lock); diff --git a/tools/perf/util/ui/helpline.h b/tools/perf/util/ui/helpline.h index ab6028d0c401..7bab6b34e35e 100644 --- a/tools/perf/util/ui/helpline.h +++ b/tools/perf/util/ui/helpline.h @@ -1,6 +1,9 @@ #ifndef _PERF_UI_HELPLINE_H_ #define _PERF_UI_HELPLINE_H_ 1 +#include <stdio.h> +#include <stdarg.h> + void ui_helpline__init(void); void ui_helpline__pop(void); void ui_helpline__push(const char *msg); @@ -8,4 +11,6 @@ void ui_helpline__vpush(const char *fmt, va_list ap); void ui_helpline__fpush(const char *fmt, ...); void ui_helpline__puts(const char *msg); +extern char ui_helpline__current[]; + #endif /* _PERF_UI_HELPLINE_H_ */ diff --git a/tools/perf/util/ui/keysyms.h b/tools/perf/util/ui/keysyms.h new file mode 100644 index 000000000000..3458b1985761 --- /dev/null +++ b/tools/perf/util/ui/keysyms.h @@ -0,0 +1,25 @@ +#ifndef _PERF_KEYSYMS_H_ +#define _PERF_KEYSYMS_H_ 1 + +#include "libslang.h" + +#define K_DOWN SL_KEY_DOWN +#define K_END SL_KEY_END +#define K_ENTER '\r' +#define K_ESC 033 +#define K_F1 SL_KEY_F(1) +#define K_HOME SL_KEY_HOME +#define K_LEFT SL_KEY_LEFT +#define K_PGDN SL_KEY_NPAGE +#define K_PGUP SL_KEY_PPAGE +#define K_RIGHT SL_KEY_RIGHT +#define K_TAB '\t' +#define K_UNTAB SL_KEY_UNTAB +#define K_UP SL_KEY_UP + +/* Not really keys */ +#define K_TIMER -1 +#define K_ERROR -2 +#define K_RESIZE -3 + +#endif /* _PERF_KEYSYMS_H_ */ diff --git a/tools/perf/util/ui/libslang.h b/tools/perf/util/ui/libslang.h index 2b63e1c9b181..4d54b6450f5b 100644 --- a/tools/perf/util/ui/libslang.h +++ b/tools/perf/util/ui/libslang.h @@ -24,4 +24,6 @@ #define sltt_set_color SLtt_set_color #endif +#define SL_KEY_UNTAB 0x1000 + #endif /* _PERF_UI_SLANG_H_ */ diff --git a/tools/perf/util/ui/progress.c b/tools/perf/util/ui/progress.c index d7fc399d36b3..295e366b6311 100644 --- a/tools/perf/util/ui/progress.c +++ b/tools/perf/util/ui/progress.c @@ -1,60 +1,29 @@ -#include <stdlib.h> -#include <newt.h> #include "../cache.h" #include "progress.h" +#include "libslang.h" +#include "ui.h" +#include "browser.h" -struct ui_progress { - newtComponent form, scale; -}; - -struct ui_progress *ui_progress__new(const char *title, u64 total) -{ - struct ui_progress *self = malloc(sizeof(*self)); - - if (self != NULL) { - int cols; - - if (use_browser <= 0) - return self; - newtGetScreenSize(&cols, NULL); - cols -= 4; - newtCenteredWindow(cols, 1, title); - self->form = newtForm(NULL, NULL, 0); - if (self->form == NULL) - goto out_free_self; - self->scale = newtScale(0, 0, cols, total); - if (self->scale == NULL) - goto out_free_form; - newtFormAddComponent(self->form, self->scale); - newtRefresh(); - } - - return self; - -out_free_form: - newtFormDestroy(self->form); -out_free_self: - free(self); - return NULL; -} - -void ui_progress__update(struct ui_progress *self, u64 curr) +void ui_progress__update(u64 curr, u64 total, const char *title) { + int bar, y; /* * FIXME: We should have a per UI backend way of showing progress, * stdio will just show a percentage as NN%, etc. */ if (use_browser <= 0) return; - newtScaleSet(self->scale, curr); - newtRefresh(); -} -void ui_progress__delete(struct ui_progress *self) -{ - if (use_browser > 0) { - newtFormDestroy(self->form); - newtPopWindow(); - } - free(self); + ui__refresh_dimensions(true); + pthread_mutex_lock(&ui__lock); + y = SLtt_Screen_Rows / 2 - 2; + SLsmg_set_color(0); + SLsmg_draw_box(y, 0, 3, SLtt_Screen_Cols); + SLsmg_gotorc(y++, 1); + SLsmg_write_string((char *)title); + SLsmg_set_color(HE_COLORSET_SELECTED); + bar = ((SLtt_Screen_Cols - 2) * curr) / total; + SLsmg_fill_region(y, 1, 1, bar, ' '); + SLsmg_refresh(); + pthread_mutex_unlock(&ui__lock); } diff --git a/tools/perf/util/ui/progress.h b/tools/perf/util/ui/progress.h index a3820a0beb5b..d9c205b59aa1 100644 --- a/tools/perf/util/ui/progress.h +++ b/tools/perf/util/ui/progress.h @@ -1,11 +1,8 @@ #ifndef _PERF_UI_PROGRESS_H_ #define _PERF_UI_PROGRESS_H_ 1 -struct ui_progress; +#include <../types.h> -struct ui_progress *ui_progress__new(const char *title, u64 total); -void ui_progress__delete(struct ui_progress *self); - -void ui_progress__update(struct ui_progress *self, u64 curr); +void ui_progress__update(u64 curr, u64 total, const char *title); #endif diff --git a/tools/perf/util/ui/setup.c b/tools/perf/util/ui/setup.c index ee46d671db59..85a69faa09aa 100644 --- a/tools/perf/util/ui/setup.c +++ b/tools/perf/util/ui/setup.c @@ -7,9 +7,85 @@ #include "browser.h" #include "helpline.h" #include "ui.h" +#include "util.h" +#include "libslang.h" +#include "keysyms.h" pthread_mutex_t ui__lock = PTHREAD_MUTEX_INITIALIZER; +static volatile int ui__need_resize; + +void ui__refresh_dimensions(bool force) +{ + if (force || ui__need_resize) { + ui__need_resize = 0; + pthread_mutex_lock(&ui__lock); + SLtt_get_screen_size(); + SLsmg_reinit_smg(); + pthread_mutex_unlock(&ui__lock); + } +} + +static void ui__sigwinch(int sig __used) +{ + ui__need_resize = 1; +} + +static void ui__setup_sigwinch(void) +{ + static bool done; + + if (done) + return; + + done = true; + pthread__unblock_sigwinch(); + signal(SIGWINCH, ui__sigwinch); +} + +int ui__getch(int delay_secs) +{ + struct timeval timeout, *ptimeout = delay_secs ? &timeout : NULL; + fd_set read_set; + int err, key; + + ui__setup_sigwinch(); + + FD_ZERO(&read_set); + FD_SET(0, &read_set); + + if (delay_secs) { + timeout.tv_sec = delay_secs; + timeout.tv_usec = 0; + } + + err = select(1, &read_set, NULL, NULL, ptimeout); + + if (err == 0) + return K_TIMER; + + if (err == -1) { + if (errno == EINTR) + return K_RESIZE; + return K_ERROR; + } + + key = SLang_getkey(); + if (key != K_ESC) + return key; + + FD_ZERO(&read_set); + FD_SET(0, &read_set); + timeout.tv_sec = 0; + timeout.tv_usec = 20; + err = select(1, &read_set, NULL, NULL, &timeout); + if (err == 0) + return K_ESC; + + SLang_ungetkey(key); + return SLkp_getkey(); +} + static void newt_suspend(void *d __used) { newtSuspend(); @@ -17,6 +93,33 @@ static void newt_suspend(void *d __used) newtResume(); } +static int ui__init(void) +{ + int err = SLkp_init(); + + if (err < 0) + goto out; + + SLkp_define_keysym((char *)"^(kB)", SL_KEY_UNTAB); +out: + return err; +} + +static void ui__exit(void) +{ + SLtt_set_cursor_visibility(1); + SLsmg_refresh(); + SLsmg_reset_smg(); + SLang_reset_tty(); +} + +static void ui__signal(int sig) +{ + ui__exit(); + psignal(sig, "perf"); + exit(0); +} + void setup_browser(bool fallback_to_pager) { if (!isatty(1) || !use_browser || dump_trace) { @@ -28,19 +131,25 @@ void setup_browser(bool fallback_to_pager) use_browser = 1; newtInit(); - newtCls(); + ui__init(); newtSetSuspendCallback(newt_suspend, NULL); ui_helpline__init(); ui_browser__init(); + + signal(SIGSEGV, ui__signal); + signal(SIGFPE, ui__signal); + signal(SIGINT, ui__signal); + signal(SIGQUIT, ui__signal); + signal(SIGTERM, ui__signal); } void exit_browser(bool wait_for_ok) { if (use_browser > 0) { - if (wait_for_ok) { - char title[] = "Fatal Error", ok[] = "Ok"; - newtWinMessage(title, ok, ui_helpline__last_msg); - } - newtFinished(); + if (wait_for_ok) + ui__question_window("Fatal Error", + ui_helpline__last_msg, + "Press any key...", 0); + ui__exit(); } } diff --git a/tools/perf/util/ui/ui.h b/tools/perf/util/ui/ui.h index d264e059c829..7b67045479f6 100644 --- a/tools/perf/util/ui/ui.h +++ b/tools/perf/util/ui/ui.h @@ -2,7 +2,10 @@ #define _PERF_UI_H_ 1 #include <pthread.h> +#include <stdbool.h> extern pthread_mutex_t ui__lock; +void ui__refresh_dimensions(bool force); + #endif /* _PERF_UI_H_ */ diff --git a/tools/perf/util/ui/util.c b/tools/perf/util/ui/util.c index fdf1fc8f08bc..45daa7c41dad 100644 --- a/tools/perf/util/ui/util.c +++ b/tools/perf/util/ui/util.c @@ -1,6 +1,5 @@ -#include <newt.h> +#include "../util.h" #include <signal.h> -#include <stdio.h> #include <stdbool.h> #include <string.h> #include <sys/ttydefaults.h> @@ -8,72 +7,75 @@ #include "../cache.h" #include "../debug.h" #include "browser.h" +#include "keysyms.h" #include "helpline.h" #include "ui.h" #include "util.h" +#include "libslang.h" -static void newt_form__set_exit_keys(newtComponent self) +static void ui_browser__argv_write(struct ui_browser *browser, + void *entry, int row) { - newtFormAddHotKey(self, NEWT_KEY_LEFT); - newtFormAddHotKey(self, NEWT_KEY_ESCAPE); - newtFormAddHotKey(self, 'Q'); - newtFormAddHotKey(self, 'q'); - newtFormAddHotKey(self, CTRL('c')); -} + char **arg = entry; + bool current_entry = ui_browser__is_current_entry(browser, row); -static newtComponent newt_form__new(void) -{ - newtComponent self = newtForm(NULL, NULL, 0); - if (self) - newt_form__set_exit_keys(self); - return self; + ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : + HE_COLORSET_NORMAL); + slsmg_write_nstring(*arg, browser->width); } -int ui__popup_menu(int argc, char * const argv[]) +static int popup_menu__run(struct ui_browser *menu) { - struct newtExitStruct es; - int i, rc = -1, max_len = 5; - newtComponent listbox, form = newt_form__new(); + int key; - if (form == NULL) + if (ui_browser__show(menu, " ", "ESC: exit, ENTER|->: Select option") < 0) return -1; - listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT); - if (listbox == NULL) - goto out_destroy_form; + while (1) { + key = ui_browser__run(menu, 0); - newtFormAddComponent(form, listbox); + switch (key) { + case K_RIGHT: + case K_ENTER: + key = menu->index; + break; + case K_LEFT: + case K_ESC: + case 'q': + case CTRL('c'): + key = -1; + break; + default: + continue; + } - 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; + break; } - 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; + ui_browser__hide(menu); + return key; } -int ui__help_window(const char *text) +int ui__popup_menu(int argc, char * const argv[]) { - struct newtExitStruct es; - newtComponent tb, form = newt_form__new(); - int rc = -1; + struct ui_browser menu = { + .entries = (void *)argv, + .refresh = ui_browser__argv_refresh, + .seek = ui_browser__argv_seek, + .write = ui_browser__argv_write, + .nr_entries = argc, + }; + + return popup_menu__run(&menu); +} + +int ui__question_window(const char *title, const char *text, + const char *exit_msg, int delay_secs) +{ + int x, y; 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'); @@ -90,41 +92,77 @@ int ui__help_window(const char *text) 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; + max_len += 2; + nr_lines += 4; + y = SLtt_Screen_Rows / 2 - nr_lines / 2, + x = SLtt_Screen_Cols / 2 - max_len / 2; + + SLsmg_set_color(0); + SLsmg_draw_box(y, x++, nr_lines, max_len); + if (title) { + SLsmg_gotorc(y, x + 1); + SLsmg_write_string((char *)title); + } + SLsmg_gotorc(++y, x); + nr_lines -= 2; + max_len -= 2; + SLsmg_write_wrapped_string((unsigned char *)text, y, x, + nr_lines, max_len, 1); + SLsmg_gotorc(y + nr_lines - 2, x); + SLsmg_write_nstring((char *)" ", max_len); + SLsmg_gotorc(y + nr_lines - 1, x); + SLsmg_write_nstring((char *)exit_msg, max_len); + SLsmg_refresh(); + return ui__getch(delay_secs); } -static const char yes[] = "Yes", no[] = "No", - warning_str[] = "Warning!", ok[] = "Ok"; +int ui__help_window(const char *text) +{ + return ui__question_window("Help", text, "Press any key...", 0); +} -bool ui__dialog_yesno(const char *msg) +int ui__dialog_yesno(const char *msg) { - /* newtWinChoice should really be accepting const char pointers... */ - return newtWinChoice(NULL, (char *)yes, (char *)no, (char *)msg) == 1; + return ui__question_window(NULL, msg, "Enter: Yes, ESC: No", 0); } -void ui__warning(const char *format, ...) +int __ui__warning(const char *title, const char *format, va_list args) { - va_list args; + char *s; + + if (use_browser > 0 && vasprintf(&s, format, args) > 0) { + int key; - va_start(args, format); - if (use_browser > 0) { pthread_mutex_lock(&ui__lock); - newtWinMessagev((char *)warning_str, (char *)ok, - (char *)format, args); + key = ui__question_window(title, s, "Press any key...", 0); pthread_mutex_unlock(&ui__lock); - } else - vfprintf(stderr, format, args); + free(s); + return key; + } + + fprintf(stderr, "%s:\n", title); + vfprintf(stderr, format, args); + return K_ESC; +} + +int ui__warning(const char *format, ...) +{ + int key; + va_list args; + + va_start(args, format); + key = __ui__warning("Warning", format, args); + va_end(args); + return key; +} + +int ui__error(const char *format, ...) +{ + int key; + va_list args; + + va_start(args, format); + key = __ui__warning("Error", format, args); va_end(args); + return key; } diff --git a/tools/perf/util/ui/util.h b/tools/perf/util/ui/util.h index afcbc1d99531..2d1738bd71c8 100644 --- a/tools/perf/util/ui/util.h +++ b/tools/perf/util/ui/util.h @@ -1,10 +1,14 @@ #ifndef _PERF_UI_UTIL_H_ #define _PERF_UI_UTIL_H_ 1 -#include <stdbool.h> +#include <stdarg.h> +int ui__getch(int delay_secs); int ui__popup_menu(int argc, char * const argv[]); int ui__help_window(const char *text); -bool ui__dialog_yesno(const char *msg); +int ui__dialog_yesno(const char *msg); +int ui__question_window(const char *title, const char *text, + const char *exit_msg, int delay_secs); +int __ui__warning(const char *title, const char *format, va_list args); #endif /* _PERF_UI_UTIL_H_ */ diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index fc784284ac8b..0128906bac88 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -238,6 +238,7 @@ char **argv_split(const char *str, int *argcp); void argv_free(char **argv); bool strglobmatch(const char *str, const char *pat); bool strlazymatch(const char *str, const char *pat); +int strtailcmp(const char *s1, const char *s2); unsigned long convert_unit(unsigned long value, char *unit); int readn(int fd, void *buf, size_t size); |