diff options
author | Michal Marek | 2010-12-14 22:01:55 +0100 |
---|---|---|
committer | Michal Marek | 2010-12-14 22:01:55 +0100 |
commit | 8990c1bc4be46473ad19bf2fa612ca57286f3df4 (patch) | |
tree | 3cea60576903a1d26c67e6ec62891b524d390e95 /tools/perf | |
parent | 2979076fbf17a0947d6eba367b0cac19c907c160 (diff) | |
parent | c8ddb2713c624f432fa5fe3c7ecffcdda46ea0d4 (diff) |
Merge commit 'v2.6.37-rc1' into kbuild/kbuild
Diffstat (limited to 'tools/perf')
105 files changed, 6383 insertions, 2162 deletions
diff --git a/tools/perf/.gitignore b/tools/perf/.gitignore index e1d60d780784..cb43289e447f 100644 --- a/tools/perf/.gitignore +++ b/tools/perf/.gitignore @@ -18,3 +18,5 @@ perf-archive tags TAGS cscope* +config.mak +config.mak.autogen diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Documentation/perf-annotate.txt index 5164a655c39f..b2c63309a651 100644 --- a/tools/perf/Documentation/perf-annotate.txt +++ b/tools/perf/Documentation/perf-annotate.txt @@ -8,7 +8,7 @@ perf-annotate - Read perf.data (created by perf record) and display annotated co SYNOPSIS -------- [verse] -'perf annotate' [-i <file> | --input=file] symbol_name +'perf annotate' [-i <file> | --input=file] [symbol_name] DESCRIPTION ----------- @@ -24,6 +24,13 @@ OPTIONS --input=:: Input file name. (default: perf.data) +--stdio:: Use the stdio interface. + +--tui:: Use the TUI interface Use of --tui requires a tty, if one is not + present, as when piping to other commands, the stdio interface is + used. This interfaces starts by centering on the line with more + samples, TAB/UNTAB cycles thru the lines with more samples. + SEE ALSO -------- -linkperf:perf-record[1] +linkperf:perf-record[1], linkperf:perf-report[1] diff --git a/tools/perf/Documentation/perf-buildid-cache.txt b/tools/perf/Documentation/perf-buildid-cache.txt index 5d1a9500277f..c1057701a7dc 100644 --- a/tools/perf/Documentation/perf-buildid-cache.txt +++ b/tools/perf/Documentation/perf-buildid-cache.txt @@ -12,9 +12,9 @@ SYNOPSIS DESCRIPTION ----------- -This command manages the build-id cache. It can add and remove files to the -cache. In the future it should as well purge older entries, set upper limits -for the space used by the cache, etc. +This command manages the build-id cache. It can add and remove files to/from +the cache. In the future it should as well purge older entries, set upper +limits for the space used by the cache, etc. OPTIONS ------- @@ -23,7 +23,7 @@ OPTIONS Add specified file to the cache. -r:: --remove=:: - Remove specified file to the cache. + Remove specified file from the cache. -v:: --verbose:: Be more verbose. diff --git a/tools/perf/Documentation/perf-list.txt b/tools/perf/Documentation/perf-list.txt index 43e3dd284b90..399751befeed 100644 --- a/tools/perf/Documentation/perf-list.txt +++ b/tools/perf/Documentation/perf-list.txt @@ -15,6 +15,23 @@ DESCRIPTION This command displays the symbolic event types which can be selected in the various perf commands with the -e option. +EVENT MODIFIERS +--------------- + +Events can optionally have a modifer by appending a colon and one or +more modifiers. Modifiers allow the user to restrict when events are +counted with 'u' for user-space, 'k' for kernel, 'h' for hypervisor. + +The 'p' modifier can be used for specifying how precise the instruction +address should be. The 'p' modifier is currently only implemented for +Intel PEBS and can be specified multiple times: + 0 - SAMPLE_IP can have arbitrary skid + 1 - SAMPLE_IP must have constant skid + 2 - SAMPLE_IP requested to have 0 skid + 3 - SAMPLE_IP must have 0 skid + +The PEBS implementation now supports up to 2. + RAW HARDWARE EVENT DESCRIPTOR ----------------------------- Even when an event is not available in a symbolic form within perf right now, diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt index 94a258c96a44..62de1b7f4e76 100644 --- a/tools/perf/Documentation/perf-probe.txt +++ b/tools/perf/Documentation/perf-probe.txt @@ -16,7 +16,9 @@ or or 'perf probe' --list or -'perf probe' --line='FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]' +'perf probe' [options] --line='FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]' +or +'perf probe' [options] --vars='PROBEPOINT' DESCRIPTION ----------- @@ -31,6 +33,15 @@ OPTIONS --vmlinux=PATH:: Specify vmlinux path which has debuginfo (Dwarf binary). +-m:: +--module=MODNAME:: + Specify module name in which perf-probe searches probe points + or lines. + +-s:: +--source=PATH:: + Specify path to kernel source. + -v:: --verbose:: Be more verbose (show parsed arguments, etc). @@ -53,6 +64,15 @@ OPTIONS Show source code lines which can be probed. This needs an argument which specifies a range of the source code. (see LINE SYNTAX for detail) +-V:: +--vars=:: + Show available local variables at given probe point. The argument + syntax is same as PROBE SYNTAX, but NO ARGs. + +--externs:: + (Only for --vars) Show external defined variables in addition to local + variables. + -f:: --force:: Forcibly add events with existing name. @@ -90,8 +110,8 @@ Each probe argument follows below syntax. [NAME=]LOCALVAR|$retval|%REG|@SYMBOL[:TYPE] -'NAME' specifies the name of this argument (optional). You can use the name of local variable, local data structure member (e.g. var->field, var.field2), or kprobe-tracer argument format (e.g. $retval, %ax, etc). Note that the name of this argument will be set as the last member name if you specify a local data structure member (e.g. field2 for 'var->field1.field2'.) -'TYPE' casts the type of this argument (optional). If omitted, perf probe automatically set the type based on debuginfo. +'NAME' specifies the name of this argument (optional). You can use the name of local variable, local data structure member (e.g. var->field, var.field2), local array with fixed index (e.g. array[1], var->array[0], var->pointer[2]), or kprobe-tracer argument format (e.g. $retval, %ax, etc). Note that the name of this argument will be set as the last member name if you specify a local data structure member (e.g. field2 for 'var->field1.field2'.) +'TYPE' casts the type of this argument (optional). If omitted, perf probe automatically set the type based on debuginfo. You can specify 'string' type only for the local variable or structure member which is an array of or a pointer to 'char' or 'unsigned char' type. LINE SYNTAX ----------- diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt index 34e255fc3e2f..a91f9f9e6e5c 100644 --- a/tools/perf/Documentation/perf-record.txt +++ b/tools/perf/Documentation/perf-record.txt @@ -83,6 +83,10 @@ OPTIONS --call-graph:: Do call-graph (stack chain/backtrace) recording. +-q:: +--quiet:: + Don't print any message, useful for scripting. + -v:: --verbose:: Be more verbose (show counter open errors, etc). @@ -103,6 +107,19 @@ OPTIONS --raw-samples:: Collect raw sample records from all opened counters (default for tracepoint counters). +-C:: +--cpu:: +Collect samples only on the list of cpus provided. Multiple CPUs can be provided as a +comma-sperated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. +In per-thread mode with inheritance mode on (default), samples are captured only when +the thread executes on the designated CPUs. Default is to monitor all CPUs. + +-N:: +--no-buildid-cache:: +Do not update the builid cache. This saves some overhead in situations +where the information in the perf.data file (which includes buildids) +is sufficient. + SEE ALSO -------- linkperf:perf-stat[1], linkperf:perf-list[1] diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index abfabe9147a4..12052c9ed0ba 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -65,6 +65,13 @@ OPTIONS the tree is considered as a new profiled object. + Default: fractal,0.5. +--stdio:: Use the stdio interface. + +--tui:: Use the TUI interface, that is integrated with annotate and allows + zooming into DSOs or threads, among other features. Use of --tui + requires a tty, if one is not present, as when piping to other + commands, the stdio interface is used. + SEE ALSO -------- linkperf:perf-stat[1] diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt index 909fa766fa1c..4b3a2d46b437 100644 --- a/tools/perf/Documentation/perf-stat.txt +++ b/tools/perf/Documentation/perf-stat.txt @@ -46,6 +46,13 @@ OPTIONS -B:: print large numbers with thousands' separators according to locale +-C:: +--cpu=:: +Count only on the list of cpus provided. Multiple CPUs can be provided as a +comma-sperated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. +In per-thread mode, this option is ignored. The -a option is still necessary +to activate system-wide monitoring. Default is to count on all CPUs. + EXAMPLES -------- diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt index 785b9fc32a46..1f9687663f2a 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -25,9 +25,11 @@ OPTIONS --count=<count>:: Event period to sample. --C <cpu>:: ---CPU=<cpu>:: - CPU to profile. +-C <cpu-list>:: +--cpu=<cpu>:: +Monitor only on the list of cpus provided. Multiple CPUs can be provided as a +comma-sperated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. +Default is to monitor all CPUS. -d <seconds>:: --delay=<seconds>:: diff --git a/tools/perf/MANIFEST b/tools/perf/MANIFEST new file mode 100644 index 000000000000..8c7fc0c8f0b8 --- /dev/null +++ b/tools/perf/MANIFEST @@ -0,0 +1,12 @@ +tools/perf +include/linux/perf_event.h +include/linux/rbtree.h +include/linux/list.h +include/linux/hash.h +include/linux/stringify.h +lib/rbtree.c +include/linux/swab.h +arch/*/include/asm/unistd*.h +include/linux/poison.h +include/linux/magic.h +include/linux/hw_breakpoint.h diff --git a/tools/perf/Makefile b/tools/perf/Makefile index d75c28a825f5..d1db0f676a4b 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -5,6 +5,12 @@ endif # The default target of this Makefile is... all:: +ifneq ($(OUTPUT),) +# check that the output directory actually exists +OUTDIR := $(shell cd $(OUTPUT) && /bin/pwd) +$(if $(OUTDIR),, $(error output directory "$(OUTPUT)" does not exist)) +endif + # Define V=1 to have a more verbose compile. # Define V=2 to have an even more verbose compile. # @@ -157,11 +163,6 @@ all:: # # Define NO_DWARF if you do not want debug-info analysis feature at all. -$(shell sh -c 'mkdir -p $(OUTPUT)scripts/python/Perf-Trace-Util/' 2> /dev/null) -$(shell sh -c 'mkdir -p $(OUTPUT)scripts/perl/Perf-Trace-Util/' 2> /dev/null) -$(shell sh -c 'mkdir -p $(OUTPUT)util/scripting-engines/' 2> /dev/null) -$(shell sh -c 'mkdir $(OUTPUT)bench' 2> /dev/null) - $(OUTPUT)PERF-VERSION-FILE: .FORCE-PERF-VERSION-FILE @$(SHELL_PATH) util/PERF-VERSION-GEN $(OUTPUT) -include $(OUTPUT)PERF-VERSION-FILE @@ -187,8 +188,6 @@ ifeq ($(ARCH),x86_64) ARCH := x86 endif -$(shell sh -c 'mkdir -p $(OUTPUT)arch/$(ARCH)/util/' 2> /dev/null) - # CFLAGS and LDFLAGS are for the users to override from the command line. # @@ -269,6 +268,7 @@ export prefix bindir sharedir sysconfdir CC = $(CROSS_COMPILE)gcc AR = $(CROSS_COMPILE)ar RM = rm -f +MKDIR = mkdir TAR = tar FIND = find INSTALL = install @@ -285,14 +285,10 @@ else QUIET_STDERR = ">/dev/null 2>&1" endif -BITBUCKET = "/dev/null" +-include feature-tests.mak -ifneq ($(shell sh -c "(echo '\#include <stdio.h>'; echo 'int main(void) { return puts(\"hi\"); }') | $(CC) -x c - $(ALL_CFLAGS) -o $(BITBUCKET) "$(QUIET_STDERR)" && echo y"), y) - BITBUCKET = .perf.dev.null -endif - -ifeq ($(shell sh -c "echo 'int foo(void) {char X[2]; return 3;}' | $(CC) -x c -c -Werror -fstack-protector-all - -o $(BITBUCKET) "$(QUIET_STDERR)" && echo y"), y) - CFLAGS := $(CFLAGS) -fstack-protector-all +ifeq ($(call try-cc,$(SOURCE_HELLO),-Werror -fstack-protector-all),y) + CFLAGS := $(CFLAGS) -fstack-protector-all endif @@ -317,6 +313,9 @@ TEST_PROGRAMS = SCRIPT_SH += perf-archive.sh +grep-libs = $(filter -l%,$(1)) +strip-libs = $(filter-out -l%,$(1)) + # # No Perl scripts right now: # @@ -508,7 +507,8 @@ PERFLIBS = $(LIB_FILE) -include config.mak ifndef NO_DWARF -ifneq ($(shell sh -c "(echo '\#include <dwarf.h>'; echo '\#include <libdw.h>'; echo '\#include <version.h>'; echo '\#ifndef _ELFUTILS_PREREQ'; echo '\#error'; echo '\#endif'; echo 'int main(void) { Dwarf *dbg; dbg = dwarf_begin(0, DWARF_C_READ); return (long)dbg; }') | $(CC) -x c - $(ALL_CFLAGS) -I/usr/include/elfutils -ldw -lelf -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) "$(QUIET_STDERR)" && echo y"), y) +FLAGS_DWARF=$(ALL_CFLAGS) -I/usr/include/elfutils -ldw -lelf $(ALL_LDFLAGS) $(EXTLIBS) +ifneq ($(call try-cc,$(SOURCE_DWARF),$(FLAGS_DWARF)),y) msg := $(warning No libdw.h found or old libdw.h found or elfutils is older than 0.138, disables dwarf support. Please install new elfutils-devel/libdw-dev); NO_DWARF := 1 endif # Dwarf support @@ -536,16 +536,18 @@ ifneq ($(OUTPUT),) BASIC_CFLAGS += -I$(OUTPUT) endif -ifeq ($(shell sh -c "(echo '\#include <libelf.h>'; echo 'int main(void) { Elf * elf = elf_begin(0, ELF_C_READ, 0); return (long)elf; }') | $(CC) -x c - $(ALL_CFLAGS) -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) "$(QUIET_STDERR)" && echo y"), y) -ifneq ($(shell sh -c "(echo '\#include <gnu/libc-version.h>'; echo 'int main(void) { const char * version = gnu_get_libc_version(); return (long)version; }') | $(CC) -x c - $(ALL_CFLAGS) -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) "$(QUIET_STDERR)" && echo y"), y) - msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]/glibc-static); +FLAGS_LIBELF=$(ALL_CFLAGS) $(ALL_LDFLAGS) $(EXTLIBS) +ifneq ($(call try-cc,$(SOURCE_LIBELF),$(FLAGS_LIBELF)),y) + FLAGS_GLIBC=$(ALL_CFLAGS) $(ALL_LDFLAGS) + ifneq ($(call try-cc,$(SOURCE_GLIBC),$(FLAGS_GLIBC)),y) + msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]/glibc-static); + else + msg := $(error No libelf.h/libelf found, please install libelf-dev/elfutils-libelf-devel); + endif endif - ifneq ($(shell sh -c "(echo '\#include <libelf.h>'; echo 'int main(void) { Elf * elf = elf_begin(0, ELF_C_READ_MMAP, 0); return (long)elf; }') | $(CC) -x c - $(ALL_CFLAGS) -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) "$(QUIET_STDERR)" && echo y"), y) - BASIC_CFLAGS += -DLIBELF_NO_MMAP - endif -else - msg := $(error No libelf.h/libelf found, please install libelf-dev/elfutils-libelf-devel and glibc-dev[el]); +ifneq ($(call try-cc,$(SOURCE_ELF_MMAP),$(FLAGS_COMMON)),y) + BASIC_CFLAGS += -DLIBELF_NO_MMAP endif ifndef NO_DWARF @@ -561,64 +563,92 @@ endif # NO_DWARF ifdef NO_NEWT BASIC_CFLAGS += -DNO_NEWT_SUPPORT else -ifneq ($(shell sh -c "(echo '\#include <newt.h>'; echo 'int main(void) { newtInit(); newtCls(); return newtFinished(); }') | $(CC) -x c - $(ALL_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -lnewt -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) "$(QUIET_STDERR)" && echo y"), y) - msg := $(warning newt not found, disables TUI support. Please install newt-devel or libnewt-dev); - BASIC_CFLAGS += -DNO_NEWT_SUPPORT -else - # Fedora has /usr/include/slang/slang.h, but ubuntu /usr/include/slang.h - BASIC_CFLAGS += -I/usr/include/slang - EXTLIBS += -lnewt -lslang - LIB_OBJS += $(OUTPUT)util/newt.o -endif -endif # NO_NEWT - -ifndef NO_LIBPERL -PERL_EMBED_LDOPTS = `perl -MExtUtils::Embed -e ldopts 2>/dev/null` -PERL_EMBED_CCOPTS = `perl -MExtUtils::Embed -e ccopts 2>/dev/null` + FLAGS_NEWT=$(ALL_CFLAGS) $(ALL_LDFLAGS) $(EXTLIBS) -lnewt + ifneq ($(call try-cc,$(SOURCE_NEWT),$(FLAGS_NEWT)),y) + msg := $(warning newt not found, disables TUI support. Please install newt-devel or libnewt-dev); + BASIC_CFLAGS += -DNO_NEWT_SUPPORT + else + # Fedora has /usr/include/slang/slang.h, but ubuntu /usr/include/slang.h + BASIC_CFLAGS += -I/usr/include/slang + EXTLIBS += -lnewt -lslang + LIB_OBJS += $(OUTPUT)util/ui/setup.o + LIB_OBJS += $(OUTPUT)util/ui/browser.o + 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/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/libslang.h + LIB_H += util/ui/progress.h + LIB_H += util/ui/util.h + endif endif -ifneq ($(shell sh -c "(echo '\#include <EXTERN.h>'; echo '\#include <perl.h>'; echo 'int main(void) { perl_alloc(); return 0; }') | $(CC) -x c - $(PERL_EMBED_CCOPTS) -o $(BITBUCKET) $(PERL_EMBED_LDOPTS) > /dev/null 2>&1 && echo y"), y) +ifdef NO_LIBPERL BASIC_CFLAGS += -DNO_LIBPERL else - ALL_LDFLAGS += $(PERL_EMBED_LDOPTS) - LIB_OBJS += $(OUTPUT)util/scripting-engines/trace-event-perl.o - LIB_OBJS += $(OUTPUT)scripts/perl/Perf-Trace-Util/Context.o -endif - -ifndef NO_LIBPYTHON -PYTHON_EMBED_LDOPTS = `python-config --ldflags 2>/dev/null` -PYTHON_EMBED_CCOPTS = `python-config --cflags 2>/dev/null` + PERL_EMBED_LDOPTS = $(shell perl -MExtUtils::Embed -e ldopts 2>/dev/null) + PERL_EMBED_LDFLAGS = $(call strip-libs,$(PERL_EMBED_LDOPTS)) + PERL_EMBED_LIBADD = $(call grep-libs,$(PERL_EMBED_LDOPTS)) + PERL_EMBED_CCOPTS = `perl -MExtUtils::Embed -e ccopts 2>/dev/null` + FLAGS_PERL_EMBED=$(PERL_EMBED_CCOPTS) $(PERL_EMBED_LDOPTS) + + ifneq ($(call try-cc,$(SOURCE_PERL_EMBED),$(FLAGS_PERL_EMBED)),y) + BASIC_CFLAGS += -DNO_LIBPERL + else + ALL_LDFLAGS += $(PERL_EMBED_LDFLAGS) + EXTLIBS += $(PERL_EMBED_LIBADD) + LIB_OBJS += $(OUTPUT)util/scripting-engines/trace-event-perl.o + LIB_OBJS += $(OUTPUT)scripts/perl/Perf-Trace-Util/Context.o + endif endif -ifneq ($(shell sh -c "(echo '\#include <Python.h>'; echo 'int main(void) { Py_Initialize(); return 0; }') | $(CC) -x c - $(PYTHON_EMBED_CCOPTS) -o $(BITBUCKET) $(PYTHON_EMBED_LDOPTS) > /dev/null 2>&1 && echo y"), y) +ifdef NO_LIBPYTHON BASIC_CFLAGS += -DNO_LIBPYTHON else - ALL_LDFLAGS += $(PYTHON_EMBED_LDOPTS) - LIB_OBJS += $(OUTPUT)util/scripting-engines/trace-event-python.o - LIB_OBJS += $(OUTPUT)scripts/python/Perf-Trace-Util/Context.o + PYTHON_EMBED_LDOPTS = $(shell python-config --ldflags 2>/dev/null) + PYTHON_EMBED_LDFLAGS = $(call strip-libs,$(PYTHON_EMBED_LDOPTS)) + PYTHON_EMBED_LIBADD = $(call grep-libs,$(PYTHON_EMBED_LDOPTS)) + PYTHON_EMBED_CCOPTS = `python-config --cflags 2>/dev/null` + FLAGS_PYTHON_EMBED=$(PYTHON_EMBED_CCOPTS) $(PYTHON_EMBED_LDOPTS) + ifneq ($(call try-cc,$(SOURCE_PYTHON_EMBED),$(FLAGS_PYTHON_EMBED)),y) + BASIC_CFLAGS += -DNO_LIBPYTHON + else + ALL_LDFLAGS += $(PYTHON_EMBED_LDFLAGS) + EXTLIBS += $(PYTHON_EMBED_LIBADD) + LIB_OBJS += $(OUTPUT)util/scripting-engines/trace-event-python.o + LIB_OBJS += $(OUTPUT)scripts/python/Perf-Trace-Util/Context.o + endif endif ifdef NO_DEMANGLE BASIC_CFLAGS += -DNO_DEMANGLE else - ifdef HAVE_CPLUS_DEMANGLE + ifdef HAVE_CPLUS_DEMANGLE EXTLIBS += -liberty BASIC_CFLAGS += -DHAVE_CPLUS_DEMANGLE - else - has_bfd := $(shell sh -c "(echo '\#include <bfd.h>'; echo 'int main(void) { bfd_demangle(0, 0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) -lbfd "$(QUIET_STDERR)" && echo y") - + else + FLAGS_BFD=$(ALL_CFLAGS) $(ALL_LDFLAGS) $(EXTLIBS) -lbfd + has_bfd := $(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD)) ifeq ($(has_bfd),y) EXTLIBS += -lbfd else - has_bfd_iberty := $(shell sh -c "(echo '\#include <bfd.h>'; echo 'int main(void) { bfd_demangle(0, 0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) -lbfd -liberty "$(QUIET_STDERR)" && echo y") + FLAGS_BFD_IBERTY=$(FLAGS_BFD) -liberty + has_bfd_iberty := $(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD_IBERTY)) ifeq ($(has_bfd_iberty),y) EXTLIBS += -lbfd -liberty else - has_bfd_iberty_z := $(shell sh -c "(echo '\#include <bfd.h>'; echo 'int main(void) { bfd_demangle(0, 0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) -lbfd -liberty -lz "$(QUIET_STDERR)" && echo y") + FLAGS_BFD_IBERTY_Z=$(FLAGS_BFD_IBERTY) -lz + has_bfd_iberty_z := $(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD_IBERTY_Z)) ifeq ($(has_bfd_iberty_z),y) EXTLIBS += -lbfd -liberty -lz else - has_cplus_demangle := $(shell sh -c "(echo 'extern char *cplus_demangle(const char *, int);'; echo 'int main(void) { cplus_demangle(0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o $(BITBUCKET) $(ALL_LDFLAGS) $(EXTLIBS) -liberty "$(QUIET_STDERR)" && echo y") + FLAGS_CPLUS_DEMANGLE=$(ALL_CFLAGS) $(ALL_LDFLAGS) $(EXTLIBS) -liberty + has_cplus_demangle := $(call try-cc,$(SOURCE_CPLUS_DEMANGLE),$(FLAGS_CPLUS_DEMANGLE)) ifeq ($(has_cplus_demangle),y) EXTLIBS += -liberty BASIC_CFLAGS += -DHAVE_CPLUS_DEMANGLE @@ -632,6 +662,15 @@ else endif endif + +ifdef NO_STRLCPY + BASIC_CFLAGS += -DNO_STRLCPY +else + ifneq ($(call try-cc,$(SOURCE_STRLCPY),),y) + BASIC_CFLAGS += -DNO_STRLCPY + endif +endif + ifndef CC_LD_DYNPATH ifdef NO_R_TO_GCC_LINKER # Some gcc does not accept and pass -R to the linker to specify @@ -818,6 +857,7 @@ ifndef V QUIET_CC = @echo ' ' CC $@; QUIET_AR = @echo ' ' AR $@; QUIET_LINK = @echo ' ' LINK $@; + QUIET_MKDIR = @echo ' ' MKDIR $@; QUIET_BUILT_IN = @echo ' ' BUILTIN $@; QUIET_GEN = @echo ' ' GEN $@; QUIET_SUBDIR0 = +@subdir= @@ -867,7 +907,7 @@ export TAR INSTALL DESTDIR SHELL_PATH SHELL = $(SHELL_PATH) -all:: .perf.dev.null shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) $(OUTPUT)PERF-BUILD-OPTIONS +all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) $(OUTPUT)PERF-BUILD-OPTIONS ifneq (,$X) $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) perf$X)), test '$p' -ef '$p$X' || $(RM) '$p';) endif @@ -888,8 +928,8 @@ $(OUTPUT)perf.o: perf.c $(OUTPUT)common-cmds.h $(OUTPUT)PERF-CFLAGS $(ALL_CFLAGS) -c $(filter %.c,$^) -o $@ $(OUTPUT)perf$X: $(OUTPUT)perf.o $(BUILTIN_OBJS) $(PERFLIBS) - $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(OUTPUT)perf.o \ - $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) $(OUTPUT)perf.o \ + $(BUILTIN_OBJS) $(LIBS) -o $@ $(OUTPUT)builtin-help.o: builtin-help.c $(OUTPUT)common-cmds.h $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) \ @@ -915,15 +955,15 @@ $(OUTPUT)common-cmds.h: $(wildcard Documentation/perf-*.txt) $(QUIET_GEN). util/generate-cmdlist.sh > $@+ && mv $@+ $@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh - $(QUIET_GEN)$(RM) $@ $@+ && \ + $(QUIET_GEN)$(RM) $(OUTPUT)$@ $(OUTPUT)$@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ -e 's/@@PERF_VERSION@@/$(PERF_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - $@.sh >$@+ && \ - chmod +x $@+ && \ - mv $@+ $(OUTPUT)$@ + $@.sh > $(OUTPUT)$@+ && \ + chmod +x $(OUTPUT)$@+ && \ + mv $(OUTPUT)$@+ $(OUTPUT)$@ configure: configure.ac $(QUIET_GEN)$(RM) $@ $<+ && \ @@ -958,7 +998,16 @@ $(OUTPUT)builtin-init-db.o: builtin-init-db.c $(OUTPUT)PERF-CFLAGS $(OUTPUT)util/config.o: util/config.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DETC_PERFCONFIG='"$(ETC_PERFCONFIG_SQ)"' $< -$(OUTPUT)util/newt.o: util/newt.c $(OUTPUT)PERF-CFLAGS +$(OUTPUT)util/ui/browser.o: util/ui/browser.c $(OUTPUT)PERF-CFLAGS + $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< + +$(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/hists.o: util/ui/browsers/hists.c $(OUTPUT)PERF-CFLAGS + $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< + +$(OUTPUT)util/ui/browsers/map.o: util/ui/browsers/map.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< $(OUTPUT)util/rbtree.o: ../../lib/rbtree.c $(OUTPUT)PERF-CFLAGS @@ -983,6 +1032,14 @@ $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst perf-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) builtin-revert.o wt-status.o: wt-status.h +# we compile into subdirectories. if the target directory is not the source directory, they might not exists. So +# we depend the various files onto their directories. +DIRECTORY_DEPS = $(LIB_OBJS) $(BUILTIN_OBJS) $(OUTPUT)PERF-VERSION-FILE $(OUTPUT)common-cmds.h +$(DIRECTORY_DEPS): | $(sort $(dir $(DIRECTORY_DEPS))) +# In the second step, we make a rule to actually create these directories +$(sort $(dir $(DIRECTORY_DEPS))): + $(QUIET_MKDIR)$(MKDIR) -p $@ 2>/dev/null + $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) @@ -1197,11 +1254,6 @@ clean: .PHONY: .FORCE-PERF-VERSION-FILE TAGS tags cscope .FORCE-PERF-CFLAGS .PHONY: .FORCE-PERF-BUILD-OPTIONS -.perf.dev.null: - touch .perf.dev.null - -.INTERMEDIATE: .perf.dev.null - ### Make sure built-ins do not have dups and listed in perf.c # check-builtins:: diff --git a/tools/perf/arch/arm/Makefile b/tools/perf/arch/arm/Makefile new file mode 100644 index 000000000000..15130b50dfe3 --- /dev/null +++ b/tools/perf/arch/arm/Makefile @@ -0,0 +1,4 @@ +ifndef NO_DWARF +PERF_HAVE_DWARF_REGS := 1 +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o +endif diff --git a/tools/perf/arch/arm/util/dwarf-regs.c b/tools/perf/arch/arm/util/dwarf-regs.c new file mode 100644 index 000000000000..fff6450c8c99 --- /dev/null +++ b/tools/perf/arch/arm/util/dwarf-regs.c @@ -0,0 +1,64 @@ +/* + * Mapping of DWARF debug register numbers into register names. + * + * Copyright (C) 2010 Will Deacon, ARM Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <libio.h> +#include <dwarf-regs.h> + +struct pt_regs_dwarfnum { + const char *name; + unsigned int dwarfnum; +}; + +#define STR(s) #s +#define REG_DWARFNUM_NAME(r, num) {.name = r, .dwarfnum = num} +#define GPR_DWARFNUM_NAME(num) \ + {.name = STR(%r##num), .dwarfnum = num} +#define REG_DWARFNUM_END {.name = NULL, .dwarfnum = 0} + +/* + * Reference: + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0040a/IHI0040A_aadwarf.pdf + */ +static const struct pt_regs_dwarfnum regdwarfnum_table[] = { + GPR_DWARFNUM_NAME(0), + GPR_DWARFNUM_NAME(1), + GPR_DWARFNUM_NAME(2), + GPR_DWARFNUM_NAME(3), + GPR_DWARFNUM_NAME(4), + GPR_DWARFNUM_NAME(5), + GPR_DWARFNUM_NAME(6), + GPR_DWARFNUM_NAME(7), + GPR_DWARFNUM_NAME(8), + GPR_DWARFNUM_NAME(9), + GPR_DWARFNUM_NAME(10), + REG_DWARFNUM_NAME("%fp", 11), + REG_DWARFNUM_NAME("%ip", 12), + REG_DWARFNUM_NAME("%sp", 13), + REG_DWARFNUM_NAME("%lr", 14), + REG_DWARFNUM_NAME("%pc", 15), + REG_DWARFNUM_END, +}; + +/** + * get_arch_regstr() - lookup register name from it's DWARF register number + * @n: the DWARF register number + * + * get_arch_regstr() returns the name of the register in struct + * regdwarfnum_table from it's DWARF register number. If the register is not + * found in the table, this returns NULL; + */ +const char *get_arch_regstr(unsigned int n) +{ + const struct pt_regs_dwarfnum *roff; + for (roff = regdwarfnum_table; roff->name != NULL; roff++) + if (roff->dwarfnum == n) + return roff->name; + return NULL; +} diff --git a/tools/perf/arch/sh/Makefile b/tools/perf/arch/sh/Makefile new file mode 100644 index 000000000000..15130b50dfe3 --- /dev/null +++ b/tools/perf/arch/sh/Makefile @@ -0,0 +1,4 @@ +ifndef NO_DWARF +PERF_HAVE_DWARF_REGS := 1 +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o +endif diff --git a/tools/perf/arch/sh/util/dwarf-regs.c b/tools/perf/arch/sh/util/dwarf-regs.c new file mode 100644 index 000000000000..a11edb007a6c --- /dev/null +++ b/tools/perf/arch/sh/util/dwarf-regs.c @@ -0,0 +1,55 @@ +/* + * Mapping of DWARF debug register numbers into register names. + * + * Copyright (C) 2010 Matt Fleming <matt@console-pimps.org> + * + * 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 <libio.h> +#include <dwarf-regs.h> + +/* + * Generic dwarf analysis helpers + */ + +#define SH_MAX_REGS 18 +const char *sh_regs_table[SH_MAX_REGS] = { + "r0", + "r1", + "r2", + "r3", + "r4", + "r5", + "r6", + "r7", + "r8", + "r9", + "r10", + "r11", + "r12", + "r13", + "r14", + "r15", + "pc", + "pr", +}; + +/* Return architecture dependent register string (for kprobe-tracer) */ +const char *get_arch_regstr(unsigned int n) +{ + return (n <= SH_MAX_REGS) ? sh_regs_table[n] : NULL; +} diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index 96db5248e995..6d5604d8df95 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -28,7 +28,7 @@ static char const *input_name = "perf.data"; -static bool force; +static bool force, use_tui, use_stdio; static bool full_paths; @@ -61,11 +61,9 @@ static int hists__add_entry(struct hists *self, struct addr_location *al) static int process_sample_event(event_t *event, struct perf_session *session) { struct addr_location al; + struct sample_data data; - dump_printf("(IP, %d): %d: %#Lx\n", event->header.misc, - event->ip.pid, event->ip.ip); - - if (event__preprocess_sample(event, session, &al, NULL) < 0) { + if (event__preprocess_sample(event, session, &al, &data, NULL) < 0) { pr_warning("problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -287,7 +285,7 @@ static int hist_entry__tty_annotate(struct hist_entry *he) LIST_HEAD(head); struct objdump_line *pos, *n; - if (hist_entry__annotate(he, &head) < 0) + if (hist_entry__annotate(he, &head, 0) < 0) return -1; if (full_paths) @@ -323,7 +321,7 @@ static int hist_entry__tty_annotate(struct hist_entry *he) static void hists__find_annotations(struct hists *self) { - struct rb_node *first = rb_first(&self->entries), *nd = first; + struct rb_node *nd = rb_first(&self->entries), *next; int key = KEY_RIGHT; while (nd) { @@ -345,20 +343,19 @@ find_next: if (use_browser > 0) { key = hist_entry__tui_annotate(he); - if (is_exit_key(key)) - break; switch (key) { case KEY_RIGHT: - case '\t': - nd = rb_next(nd); + next = rb_next(nd); break; case KEY_LEFT: - if (nd == first) - continue; - nd = rb_prev(nd); - default: + next = rb_prev(nd); break; + default: + return; } + + if (next != NULL) + nd = next; } else { hist_entry__tty_annotate(he); nd = rb_next(nd); @@ -430,6 +427,8 @@ static const struct option options[] = { "be more verbose (show symbol address, etc)"), OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, "dump raw trace in ASCII"), + OPT_BOOLEAN(0, "tui", &use_tui, "Use the TUI interface"), + OPT_BOOLEAN(0, "stdio", &use_stdio, "Use the stdio interface"), OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, "file", "vmlinux pathname"), OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules, @@ -445,6 +444,11 @@ int cmd_annotate(int argc, const char **argv, const char *prefix __used) { argc = parse_options(argc, argv, options, annotate_usage, 0); + if (use_stdio) + use_browser = 0; + else if (use_tui) + use_browser = 1; + setup_browser(); symbol_conf.priv_size = sizeof(struct sym_priv); diff --git a/tools/perf/builtin-buildid-cache.c b/tools/perf/builtin-buildid-cache.c index f8e3d1852029..29ad20e67919 100644 --- a/tools/perf/builtin-buildid-cache.c +++ b/tools/perf/builtin-buildid-cache.c @@ -78,8 +78,7 @@ static int __cmd_buildid_cache(void) struct str_node *pos; char debugdir[PATH_MAX]; - snprintf(debugdir, sizeof(debugdir), "%s/%s", getenv("HOME"), - DEBUG_CACHE_DIR); + snprintf(debugdir, sizeof(debugdir), "%s", buildid_dir); if (add_name_list_str) { list = strlist__new(true, add_name_list_str); diff --git a/tools/perf/builtin-buildid-list.c b/tools/perf/builtin-buildid-list.c index 99890728409e..44a47e13bd67 100644 --- a/tools/perf/builtin-buildid-list.c +++ b/tools/perf/builtin-buildid-list.c @@ -43,10 +43,8 @@ static int __cmd_buildid_list(void) if (session == NULL) return -1; - if (with_hits) { - symbol_conf.full_paths = true; + if (with_hits) perf_session__process_events(session, &build_id__mark_dso_hit_ops); - } perf_session__fprintf_dsos_buildid(session, stdout, with_hits); diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index a6e2fdc7a04e..fca1d4402910 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -35,10 +35,7 @@ static int diff__process_sample_event(event_t *event, struct perf_session *sessi struct addr_location al; struct sample_data data = { .period = 1, }; - dump_printf("(IP, %d): %d: %#Lx\n", event->header.misc, - event->ip.pid, event->ip.ip); - - if (event__preprocess_sample(event, session, &al, NULL) < 0) { + if (event__preprocess_sample(event, session, &al, &data, NULL) < 0) { pr_warning("problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -47,8 +44,6 @@ static int diff__process_sample_event(event_t *event, struct perf_session *sessi if (al.filtered || al.sym == NULL) return 0; - event__parse_sample(event, session->sample_type, &data); - if (hists__add_entry(&session->hists, &al, data.period)) { pr_warning("problem incrementing symbol period, skipping event\n"); return -1; @@ -185,8 +180,6 @@ static const struct option options[] = { OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules, "load module symbols - WARNING: use only with -k and LIVE kernel"), - OPT_BOOLEAN('P', "full-paths", &symbol_conf.full_paths, - "Don't shorten the pathnames taking into account the cwd"), 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...]", diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index e4a4da32a568..2e000c068cc5 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -50,14 +50,17 @@ static struct { bool list_events; bool force_add; bool show_lines; + bool show_vars; + bool show_ext_vars; + bool mod_events; int nevents; struct perf_probe_event events[MAX_PROBES]; struct strlist *dellist; struct line_range line_range; + const char *target_module; int max_probe_points; } params; - /* Parse an event definition. Note that any error must die. */ static int parse_probe_event(const char *str) { @@ -92,6 +95,7 @@ static int parse_probe_event_argv(int argc, const char **argv) len = 0; for (i = 0; i < argc; i++) len += sprintf(&buf[len], "%s ", argv[i]); + params.mod_events = true; ret = parse_probe_event(buf); free(buf); return ret; @@ -100,9 +104,10 @@ static int parse_probe_event_argv(int argc, const char **argv) static int opt_add_probe_event(const struct option *opt __used, const char *str, int unset __used) { - if (str) + if (str) { + params.mod_events = true; return parse_probe_event(str); - else + } else return 0; } @@ -110,6 +115,7 @@ static int opt_del_probe_event(const struct option *opt __used, const char *str, int unset __used) { if (str) { + params.mod_events = true; if (!params.dellist) params.dellist = strlist__new(true, NULL); strlist__add(params.dellist, str); @@ -130,6 +136,25 @@ static int opt_show_lines(const struct option *opt __used, return ret; } + +static int opt_show_vars(const struct option *opt __used, + const char *str, int unset __used) +{ + struct perf_probe_event *pev = ¶ms.events[params.nevents]; + int ret; + + if (!str) + return 0; + + ret = parse_probe_event(str); + if (!ret && pev->nargs != 0) { + pr_err(" Error: '--vars' doesn't accept arguments.\n"); + return -EINVAL; + } + params.show_vars = true; + + return ret; +} #endif static const char * const probe_usage[] = { @@ -138,7 +163,8 @@ static const char * const probe_usage[] = { "perf probe [<options>] --del '[GROUP:]EVENT' ...", "perf probe --list", #ifdef DWARF_SUPPORT - "perf probe --line 'LINEDESC'", + "perf probe [<options>] --line 'LINEDESC'", + "perf probe [<options>] --vars 'PROBEPOINT'", #endif NULL }; @@ -180,8 +206,17 @@ static const struct option options[] = { OPT_CALLBACK('L', "line", NULL, "FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2]", "Show source code lines.", opt_show_lines), + OPT_CALLBACK('V', "vars", NULL, + "FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT", + "Show accessible variables on PROBEDEF", opt_show_vars), + OPT_BOOLEAN('\0', "externs", ¶ms.show_ext_vars, + "Show external variables too (with --vars only)"), OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, "file", "vmlinux pathname"), + OPT_STRING('s', "source", &symbol_conf.source_prefix, + "directory", "path to kernel source"), + OPT_STRING('m', "module", ¶ms.target_module, + "modname", "target module name"), #endif OPT__DRY_RUN(&probe_event_dry_run), OPT_INTEGER('\0', "max-probes", ¶ms.max_probe_points, @@ -215,7 +250,7 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) usage_with_options(probe_usage, options); if (params.list_events) { - if (params.nevents != 0 || params.dellist) { + if (params.mod_events) { pr_err(" Error: Don't use --list with --add/--del.\n"); usage_with_options(probe_usage, options); } @@ -223,6 +258,10 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) pr_err(" Error: Don't use --list with --line.\n"); usage_with_options(probe_usage, options); } + if (params.show_vars) { + pr_err(" Error: Don't use --list with --vars.\n"); + usage_with_options(probe_usage, options); + } ret = show_perf_probe_events(); if (ret < 0) pr_err(" Error: Failed to show event list. (%d)\n", @@ -232,17 +271,35 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) #ifdef DWARF_SUPPORT if (params.show_lines) { - if (params.nevents != 0 || params.dellist) { - pr_warning(" Error: Don't use --line with" - " --add/--del.\n"); + if (params.mod_events) { + pr_err(" Error: Don't use --line with" + " --add/--del.\n"); + usage_with_options(probe_usage, options); + } + if (params.show_vars) { + pr_err(" Error: Don't use --line with --vars.\n"); usage_with_options(probe_usage, options); } - ret = show_line_range(¶ms.line_range); + ret = show_line_range(¶ms.line_range, params.target_module); if (ret < 0) pr_err(" Error: Failed to show lines. (%d)\n", ret); return ret; } + if (params.show_vars) { + if (params.mod_events) { + pr_err(" Error: Don't use --vars with" + " --add/--del.\n"); + usage_with_options(probe_usage, options); + } + ret = show_available_vars(params.events, params.nevents, + params.max_probe_points, + params.target_module, + params.show_ext_vars); + if (ret < 0) + pr_err(" Error: Failed to show vars. (%d)\n", ret); + return ret; + } #endif if (params.dellist) { @@ -256,8 +313,9 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) if (params.nevents) { ret = add_perf_probe_events(params.events, params.nevents, - params.force_add, - params.max_probe_points); + params.max_probe_points, + params.target_module, + params.force_add); if (ret < 0) { pr_err(" Error: Failed to add events. (%d)\n", ret); return ret; @@ -265,4 +323,3 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) } return 0; } - diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 711745f56bba..4e75583ddd6d 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -49,7 +49,6 @@ static int group = 0; static int realtime_prio = 0; static bool raw_samples = false; static bool system_wide = false; -static int profile_cpu = -1; static pid_t target_pid = -1; static pid_t target_tid = -1; static pid_t *all_tids = NULL; @@ -61,6 +60,7 @@ static bool call_graph = false; static bool inherit_stat = false; static bool no_samples = false; static bool sample_address = false; +static bool no_buildid = false; static long samples = 0; static u64 bytes_written = 0; @@ -74,6 +74,7 @@ static int file_new = 1; static off_t post_processing_offset; static struct perf_session *session; +static const char *cpu_list; struct mmap_data { int counter; @@ -268,12 +269,17 @@ static void create_counter(int counter, int cpu) if (inherit_stat) attr->inherit_stat = 1; - if (sample_address) + if (sample_address) { attr->sample_type |= PERF_SAMPLE_ADDR; + attr->mmap_data = track; + } if (call_graph) attr->sample_type |= PERF_SAMPLE_CALLCHAIN; + if (system_wide) + attr->sample_type |= PERF_SAMPLE_CPU; + if (raw_samples) { attr->sample_type |= PERF_SAMPLE_TIME; attr->sample_type |= PERF_SAMPLE_RAW; @@ -300,7 +306,7 @@ try_again: die("Permission error - are you root?\n" "\t Consider tweaking" " /proc/sys/kernel/perf_event_paranoid.\n"); - else if (err == ENODEV && profile_cpu != -1) { + else if (err == ENODEV && cpu_list) { die("No such device - did you specify" " an out-of-range profile CPU?\n"); } @@ -347,7 +353,7 @@ try_again: } if (read(fd[nr_cpu][counter][thread_index], &read_data, sizeof(read_data)) == -1) { - perror("Unable to read perf file descriptor\n"); + perror("Unable to read perf file descriptor"); exit(-1); } @@ -433,14 +439,14 @@ static void atexit_header(void) process_buildids(); perf_header__write(&session->header, output, true); + perf_session__delete(session); + symbol__exit(); } } static void event__synthesize_guest_os(struct machine *machine, void *data) { int err; - char *guest_kallsyms; - char path[PATH_MAX]; struct perf_session *psession = data; if (machine__is_host(machine)) @@ -460,13 +466,6 @@ static void event__synthesize_guest_os(struct machine *machine, void *data) pr_err("Couldn't record guest kernel [%d]'s reference" " relocation symbol.\n", machine->pid); - if (machine__is_default_guest(machine)) - guest_kallsyms = (char *) symbol_conf.default_guest_kallsyms; - else { - sprintf(path, "%s/proc/kallsyms", machine->root_dir); - guest_kallsyms = path; - } - /* * We use _stext for guest kernel because guest kernel's /proc/kallsyms * have no _text sometimes. @@ -561,12 +560,15 @@ static int __cmd_record(int argc, const char **argv) if (!file_new) { err = perf_header__read(session, output); if (err < 0) - return err; + goto out_delete_session; } if (have_tracepoints(attrs, nr_counters)) perf_header__set_feat(&session->header, HEADER_TRACE_INFO); + /* + * perf_session__delete(session) will be called at atexit_header() + */ atexit(atexit_header); if (forks) { @@ -622,10 +624,15 @@ static int __cmd_record(int argc, const char **argv) close(child_ready_pipe[0]); } - if ((!system_wide && no_inherit) || profile_cpu != -1) { - open_counters(profile_cpu); + nr_cpus = read_cpu_map(cpu_list); + if (nr_cpus < 1) { + perror("failed to collect number of CPUs"); + return -1; + } + + if (!system_wide && no_inherit && !cpu_list) { + open_counters(-1); } else { - nr_cpus = read_cpu_map(); for (i = 0; i < nr_cpus; i++) open_counters(cpumap[i]); } @@ -704,7 +711,7 @@ static int __cmd_record(int argc, const char **argv) if (perf_guest) perf_session__process_machines(session, event__synthesize_guest_os); - if (!system_wide && profile_cpu == -1) + if (!system_wide) event__synthesize_thread(target_tid, process_synthesized_event, session); else @@ -754,6 +761,9 @@ static int __cmd_record(int argc, const char **argv) } } + if (quiet) + return 0; + fprintf(stderr, "[ perf record: Woken up %ld times to write data ]\n", waking); /* @@ -766,6 +776,10 @@ static int __cmd_record(int argc, const char **argv) bytes_written / 24); return 0; + +out_delete_session: + perf_session__delete(session); + return err; } static const char * const record_usage[] = { @@ -794,8 +808,8 @@ static const struct option options[] = { "system-wide collection from all CPUs"), OPT_BOOLEAN('A', "append", &append_file, "append to the output file to do incremental profiling"), - OPT_INTEGER('C', "profile_cpu", &profile_cpu, - "CPU to profile on"), + OPT_STRING('C', "cpu", &cpu_list, "cpu", + "list of cpus to monitor"), OPT_BOOLEAN('f', "force", &force, "overwrite existing data file (deprecated)"), OPT_U64('c', "count", &user_interval, "event period to sample"), @@ -809,23 +823,26 @@ static const struct option options[] = { "do call-graph (stack chain/backtrace) recording"), OPT_INCR('v', "verbose", &verbose, "be more verbose (show counter open errors, etc)"), + OPT_BOOLEAN('q', "quiet", &quiet, "don't print any message"), OPT_BOOLEAN('s', "stat", &inherit_stat, "per thread counts"), OPT_BOOLEAN('d', "data", &sample_address, "Sample addresses"), OPT_BOOLEAN('n', "no-samples", &no_samples, "don't sample"), + OPT_BOOLEAN('N', "no-buildid-cache", &no_buildid, + "do not update the buildid cache"), OPT_END() }; int cmd_record(int argc, const char **argv, const char *prefix __used) { - int i,j; + int i, j, err = -ENOMEM; argc = parse_options(argc, argv, options, record_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (!argc && target_pid == -1 && target_tid == -1 && - !system_wide && profile_cpu == -1) + !system_wide && !cpu_list) usage_with_options(record_usage, options); if (force && append_file) { @@ -839,6 +856,8 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) } symbol__init(); + if (no_buildid) + disable_buildid_cache(); if (!nr_counters) { nr_counters = 1; @@ -857,7 +876,7 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) } else { all_tids=malloc(sizeof(pid_t)); if (!all_tids) - return -ENOMEM; + goto out_symbol_exit; all_tids[0] = target_tid; thread_num = 1; @@ -867,13 +886,13 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) for (j = 0; j < MAX_COUNTERS; j++) { fd[i][j] = malloc(sizeof(int)*thread_num); if (!fd[i][j]) - return -ENOMEM; + goto out_free_fd; } } event_array = malloc( sizeof(struct pollfd)*MAX_NR_CPUS*MAX_COUNTERS*thread_num); if (!event_array) - return -ENOMEM; + goto out_free_fd; if (user_interval != ULLONG_MAX) default_interval = user_interval; @@ -889,8 +908,22 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) default_interval = freq; } else { fprintf(stderr, "frequency and count are zero, aborting\n"); - exit(EXIT_FAILURE); + err = -EINVAL; + goto out_free_event_array; } - return __cmd_record(argc, argv); + err = __cmd_record(argc, argv); + +out_free_event_array: + free(event_array); +out_free_fd: + for (i = 0; i < MAX_NR_CPUS; i++) { + for (j = 0; j < MAX_COUNTERS; j++) + free(fd[i][j]); + } + free(all_tids); + all_tids = NULL; +out_symbol_exit: + symbol__exit(); + return err; } diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index fd7407c7205c..5de405d45230 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -32,7 +32,7 @@ static char const *input_name = "perf.data"; -static bool force; +static bool force, use_tui, use_stdio; static bool hide_unresolved; static bool dont_use_callchains; @@ -107,7 +107,8 @@ static int perf_session__add_hist_entry(struct perf_session *self, goto out_free_syms; err = 0; if (symbol_conf.use_callchain) { - err = append_chain(he->callchain, data->callchain, syms, data->period); + err = callchain_append(he->callchain, data->callchain, syms, + data->period); if (err) goto out_free_syms; } @@ -155,30 +156,7 @@ static int process_sample_event(event_t *event, struct perf_session *session) struct addr_location al; struct perf_event_attr *attr; - event__parse_sample(event, session->sample_type, &data); - - dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc, - data.pid, data.tid, data.ip, data.period); - - if (session->sample_type & PERF_SAMPLE_CALLCHAIN) { - unsigned int i; - - dump_printf("... chain: nr:%Lu\n", data.callchain->nr); - - if (!ip_callchain__valid(data.callchain, event)) { - pr_debug("call-chain problem with event, " - "skipping it.\n"); - return 0; - } - - if (dump_trace) { - for (i = 0; i < data.callchain->nr; i++) - dump_printf("..... %2d: %016Lx\n", - i, data.callchain->ips[i]); - } - } - - if (event__preprocess_sample(event, session, &al, NULL) < 0) { + if (event__preprocess_sample(event, session, &al, &data, NULL) < 0) { fprintf(stderr, "problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -371,7 +349,18 @@ static int __cmd_report(void) hists__tty_browse_tree(&session->hists_tree, help); out_delete: - perf_session__delete(session); + /* + * Speed up the exit process, for large files this can + * take quite a while. + * + * XXX Enable this when using valgrind or if we ever + * librarize this command. + * + * Also experiment with obstacks to see how much speed + * up we'll get here. + * + * perf_session__delete(session); + */ return ret; } @@ -462,10 +451,10 @@ static const struct option options[] = { "Show per-thread event counters"), OPT_STRING(0, "pretty", &pretty_printing_style, "key", "pretty printing style key: normal raw"), + OPT_BOOLEAN(0, "tui", &use_tui, "Use the TUI interface"), + OPT_BOOLEAN(0, "stdio", &use_stdio, "Use the stdio interface"), OPT_STRING('s', "sort", &sort_order, "key[,key2...]", "sort by key(s): pid, comm, dso, symbol, parent"), - OPT_BOOLEAN('P', "full-paths", &symbol_conf.full_paths, - "Don't shorten the pathnames taking into account the cwd"), OPT_BOOLEAN(0, "showcpuutilization", &symbol_conf.show_cpu_utilization, "Show sample percentage for different cpu modes"), OPT_STRING('p', "parent", &parent_pattern, "regex", @@ -496,15 +485,38 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) { argc = parse_options(argc, argv, options, report_usage, 0); + if (use_stdio) + use_browser = 0; + else if (use_tui) + use_browser = 1; + if (strcmp(input_name, "-") != 0) setup_browser(); + else + use_browser = 0; /* * Only in the newt browser we are doing integrated annotation, * so don't allocate extra space that won't be used in the stdio * implementation. */ - if (use_browser > 0) + if (use_browser > 0) { symbol_conf.priv_size = sizeof(struct sym_priv); + /* + * For searching by name on the "Browse map details". + * providing it only in verbose mode not to bloat too + * much struct symbol. + */ + if (verbose) { + /* + * XXX: Need to provide a less kludgy way to ask for + * more space per symbol, the u32 is for the index on + * the ui browser. + * See symbol__browser_index. + */ + symbol_conf.priv_size += sizeof(u32); + symbol_conf.sort_by_name = true; + } + } if (symbol__init() < 0) return -1; diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 9a39ca3c3ac4..a6b4d44f9502 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -69,7 +69,7 @@ static struct perf_event_attr default_attrs[] = { }; static bool system_wide = false; -static unsigned int nr_cpus = 0; +static int nr_cpus = 0; static int run_idx = 0; static int run_count = 1; @@ -82,6 +82,7 @@ static int thread_num = 0; static pid_t child_pid = -1; static bool null_run = false; static bool big_num = false; +static const char *cpu_list; static int *fd[MAX_NR_CPUS][MAX_COUNTERS]; @@ -158,7 +159,7 @@ static int create_perf_stat_counter(int counter) PERF_FORMAT_TOTAL_TIME_RUNNING; if (system_wide) { - unsigned int cpu; + int cpu; for (cpu = 0; cpu < nr_cpus; cpu++) { fd[cpu][counter][0] = sys_perf_event_open(attr, @@ -208,7 +209,7 @@ static inline int nsec_counter(int counter) static void read_counter(int counter) { u64 count[3], single_count[3]; - unsigned int cpu; + int cpu; size_t res, nv; int scaled; int i, thread; @@ -542,6 +543,8 @@ static const struct option options[] = { "null run - dont start any counters"), OPT_BOOLEAN('B', "big-num", &big_num, "print large numbers with thousands\' separators"), + OPT_STRING('C', "cpu", &cpu_list, "cpu", + "list of cpus to monitor in system-wide"), OPT_END() }; @@ -566,10 +569,13 @@ int cmd_stat(int argc, const char **argv, const char *prefix __used) } if (system_wide) - nr_cpus = read_cpu_map(); + nr_cpus = read_cpu_map(cpu_list); else nr_cpus = 1; + if (nr_cpus < 1) + usage_with_options(stat_usage, options); + if (target_pid != -1) { target_tid = target_pid; thread_num = find_all_tid(target_pid, &all_tids); diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index 5a52ed9fc10b..9bcc38f0b706 100644 --- a/tools/perf/builtin-timechart.c +++ b/tools/perf/builtin-timechart.c @@ -300,8 +300,9 @@ struct trace_entry { struct power_entry { struct trace_entry te; - s64 type; - s64 value; + u64 type; + u64 value; + u64 cpu_id; }; #define TASK_COMM_LEN 16 @@ -454,8 +455,8 @@ static void sched_switch(int cpu, u64 timestamp, struct trace_entry *te) if (p->current->state != TYPE_NONE) pid_put_sample(sw->next_pid, p->current->state, cpu, p->current->state_since, timestamp); - p->current->state_since = timestamp; - p->current->state = TYPE_RUNNING; + p->current->state_since = timestamp; + p->current->state = TYPE_RUNNING; } if (prev_p->current) { @@ -498,13 +499,13 @@ static int process_sample_event(event_t *event, struct perf_session *session) return 0; if (strcmp(event_str, "power:power_start") == 0) - c_state_start(data.cpu, data.time, pe->value); + c_state_start(pe->cpu_id, data.time, pe->value); if (strcmp(event_str, "power:power_end") == 0) - c_state_end(data.cpu, data.time); + c_state_end(pe->cpu_id, data.time); if (strcmp(event_str, "power:power_frequency") == 0) - p_state_change(data.cpu, data.time, pe->value); + p_state_change(pe->cpu_id, data.time, pe->value); if (strcmp(event_str, "sched:sched_wakeup") == 0) sched_wakeup(data.cpu, data.time, data.pid, te); diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index a66f4272b994..b513e40974f4 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -102,6 +102,7 @@ struct sym_entry *sym_filter_entry_sched = NULL; static int sym_pcnt_filter = 5; static int sym_counter = 0; static int display_weighted = -1; +static const char *cpu_list; /* * Symbols @@ -982,6 +983,7 @@ static void event__process_sample(const event_t *self, u64 ip = self->ip.ip; struct sym_entry *syme; struct addr_location al; + struct sample_data data; struct machine *machine; u8 origin = self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; @@ -1024,7 +1026,8 @@ static void event__process_sample(const event_t *self, if (self->header.misc & PERF_RECORD_MISC_EXACT_IP) exact_samples++; - if (event__preprocess_sample(self, session, &al, symbol_filter) < 0 || + if (event__preprocess_sample(self, session, &al, &data, + symbol_filter) < 0 || al.filtered) return; @@ -1079,26 +1082,6 @@ static void event__process_sample(const event_t *self, } } -static int event__process(event_t *event, struct perf_session *session) -{ - switch (event->header.type) { - case PERF_RECORD_COMM: - event__process_comm(event, session); - break; - case PERF_RECORD_MMAP: - event__process_mmap(event, session); - break; - case PERF_RECORD_FORK: - case PERF_RECORD_EXIT: - event__process_task(event, session); - break; - default: - break; - } - - return 0; -} - struct mmap_data { int counter; void *base; @@ -1351,8 +1334,8 @@ static const struct option options[] = { "profile events on existing thread id"), OPT_BOOLEAN('a', "all-cpus", &system_wide, "system-wide collection from all CPUs"), - OPT_INTEGER('C', "CPU", &profile_cpu, - "CPU to profile on"), + OPT_STRING('C', "cpu", &cpu_list, "cpu", + "list of cpus to monitor"), OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, "file", "vmlinux pathname"), OPT_BOOLEAN('K', "hide_kernel_symbols", &hide_kernel_symbols, @@ -1428,10 +1411,10 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) return -ENOMEM; /* CPU and PID are mutually exclusive */ - if (target_tid > 0 && profile_cpu != -1) { + if (target_tid > 0 && cpu_list) { printf("WARNING: PID switch overriding CPU\n"); sleep(1); - profile_cpu = -1; + cpu_list = NULL; } if (!nr_counters) @@ -1469,10 +1452,13 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) attrs[counter].sample_period = default_interval; } - if (target_tid != -1 || profile_cpu != -1) + if (target_tid != -1) nr_cpus = 1; else - nr_cpus = read_cpu_map(); + nr_cpus = read_cpu_map(cpu_list); + + if (nr_cpus < 1) + usage_with_options(top_usage, options); get_term_dimensions(&winsize); if (print_entries == 0) { diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index dddf3f01b5ab..2f8df45c4dcb 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -1,18 +1,22 @@ #include "builtin.h" -#include "util/util.h" +#include "perf.h" #include "util/cache.h" +#include "util/debug.h" +#include "util/exec_cmd.h" +#include "util/header.h" +#include "util/parse-options.h" +#include "util/session.h" #include "util/symbol.h" #include "util/thread.h" -#include "util/header.h" -#include "util/exec_cmd.h" #include "util/trace-event.h" -#include "util/session.h" +#include "util/util.h" static char const *script_name; static char const *generate_script_lang; -static bool debug_ordering; +static bool debug_mode; static u64 last_timestamp; +static u64 nr_unordered; static int default_start_script(const char *script __unused, int argc __unused, @@ -42,9 +46,6 @@ static struct scripting_ops *scripting_ops; static void setup_scripting(void) { - /* make sure PERF_EXEC_PATH is set for scripts */ - perf_set_argv_exec_path(perf_exec_path()); - setup_perl_scripting(); setup_python_scripting(); @@ -58,14 +59,6 @@ static int cleanup_scripting(void) return scripting_ops->stop_script(); } -#include "util/parse-options.h" - -#include "perf.h" -#include "util/debug.h" - -#include "util/trace-event.h" -#include "util/exec_cmd.h" - static char const *input_name = "perf.data"; static int process_sample_event(event_t *event, struct perf_session *session) @@ -91,13 +84,15 @@ static int process_sample_event(event_t *event, struct perf_session *session) } if (session->sample_type & PERF_SAMPLE_RAW) { - if (debug_ordering) { + if (debug_mode) { if (data.time < last_timestamp) { pr_err("Samples misordered, previous: %llu " "this: %llu\n", last_timestamp, data.time); + nr_unordered++; } last_timestamp = data.time; + return 0; } /* * FIXME: better resolve from pid from the struct trace_entry @@ -113,6 +108,15 @@ static int process_sample_event(event_t *event, struct perf_session *session) return 0; } +static u64 nr_lost; + +static int process_lost_event(event_t *event, struct perf_session *session __used) +{ + nr_lost += event->lost.lost; + + return 0; +} + static struct perf_event_ops event_ops = { .sample = process_sample_event, .comm = event__process_comm, @@ -120,6 +124,7 @@ static struct perf_event_ops event_ops = { .event_type = event__process_event_type, .tracing_data = event__process_tracing_data, .build_id = event__process_build_id, + .lost = process_lost_event, .ordered_samples = true, }; @@ -132,9 +137,18 @@ static void sig_handler(int sig __unused) static int __cmd_trace(struct perf_session *session) { + int ret; + signal(SIGINT, sig_handler); - return perf_session__process_events(session, &event_ops); + ret = perf_session__process_events(session, &event_ops); + + if (debug_mode) { + pr_err("Misordered timestamps: %llu\n", nr_unordered); + pr_err("Lost events: %llu\n", nr_lost); + } + + return ret; } struct script_spec { @@ -268,7 +282,7 @@ static int parse_scriptname(const struct option *opt __used, script++; } else { script = str; - ext = strchr(script, '.'); + ext = strrchr(script, '.'); if (!ext) { fprintf(stderr, "invalid script extension"); return -1; @@ -544,8 +558,8 @@ static const struct option options[] = { "generate perf-trace.xx script in specified language"), OPT_STRING('i', "input", &input_name, "file", "input file name"), - OPT_BOOLEAN('d', "debug-ordering", &debug_ordering, - "check that samples time ordering is monotonic"), + OPT_BOOLEAN('d', "debug-mode", &debug_mode, + "do various checks like samples ordering and lost events"), OPT_END() }; @@ -576,6 +590,9 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) suffix = REPORT_SUFFIX; } + /* make sure PERF_EXEC_PATH is set for scripts */ + perf_set_argv_exec_path(perf_exec_path()); + if (!suffix && argc >= 2 && strncmp(argv[1], "-", strlen("-")) != 0) { char *record_script_path, *report_script_path; int live_pipe[2]; @@ -608,12 +625,13 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) dup2(live_pipe[1], 1); close(live_pipe[0]); - __argv = malloc(5 * sizeof(const char *)); + __argv = malloc(6 * sizeof(const char *)); __argv[0] = "/bin/sh"; __argv[1] = record_script_path; - __argv[2] = "-o"; - __argv[3] = "-"; - __argv[4] = NULL; + __argv[2] = "-q"; + __argv[3] = "-o"; + __argv[4] = "-"; + __argv[5] = NULL; execvp("/bin/sh", (char **)__argv); exit(-1); diff --git a/tools/perf/feature-tests.mak b/tools/perf/feature-tests.mak new file mode 100644 index 000000000000..b253db634f04 --- /dev/null +++ b/tools/perf/feature-tests.mak @@ -0,0 +1,130 @@ +define SOURCE_HELLO +#include <stdio.h> +int main(void) +{ + return puts(\"hi\"); +} +endef + +ifndef NO_DWARF +define SOURCE_DWARF +#include <dwarf.h> +#include <libdw.h> +#include <version.h> +#ifndef _ELFUTILS_PREREQ +#error +#endif + +int main(void) +{ + Dwarf *dbg = dwarf_begin(0, DWARF_C_READ); + return (long)dbg; +} +endef +endif + +define SOURCE_LIBELF +#include <libelf.h> + +int main(void) +{ + Elf *elf = elf_begin(0, ELF_C_READ, 0); + return (long)elf; +} +endef + +define SOURCE_GLIBC +#include <gnu/libc-version.h> + +int main(void) +{ + const char *version = gnu_get_libc_version(); + return (long)version; +} +endef + +define SOURCE_ELF_MMAP +#include <libelf.h> +int main(void) +{ + Elf *elf = elf_begin(0, ELF_C_READ_MMAP, 0); + return (long)elf; +} +endef + +ifndef NO_NEWT +define SOURCE_NEWT +#include <newt.h> + +int main(void) +{ + newtInit(); + newtCls(); + return newtFinished(); +} +endef +endif + +ifndef NO_LIBPERL +define SOURCE_PERL_EMBED +#include <EXTERN.h> +#include <perl.h> + +int main(void) +{ +perl_alloc(); +return 0; +} +endef +endif + +ifndef NO_LIBPYTHON +define SOURCE_PYTHON_EMBED +#include <Python.h> + +int main(void) +{ + Py_Initialize(); + return 0; +} +endef +endif + +define SOURCE_BFD +#include <bfd.h> + +int main(void) +{ + bfd_demangle(0, 0, 0); + return 0; +} +endef + +define SOURCE_CPLUS_DEMANGLE +extern char *cplus_demangle(const char *, int); + +int main(void) +{ + cplus_demangle(0, 0); + return 0; +} +endef + +define SOURCE_STRLCPY +#include <stdlib.h> +extern size_t strlcpy(char *dest, const char *src, size_t size); + +int main(void) +{ + strlcpy(NULL, NULL, 0); + return 0; +} +endef + +# try-cc +# Usage: option = $(call try-cc, source-to-build, cc-options) +try-cc = $(shell sh -c \ + 'TMP="$(OUTPUT)$(TMPOUT).$$$$"; \ + echo "$(1)" | \ + $(CC) -x c - $(2) -o "$$TMP" > /dev/null 2>&1 && echo y; \ + rm -f "$$TMP"') diff --git a/tools/perf/perf-archive.sh b/tools/perf/perf-archive.sh index 2e7a4f417e20..677e59d62a8d 100644 --- a/tools/perf/perf-archive.sh +++ b/tools/perf/perf-archive.sh @@ -7,7 +7,17 @@ if [ $# -ne 0 ] ; then PERF_DATA=$1 fi -DEBUGDIR=~/.debug/ +# +# PERF_BUILDID_DIR environment variable set by perf +# path to buildid directory, default to $HOME/.debug +# +if [ -z $PERF_BUILDID_DIR ]; then + PERF_BUILDID_DIR=~/.debug/ +else + # append / to make substitutions work + PERF_BUILDID_DIR=$PERF_BUILDID_DIR/ +fi + BUILDIDS=$(mktemp /tmp/perf-archive-buildids.XXXXXX) NOBUILDID=0000000000000000000000000000000000000000 @@ -22,13 +32,13 @@ MANIFEST=$(mktemp /tmp/perf-archive-manifest.XXXXXX) cut -d ' ' -f 1 $BUILDIDS | \ while read build_id ; do - linkname=$DEBUGDIR.build-id/${build_id:0:2}/${build_id:2} + linkname=$PERF_BUILDID_DIR.build-id/${build_id:0:2}/${build_id:2} filename=$(readlink -f $linkname) - echo ${linkname#$DEBUGDIR} >> $MANIFEST - echo ${filename#$DEBUGDIR} >> $MANIFEST + echo ${linkname#$PERF_BUILDID_DIR} >> $MANIFEST + echo ${filename#$PERF_BUILDID_DIR} >> $MANIFEST done -tar cfj $PERF_DATA.tar.bz2 -C $DEBUGDIR -T $MANIFEST +tar cfj $PERF_DATA.tar.bz2 -C $PERF_BUILDID_DIR -T $MANIFEST rm -f $MANIFEST $BUILDIDS echo -e "Now please run:\n" echo -e "$ tar xvf $PERF_DATA.tar.bz2 -C ~/.debug\n" diff --git a/tools/perf/perf.c b/tools/perf/perf.c index 6e4871191138..cdd6c03f1e14 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -458,6 +458,8 @@ int main(int argc, const char **argv) handle_options(&argv, &argc, NULL); commit_pager_choice(); set_debugfs_path(); + set_buildid_dir(); + if (argc > 0) { if (!prefixcmp(argv[0], "--")) argv[0] += 2; diff --git a/tools/perf/perf.h b/tools/perf/perf.h index ef7aa0a0c526..95aaf565c704 100644 --- a/tools/perf/perf.h +++ b/tools/perf/perf.h @@ -73,6 +73,18 @@ void get_term_dimensions(struct winsize *ws); #define cpu_relax() asm volatile("":::"memory") #endif +#ifdef __mips__ +#include "../../arch/mips/include/asm/unistd.h" +#define rmb() asm volatile( \ + ".set mips2\n\t" \ + "sync\n\t" \ + ".set mips0" \ + : /* no output */ \ + : /* no input */ \ + : "memory") +#define cpu_relax() asm volatile("" ::: "memory") +#endif + #include <time.h> #include <unistd.h> #include <sys/types.h> diff --git a/tools/perf/scripts/perl/bin/failed-syscalls-report b/tools/perf/scripts/perl/bin/failed-syscalls-report index e3a5e55d54ff..4028d92dc4ae 100644 --- a/tools/perf/scripts/perl/bin/failed-syscalls-report +++ b/tools/perf/scripts/perl/bin/failed-syscalls-report @@ -7,4 +7,4 @@ if [ $# -gt 0 ] ; then shift fi fi -perf trace $@ -s ~/libexec/perf-core/scripts/perl/failed-syscalls.pl $comm +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/failed-syscalls.pl $comm diff --git a/tools/perf/scripts/perl/bin/rw-by-file-report b/tools/perf/scripts/perl/bin/rw-by-file-report index d83070b7eeb5..ba25f4d41fb0 100644 --- a/tools/perf/scripts/perl/bin/rw-by-file-report +++ b/tools/perf/scripts/perl/bin/rw-by-file-report @@ -7,7 +7,7 @@ if [ $# -lt 1 ] ; then fi comm=$1 shift -perf trace $@ -s ~/libexec/perf-core/scripts/perl/rw-by-file.pl $comm +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-file.pl $comm diff --git a/tools/perf/scripts/perl/bin/rw-by-pid-report b/tools/perf/scripts/perl/bin/rw-by-pid-report index 7ef46983f62f..641a3f5d085c 100644 --- a/tools/perf/scripts/perl/bin/rw-by-pid-report +++ b/tools/perf/scripts/perl/bin/rw-by-pid-report @@ -1,6 +1,6 @@ #!/bin/bash # description: system-wide r/w activity -perf trace $@ -s ~/libexec/perf-core/scripts/perl/rw-by-pid.pl +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-pid.pl diff --git a/tools/perf/scripts/perl/bin/rwtop-report b/tools/perf/scripts/perl/bin/rwtop-report index 93e698cd3f38..4918dba77021 100644 --- a/tools/perf/scripts/perl/bin/rwtop-report +++ b/tools/perf/scripts/perl/bin/rwtop-report @@ -17,7 +17,7 @@ if [ "$n_args" -gt 0 ] ; then interval=$1 shift fi -perf trace $@ -s ~/libexec/perf-core/scripts/perl/rwtop.pl $interval +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/rwtop.pl $interval diff --git a/tools/perf/scripts/perl/bin/wakeup-latency-report b/tools/perf/scripts/perl/bin/wakeup-latency-report index a0d898f9ca1d..49052ebcb632 100644 --- a/tools/perf/scripts/perl/bin/wakeup-latency-report +++ b/tools/perf/scripts/perl/bin/wakeup-latency-report @@ -1,6 +1,6 @@ #!/bin/bash # description: system-wide min/max/avg wakeup latency -perf trace $@ -s ~/libexec/perf-core/scripts/perl/wakeup-latency.pl +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/wakeup-latency.pl diff --git a/tools/perf/scripts/perl/bin/workqueue-stats-report b/tools/perf/scripts/perl/bin/workqueue-stats-report index 35081132ef97..df0c65f4ca93 100644 --- a/tools/perf/scripts/perl/bin/workqueue-stats-report +++ b/tools/perf/scripts/perl/bin/workqueue-stats-report @@ -1,6 +1,6 @@ #!/bin/bash # description: workqueue stats (ins/exe/create/destroy) -perf trace $@ -s ~/libexec/perf-core/scripts/perl/workqueue-stats.pl +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/workqueue-stats.pl diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py index 1dc464ee2ca8..aad7525bca1d 100644 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py +++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py @@ -89,3 +89,33 @@ def trace_flag_str(value): value &= ~idx return string + + +def taskState(state): + states = { + 0 : "R", + 1 : "S", + 2 : "D", + 64: "DEAD" + } + + if state not in states: + return "Unknown" + + return states[state] + + +class EventHeaders: + def __init__(self, common_cpu, common_secs, common_nsecs, + common_pid, common_comm): + self.cpu = common_cpu + self.secs = common_secs + self.nsecs = common_nsecs + self.pid = common_pid + self.comm = common_comm + + def ts(self): + return (self.secs * (10 ** 9)) + self.nsecs + + def ts_format(self): + return "%d.%d" % (self.secs, int(self.nsecs / 1000)) diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py new file mode 100644 index 000000000000..ae9a56e43e05 --- /dev/null +++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py @@ -0,0 +1,184 @@ +# SchedGui.py - Python extension for perf trace, basic GUI code for +# traces drawing and overview. +# +# Copyright (C) 2010 by Frederic Weisbecker <fweisbec@gmail.com> +# +# This software is distributed under the terms of the GNU General +# Public License ("GPL") version 2 as published by the Free Software +# Foundation. + + +try: + import wx +except ImportError: + raise ImportError, "You need to install the wxpython lib for this script" + + +class RootFrame(wx.Frame): + Y_OFFSET = 100 + RECT_HEIGHT = 100 + RECT_SPACE = 50 + EVENT_MARKING_WIDTH = 5 + + def __init__(self, sched_tracer, title, parent = None, id = -1): + wx.Frame.__init__(self, parent, id, title) + + (self.screen_width, self.screen_height) = wx.GetDisplaySize() + self.screen_width -= 10 + self.screen_height -= 10 + self.zoom = 0.5 + self.scroll_scale = 20 + self.sched_tracer = sched_tracer + self.sched_tracer.set_root_win(self) + (self.ts_start, self.ts_end) = sched_tracer.interval() + self.update_width_virtual() + self.nr_rects = sched_tracer.nr_rectangles() + 1 + self.height_virtual = RootFrame.Y_OFFSET + (self.nr_rects * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)) + + # whole window panel + self.panel = wx.Panel(self, size=(self.screen_width, self.screen_height)) + + # scrollable container + self.scroll = wx.ScrolledWindow(self.panel) + self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale) + self.scroll.EnableScrolling(True, True) + self.scroll.SetFocus() + + # scrollable drawing area + self.scroll_panel = wx.Panel(self.scroll, size=(self.screen_width - 15, self.screen_height / 2)) + self.scroll_panel.Bind(wx.EVT_PAINT, self.on_paint) + self.scroll_panel.Bind(wx.EVT_KEY_DOWN, self.on_key_press) + self.scroll_panel.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down) + self.scroll.Bind(wx.EVT_PAINT, self.on_paint) + self.scroll.Bind(wx.EVT_KEY_DOWN, self.on_key_press) + self.scroll.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down) + + self.scroll.Fit() + self.Fit() + + self.scroll_panel.SetDimensions(-1, -1, self.width_virtual, self.height_virtual, wx.SIZE_USE_EXISTING) + + self.txt = None + + self.Show(True) + + def us_to_px(self, val): + return val / (10 ** 3) * self.zoom + + def px_to_us(self, val): + return (val / self.zoom) * (10 ** 3) + + def scroll_start(self): + (x, y) = self.scroll.GetViewStart() + return (x * self.scroll_scale, y * self.scroll_scale) + + def scroll_start_us(self): + (x, y) = self.scroll_start() + return self.px_to_us(x) + + def paint_rectangle_zone(self, nr, color, top_color, start, end): + offset_px = self.us_to_px(start - self.ts_start) + width_px = self.us_to_px(end - self.ts_start) + + offset_py = RootFrame.Y_OFFSET + (nr * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)) + width_py = RootFrame.RECT_HEIGHT + + dc = self.dc + + if top_color is not None: + (r, g, b) = top_color + top_color = wx.Colour(r, g, b) + brush = wx.Brush(top_color, wx.SOLID) + dc.SetBrush(brush) + dc.DrawRectangle(offset_px, offset_py, width_px, RootFrame.EVENT_MARKING_WIDTH) + width_py -= RootFrame.EVENT_MARKING_WIDTH + offset_py += RootFrame.EVENT_MARKING_WIDTH + + (r ,g, b) = color + color = wx.Colour(r, g, b) + brush = wx.Brush(color, wx.SOLID) + dc.SetBrush(brush) + dc.DrawRectangle(offset_px, offset_py, width_px, width_py) + + def update_rectangles(self, dc, start, end): + start += self.ts_start + end += self.ts_start + self.sched_tracer.fill_zone(start, end) + + def on_paint(self, event): + dc = wx.PaintDC(self.scroll_panel) + self.dc = dc + + width = min(self.width_virtual, self.screen_width) + (x, y) = self.scroll_start() + start = self.px_to_us(x) + end = self.px_to_us(x + width) + self.update_rectangles(dc, start, end) + + def rect_from_ypixel(self, y): + y -= RootFrame.Y_OFFSET + rect = y / (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE) + height = y % (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE) + + if rect < 0 or rect > self.nr_rects - 1 or height > RootFrame.RECT_HEIGHT: + return -1 + + return rect + + def update_summary(self, txt): + if self.txt: + self.txt.Destroy() + self.txt = wx.StaticText(self.panel, -1, txt, (0, (self.screen_height / 2) + 50)) + + + def on_mouse_down(self, event): + (x, y) = event.GetPositionTuple() + rect = self.rect_from_ypixel(y) + if rect == -1: + return + + t = self.px_to_us(x) + self.ts_start + + self.sched_tracer.mouse_down(rect, t) + + + def update_width_virtual(self): + self.width_virtual = self.us_to_px(self.ts_end - self.ts_start) + + def __zoom(self, x): + self.update_width_virtual() + (xpos, ypos) = self.scroll.GetViewStart() + xpos = self.us_to_px(x) / self.scroll_scale + self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale, xpos, ypos) + self.Refresh() + + def zoom_in(self): + x = self.scroll_start_us() + self.zoom *= 2 + self.__zoom(x) + + def zoom_out(self): + x = self.scroll_start_us() + self.zoom /= 2 + self.__zoom(x) + + + def on_key_press(self, event): + key = event.GetRawKeyCode() + if key == ord("+"): + self.zoom_in() + return + if key == ord("-"): + self.zoom_out() + return + + key = event.GetKeyCode() + (x, y) = self.scroll.GetViewStart() + if key == wx.WXK_RIGHT: + self.scroll.Scroll(x + 1, y) + elif key == wx.WXK_LEFT: + self.scroll.Scroll(x - 1, y) + elif key == wx.WXK_DOWN: + self.scroll.Scroll(x, y + 1) + elif key == wx.WXK_UP: + self.scroll.Scroll(x, y - 1) diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py index 9689bc0acd9f..13cc02b5893a 100644 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py +++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py @@ -6,6 +6,14 @@ # Public License ("GPL") version 2 as published by the Free Software # Foundation. +import errno, os + +FUTEX_WAIT = 0 +FUTEX_WAKE = 1 +FUTEX_PRIVATE_FLAG = 128 +FUTEX_CLOCK_REALTIME = 256 +FUTEX_CMD_MASK = ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) + NSECS_PER_SEC = 1000000000 def avg(total, n): @@ -24,5 +32,55 @@ def nsecs_str(nsecs): str = "%5u.%09u" % (nsecs_secs(nsecs), nsecs_nsecs(nsecs)), return str +def add_stats(dict, key, value): + if not dict.has_key(key): + dict[key] = (value, value, value, 1) + else: + min, max, avg, count = dict[key] + if value < min: + min = value + if value > max: + max = value + avg = (avg + value) / 2 + dict[key] = (min, max, avg, count + 1) + def clear_term(): print("\x1b[H\x1b[2J") + +audit_package_warned = False + +try: + import audit + machine_to_id = { + 'x86_64': audit.MACH_86_64, + 'alpha' : audit.MACH_ALPHA, + 'ia64' : audit.MACH_IA64, + 'ppc' : audit.MACH_PPC, + 'ppc64' : audit.MACH_PPC64, + 's390' : audit.MACH_S390, + 's390x' : audit.MACH_S390X, + 'i386' : audit.MACH_X86, + 'i586' : audit.MACH_X86, + 'i686' : audit.MACH_X86, + } + try: + machine_to_id['armeb'] = audit.MACH_ARMEB + except: + pass + machine_id = machine_to_id[os.uname()[4]] +except: + if not audit_package_warned: + audit_package_warned = True + print "Install the audit-libs-python package to get syscall names" + +def syscall_name(id): + try: + return audit.audit_syscall_to_name(id, machine_id) + except: + return str(id) + +def strerror(nr): + try: + return errno.errorcode[abs(nr)] + except: + return "Unknown %d errno" % nr diff --git a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report index 30293545fcc2..03587021463d 100644 --- a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report +++ b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report @@ -7,4 +7,4 @@ if [ $# -gt 0 ] ; then shift fi fi -perf trace $@ -s ~/libexec/perf-core/scripts/python/failed-syscalls-by-pid.py $comm +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/failed-syscalls-by-pid.py $comm diff --git a/tools/perf/scripts/python/bin/futex-contention-record b/tools/perf/scripts/python/bin/futex-contention-record new file mode 100644 index 000000000000..5ecbb433caf4 --- /dev/null +++ b/tools/perf/scripts/python/bin/futex-contention-record @@ -0,0 +1,2 @@ +#!/bin/bash +perf record -a -e syscalls:sys_enter_futex -e syscalls:sys_exit_futex $@ diff --git a/tools/perf/scripts/python/bin/futex-contention-report b/tools/perf/scripts/python/bin/futex-contention-report new file mode 100644 index 000000000000..c8268138fb7e --- /dev/null +++ b/tools/perf/scripts/python/bin/futex-contention-report @@ -0,0 +1,4 @@ +#!/bin/bash +# description: futext contention measurement + +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/futex-contention.py diff --git a/tools/perf/scripts/python/bin/netdev-times-record b/tools/perf/scripts/python/bin/netdev-times-record new file mode 100644 index 000000000000..d931a828126b --- /dev/null +++ b/tools/perf/scripts/python/bin/netdev-times-record @@ -0,0 +1,8 @@ +#!/bin/bash +perf record -a -e net:net_dev_xmit -e net:net_dev_queue \ + -e net:netif_receive_skb -e net:netif_rx \ + -e skb:consume_skb -e skb:kfree_skb \ + -e skb:skb_copy_datagram_iovec -e napi:napi_poll \ + -e irq:irq_handler_entry -e irq:irq_handler_exit \ + -e irq:softirq_entry -e irq:softirq_exit \ + -e irq:softirq_raise $@ diff --git a/tools/perf/scripts/python/bin/netdev-times-report b/tools/perf/scripts/python/bin/netdev-times-report new file mode 100644 index 000000000000..4ad361b31249 --- /dev/null +++ b/tools/perf/scripts/python/bin/netdev-times-report @@ -0,0 +1,5 @@ +#!/bin/bash +# description: display a process of packet and processing time +# args: [tx] [rx] [dev=] [debug] + +perf trace -s "$PERF_EXEC_PATH"/scripts/python/netdev-times.py $@ diff --git a/tools/perf/scripts/python/bin/sched-migration-record b/tools/perf/scripts/python/bin/sched-migration-record new file mode 100644 index 000000000000..17a3e9bd9e8f --- /dev/null +++ b/tools/perf/scripts/python/bin/sched-migration-record @@ -0,0 +1,2 @@ +#!/bin/bash +perf record -m 16384 -a -e sched:sched_wakeup -e sched:sched_wakeup_new -e sched:sched_switch -e sched:sched_migrate_task $@ diff --git a/tools/perf/scripts/python/bin/sched-migration-report b/tools/perf/scripts/python/bin/sched-migration-report new file mode 100644 index 000000000000..df1791f07c24 --- /dev/null +++ b/tools/perf/scripts/python/bin/sched-migration-report @@ -0,0 +1,3 @@ +#!/bin/bash +# description: sched migration overview +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/sched-migration.py diff --git a/tools/perf/scripts/python/bin/sctop-report b/tools/perf/scripts/python/bin/sctop-report index b01c842ae7b4..36b409c05e50 100644 --- a/tools/perf/scripts/python/bin/sctop-report +++ b/tools/perf/scripts/python/bin/sctop-report @@ -21,4 +21,4 @@ elif [ "$n_args" -gt 0 ] ; then interval=$1 shift fi -perf trace $@ -s ~/libexec/perf-core/scripts/python/sctop.py $comm $interval +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/sctop.py $comm $interval diff --git a/tools/perf/scripts/python/bin/syscall-counts-by-pid-report b/tools/perf/scripts/python/bin/syscall-counts-by-pid-report index 9e9d8ddd72ce..4eb88c9fc83c 100644 --- a/tools/perf/scripts/python/bin/syscall-counts-by-pid-report +++ b/tools/perf/scripts/python/bin/syscall-counts-by-pid-report @@ -7,4 +7,4 @@ if [ $# -gt 0 ] ; then shift fi fi -perf trace $@ -s ~/libexec/perf-core/scripts/python/syscall-counts-by-pid.py $comm +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts-by-pid.py $comm diff --git a/tools/perf/scripts/python/bin/syscall-counts-report b/tools/perf/scripts/python/bin/syscall-counts-report index dc076b618796..cb2f9c5cf17e 100644 --- a/tools/perf/scripts/python/bin/syscall-counts-report +++ b/tools/perf/scripts/python/bin/syscall-counts-report @@ -7,4 +7,4 @@ if [ $# -gt 0 ] ; then shift fi fi -perf trace $@ -s ~/libexec/perf-core/scripts/python/syscall-counts.py $comm +perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts.py $comm diff --git a/tools/perf/scripts/python/failed-syscalls-by-pid.py b/tools/perf/scripts/python/failed-syscalls-by-pid.py index 0ca02278fe69..acd7848717b3 100644 --- a/tools/perf/scripts/python/failed-syscalls-by-pid.py +++ b/tools/perf/scripts/python/failed-syscalls-by-pid.py @@ -13,21 +13,26 @@ sys.path.append(os.environ['PERF_EXEC_PATH'] + \ from perf_trace_context import * from Core import * +from Util import * -usage = "perf trace -s syscall-counts-by-pid.py [comm]\n"; +usage = "perf trace -s syscall-counts-by-pid.py [comm|pid]\n"; for_comm = None +for_pid = None if len(sys.argv) > 2: sys.exit(usage) if len(sys.argv) > 1: - for_comm = sys.argv[1] + try: + for_pid = int(sys.argv[1]) + except: + for_comm = sys.argv[1] syscalls = autodict() def trace_begin(): - pass + print "Press control+C to stop and show the summary" def trace_end(): print_error_totals() @@ -35,9 +40,9 @@ def trace_end(): def raw_syscalls__sys_exit(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, id, ret): - if for_comm is not None: - if common_comm != for_comm: - return + if (for_comm and common_comm != for_comm) or \ + (for_pid and common_pid != for_pid ): + return if ret < 0: try: @@ -62,7 +67,7 @@ def print_error_totals(): print "\n%s [%d]\n" % (comm, pid), id_keys = syscalls[comm][pid].keys() for id in id_keys: - print " syscall: %-16d\n" % (id), + print " syscall: %-16s\n" % syscall_name(id), ret_keys = syscalls[comm][pid][id].keys() for ret, val in sorted(syscalls[comm][pid][id].iteritems(), key = lambda(k, v): (v, k), reverse = True): - print " err = %-20d %10d\n" % (ret, val), + print " err = %-20s %10d\n" % (strerror(ret), val), diff --git a/tools/perf/scripts/python/futex-contention.py b/tools/perf/scripts/python/futex-contention.py new file mode 100644 index 000000000000..11e70a388d41 --- /dev/null +++ b/tools/perf/scripts/python/futex-contention.py @@ -0,0 +1,50 @@ +# futex contention +# (c) 2010, Arnaldo Carvalho de Melo <acme@redhat.com> +# Licensed under the terms of the GNU GPL License version 2 +# +# Translation of: +# +# http://sourceware.org/systemtap/wiki/WSFutexContention +# +# to perf python scripting. +# +# Measures futex contention + +import os, sys +sys.path.append(os.environ['PERF_EXEC_PATH'] + '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') +from Util import * + +process_names = {} +thread_thislock = {} +thread_blocktime = {} + +lock_waits = {} # long-lived stats on (tid,lock) blockage elapsed time +process_names = {} # long-lived pid-to-execname mapping + +def syscalls__sys_enter_futex(event, ctxt, cpu, s, ns, tid, comm, + nr, uaddr, op, val, utime, uaddr2, val3): + cmd = op & FUTEX_CMD_MASK + if cmd != FUTEX_WAIT: + return # we don't care about originators of WAKE events + + process_names[tid] = comm + thread_thislock[tid] = uaddr + thread_blocktime[tid] = nsecs(s, ns) + +def syscalls__sys_exit_futex(event, ctxt, cpu, s, ns, tid, comm, + nr, ret): + if thread_blocktime.has_key(tid): + elapsed = nsecs(s, ns) - thread_blocktime[tid] + add_stats(lock_waits, (tid, thread_thislock[tid]), elapsed) + del thread_blocktime[tid] + del thread_thislock[tid] + +def trace_begin(): + print "Press control+C to stop and show the summary" + +def trace_end(): + for (tid, lock) in lock_waits: + min, max, avg, count = lock_waits[tid, lock] + print "%s[%d] lock %x contended %d times, %d avg ns" % \ + (process_names[tid], tid, lock, count, avg) + diff --git a/tools/perf/scripts/python/netdev-times.py b/tools/perf/scripts/python/netdev-times.py new file mode 100644 index 000000000000..9aa0a32972e8 --- /dev/null +++ b/tools/perf/scripts/python/netdev-times.py @@ -0,0 +1,464 @@ +# Display a process of packets and processed time. +# It helps us to investigate networking or network device. +# +# options +# tx: show only tx chart +# rx: show only rx chart +# dev=: show only thing related to specified device +# debug: work with debug mode. It shows buffer status. + +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 * + +all_event_list = []; # insert all tracepoint event related with this script +irq_dic = {}; # key is cpu and value is a list which stacks irqs + # which raise NET_RX softirq +net_rx_dic = {}; # key is cpu and value include time of NET_RX softirq-entry + # and a list which stacks receive +receive_hunk_list = []; # a list which include a sequence of receive events +rx_skb_list = []; # received packet list for matching + # skb_copy_datagram_iovec + +buffer_budget = 65536; # the budget of rx_skb_list, tx_queue_list and + # tx_xmit_list +of_count_rx_skb_list = 0; # overflow count + +tx_queue_list = []; # list of packets which pass through dev_queue_xmit +of_count_tx_queue_list = 0; # overflow count + +tx_xmit_list = []; # list of packets which pass through dev_hard_start_xmit +of_count_tx_xmit_list = 0; # overflow count + +tx_free_list = []; # list of packets which is freed + +# options +show_tx = 0; +show_rx = 0; +dev = 0; # store a name of device specified by option "dev=" +debug = 0; + +# indices of event_info tuple +EINFO_IDX_NAME= 0 +EINFO_IDX_CONTEXT=1 +EINFO_IDX_CPU= 2 +EINFO_IDX_TIME= 3 +EINFO_IDX_PID= 4 +EINFO_IDX_COMM= 5 + +# Calculate a time interval(msec) from src(nsec) to dst(nsec) +def diff_msec(src, dst): + return (dst - src) / 1000000.0 + +# Display a process of transmitting a packet +def print_transmit(hunk): + if dev != 0 and hunk['dev'].find(dev) < 0: + return + print "%7s %5d %6d.%06dsec %12.3fmsec %12.3fmsec" % \ + (hunk['dev'], hunk['len'], + nsecs_secs(hunk['queue_t']), + nsecs_nsecs(hunk['queue_t'])/1000, + diff_msec(hunk['queue_t'], hunk['xmit_t']), + diff_msec(hunk['xmit_t'], hunk['free_t'])) + +# Format for displaying rx packet processing +PF_IRQ_ENTRY= " irq_entry(+%.3fmsec irq=%d:%s)" +PF_SOFT_ENTRY=" softirq_entry(+%.3fmsec)" +PF_NAPI_POLL= " napi_poll_exit(+%.3fmsec %s)" +PF_JOINT= " |" +PF_WJOINT= " | |" +PF_NET_RECV= " |---netif_receive_skb(+%.3fmsec skb=%x len=%d)" +PF_NET_RX= " |---netif_rx(+%.3fmsec skb=%x)" +PF_CPY_DGRAM= " | skb_copy_datagram_iovec(+%.3fmsec %d:%s)" +PF_KFREE_SKB= " | kfree_skb(+%.3fmsec location=%x)" +PF_CONS_SKB= " | consume_skb(+%.3fmsec)" + +# Display a process of received packets and interrputs associated with +# a NET_RX softirq +def print_receive(hunk): + show_hunk = 0 + irq_list = hunk['irq_list'] + cpu = irq_list[0]['cpu'] + base_t = irq_list[0]['irq_ent_t'] + # check if this hunk should be showed + if dev != 0: + for i in range(len(irq_list)): + if irq_list[i]['name'].find(dev) >= 0: + show_hunk = 1 + break + else: + show_hunk = 1 + if show_hunk == 0: + return + + print "%d.%06dsec cpu=%d" % \ + (nsecs_secs(base_t), nsecs_nsecs(base_t)/1000, cpu) + for i in range(len(irq_list)): + print PF_IRQ_ENTRY % \ + (diff_msec(base_t, irq_list[i]['irq_ent_t']), + irq_list[i]['irq'], irq_list[i]['name']) + print PF_JOINT + irq_event_list = irq_list[i]['event_list'] + for j in range(len(irq_event_list)): + irq_event = irq_event_list[j] + if irq_event['event'] == 'netif_rx': + print PF_NET_RX % \ + (diff_msec(base_t, irq_event['time']), + irq_event['skbaddr']) + print PF_JOINT + print PF_SOFT_ENTRY % \ + diff_msec(base_t, hunk['sirq_ent_t']) + print PF_JOINT + event_list = hunk['event_list'] + for i in range(len(event_list)): + event = event_list[i] + if event['event_name'] == 'napi_poll': + print PF_NAPI_POLL % \ + (diff_msec(base_t, event['event_t']), event['dev']) + if i == len(event_list) - 1: + print "" + else: + print PF_JOINT + else: + print PF_NET_RECV % \ + (diff_msec(base_t, event['event_t']), event['skbaddr'], + event['len']) + if 'comm' in event.keys(): + print PF_WJOINT + print PF_CPY_DGRAM % \ + (diff_msec(base_t, event['comm_t']), + event['pid'], event['comm']) + elif 'handle' in event.keys(): + print PF_WJOINT + if event['handle'] == "kfree_skb": + print PF_KFREE_SKB % \ + (diff_msec(base_t, + event['comm_t']), + event['location']) + elif event['handle'] == "consume_skb": + print PF_CONS_SKB % \ + diff_msec(base_t, + event['comm_t']) + print PF_JOINT + +def trace_begin(): + global show_tx + global show_rx + global dev + global debug + + for i in range(len(sys.argv)): + if i == 0: + continue + arg = sys.argv[i] + if arg == 'tx': + show_tx = 1 + elif arg =='rx': + show_rx = 1 + elif arg.find('dev=',0, 4) >= 0: + dev = arg[4:] + elif arg == 'debug': + debug = 1 + if show_tx == 0 and show_rx == 0: + show_tx = 1 + show_rx = 1 + +def trace_end(): + # order all events in time + all_event_list.sort(lambda a,b :cmp(a[EINFO_IDX_TIME], + b[EINFO_IDX_TIME])) + # process all events + for i in range(len(all_event_list)): + event_info = all_event_list[i] + name = event_info[EINFO_IDX_NAME] + if name == 'irq__softirq_exit': + handle_irq_softirq_exit(event_info) + elif name == 'irq__softirq_entry': + handle_irq_softirq_entry(event_info) + elif name == 'irq__softirq_raise': + handle_irq_softirq_raise(event_info) + elif name == 'irq__irq_handler_entry': + handle_irq_handler_entry(event_info) + elif name == 'irq__irq_handler_exit': + handle_irq_handler_exit(event_info) + elif name == 'napi__napi_poll': + handle_napi_poll(event_info) + elif name == 'net__netif_receive_skb': + handle_netif_receive_skb(event_info) + elif name == 'net__netif_rx': + handle_netif_rx(event_info) + elif name == 'skb__skb_copy_datagram_iovec': + handle_skb_copy_datagram_iovec(event_info) + elif name == 'net__net_dev_queue': + handle_net_dev_queue(event_info) + elif name == 'net__net_dev_xmit': + handle_net_dev_xmit(event_info) + elif name == 'skb__kfree_skb': + handle_kfree_skb(event_info) + elif name == 'skb__consume_skb': + handle_consume_skb(event_info) + # display receive hunks + if show_rx: + for i in range(len(receive_hunk_list)): + print_receive(receive_hunk_list[i]) + # display transmit hunks + if show_tx: + print " dev len Qdisc " \ + " netdevice free" + for i in range(len(tx_free_list)): + print_transmit(tx_free_list[i]) + if debug: + print "debug buffer status" + print "----------------------------" + print "xmit Qdisc:remain:%d overflow:%d" % \ + (len(tx_queue_list), of_count_tx_queue_list) + print "xmit netdevice:remain:%d overflow:%d" % \ + (len(tx_xmit_list), of_count_tx_xmit_list) + print "receive:remain:%d overflow:%d" % \ + (len(rx_skb_list), of_count_rx_skb_list) + +# called from perf, when it finds a correspoinding event +def irq__softirq_entry(name, context, cpu, sec, nsec, pid, comm, vec): + if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX": + return + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec) + all_event_list.append(event_info) + +def irq__softirq_exit(name, context, cpu, sec, nsec, pid, comm, vec): + if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX": + return + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec) + all_event_list.append(event_info) + +def irq__softirq_raise(name, context, cpu, sec, nsec, pid, comm, vec): + if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX": + return + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec) + all_event_list.append(event_info) + +def irq__irq_handler_entry(name, context, cpu, sec, nsec, pid, comm, + irq, irq_name): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, + irq, irq_name) + all_event_list.append(event_info) + +def irq__irq_handler_exit(name, context, cpu, sec, nsec, pid, comm, irq, ret): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, irq, ret) + all_event_list.append(event_info) + +def napi__napi_poll(name, context, cpu, sec, nsec, pid, comm, napi, dev_name): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, + napi, dev_name) + all_event_list.append(event_info) + +def net__netif_receive_skb(name, context, cpu, sec, nsec, pid, comm, skbaddr, + skblen, dev_name): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, + skbaddr, skblen, dev_name) + all_event_list.append(event_info) + +def net__netif_rx(name, context, cpu, sec, nsec, pid, comm, skbaddr, + skblen, dev_name): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, + skbaddr, skblen, dev_name) + all_event_list.append(event_info) + +def net__net_dev_queue(name, context, cpu, sec, nsec, pid, comm, + skbaddr, skblen, dev_name): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, + skbaddr, skblen, dev_name) + all_event_list.append(event_info) + +def net__net_dev_xmit(name, context, cpu, sec, nsec, pid, comm, + skbaddr, skblen, rc, dev_name): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, + skbaddr, skblen, rc ,dev_name) + all_event_list.append(event_info) + +def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, + skbaddr, protocol, location): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, + skbaddr, protocol, location) + all_event_list.append(event_info) + +def skb__consume_skb(name, context, cpu, sec, nsec, pid, comm, skbaddr): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, + skbaddr) + all_event_list.append(event_info) + +def skb__skb_copy_datagram_iovec(name, context, cpu, sec, nsec, pid, comm, + skbaddr, skblen): + event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, + skbaddr, skblen) + all_event_list.append(event_info) + +def handle_irq_handler_entry(event_info): + (name, context, cpu, time, pid, comm, irq, irq_name) = event_info + if cpu not in irq_dic.keys(): + irq_dic[cpu] = [] + irq_record = {'irq':irq, 'name':irq_name, 'cpu':cpu, 'irq_ent_t':time} + irq_dic[cpu].append(irq_record) + +def handle_irq_handler_exit(event_info): + (name, context, cpu, time, pid, comm, irq, ret) = event_info + if cpu not in irq_dic.keys(): + return + irq_record = irq_dic[cpu].pop() + if irq != irq_record['irq']: + return + irq_record.update({'irq_ext_t':time}) + # if an irq doesn't include NET_RX softirq, drop. + if 'event_list' in irq_record.keys(): + irq_dic[cpu].append(irq_record) + +def handle_irq_softirq_raise(event_info): + (name, context, cpu, time, pid, comm, vec) = event_info + if cpu not in irq_dic.keys() \ + or len(irq_dic[cpu]) == 0: + return + irq_record = irq_dic[cpu].pop() + if 'event_list' in irq_record.keys(): + irq_event_list = irq_record['event_list'] + else: + irq_event_list = [] + irq_event_list.append({'time':time, 'event':'sirq_raise'}) + irq_record.update({'event_list':irq_event_list}) + irq_dic[cpu].append(irq_record) + +def handle_irq_softirq_entry(event_info): + (name, context, cpu, time, pid, comm, vec) = event_info + net_rx_dic[cpu] = {'sirq_ent_t':time, 'event_list':[]} + +def handle_irq_softirq_exit(event_info): + (name, context, cpu, time, pid, comm, vec) = event_info + irq_list = [] + event_list = 0 + if cpu in irq_dic.keys(): + irq_list = irq_dic[cpu] + del irq_dic[cpu] + if cpu in net_rx_dic.keys(): + sirq_ent_t = net_rx_dic[cpu]['sirq_ent_t'] + event_list = net_rx_dic[cpu]['event_list'] + del net_rx_dic[cpu] + if irq_list == [] or event_list == 0: + return + rec_data = {'sirq_ent_t':sirq_ent_t, 'sirq_ext_t':time, + 'irq_list':irq_list, 'event_list':event_list} + # merge information realted to a NET_RX softirq + receive_hunk_list.append(rec_data) + +def handle_napi_poll(event_info): + (name, context, cpu, time, pid, comm, napi, dev_name) = event_info + if cpu in net_rx_dic.keys(): + event_list = net_rx_dic[cpu]['event_list'] + rec_data = {'event_name':'napi_poll', + 'dev':dev_name, 'event_t':time} + event_list.append(rec_data) + +def handle_netif_rx(event_info): + (name, context, cpu, time, pid, comm, + skbaddr, skblen, dev_name) = event_info + if cpu not in irq_dic.keys() \ + or len(irq_dic[cpu]) == 0: + return + irq_record = irq_dic[cpu].pop() + if 'event_list' in irq_record.keys(): + irq_event_list = irq_record['event_list'] + else: + irq_event_list = [] + irq_event_list.append({'time':time, 'event':'netif_rx', + 'skbaddr':skbaddr, 'skblen':skblen, 'dev_name':dev_name}) + irq_record.update({'event_list':irq_event_list}) + irq_dic[cpu].append(irq_record) + +def handle_netif_receive_skb(event_info): + global of_count_rx_skb_list + + (name, context, cpu, time, pid, comm, + skbaddr, skblen, dev_name) = event_info + if cpu in net_rx_dic.keys(): + rec_data = {'event_name':'netif_receive_skb', + 'event_t':time, 'skbaddr':skbaddr, 'len':skblen} + event_list = net_rx_dic[cpu]['event_list'] + event_list.append(rec_data) + rx_skb_list.insert(0, rec_data) + if len(rx_skb_list) > buffer_budget: + rx_skb_list.pop() + of_count_rx_skb_list += 1 + +def handle_net_dev_queue(event_info): + global of_count_tx_queue_list + + (name, context, cpu, time, pid, comm, + skbaddr, skblen, dev_name) = event_info + skb = {'dev':dev_name, 'skbaddr':skbaddr, 'len':skblen, 'queue_t':time} + tx_queue_list.insert(0, skb) + if len(tx_queue_list) > buffer_budget: + tx_queue_list.pop() + of_count_tx_queue_list += 1 + +def handle_net_dev_xmit(event_info): + global of_count_tx_xmit_list + + (name, context, cpu, time, pid, comm, + skbaddr, skblen, rc, dev_name) = event_info + if rc == 0: # NETDEV_TX_OK + for i in range(len(tx_queue_list)): + skb = tx_queue_list[i] + if skb['skbaddr'] == skbaddr: + skb['xmit_t'] = time + tx_xmit_list.insert(0, skb) + del tx_queue_list[i] + if len(tx_xmit_list) > buffer_budget: + tx_xmit_list.pop() + of_count_tx_xmit_list += 1 + return + +def handle_kfree_skb(event_info): + (name, context, cpu, time, pid, comm, + skbaddr, protocol, location) = event_info + for i in range(len(tx_queue_list)): + skb = tx_queue_list[i] + if skb['skbaddr'] == skbaddr: + del tx_queue_list[i] + return + for i in range(len(tx_xmit_list)): + skb = tx_xmit_list[i] + if skb['skbaddr'] == skbaddr: + skb['free_t'] = time + tx_free_list.append(skb) + del tx_xmit_list[i] + return + for i in range(len(rx_skb_list)): + rec_data = rx_skb_list[i] + if rec_data['skbaddr'] == skbaddr: + rec_data.update({'handle':"kfree_skb", + 'comm':comm, 'pid':pid, 'comm_t':time}) + del rx_skb_list[i] + return + +def handle_consume_skb(event_info): + (name, context, cpu, time, pid, comm, skbaddr) = event_info + for i in range(len(tx_xmit_list)): + skb = tx_xmit_list[i] + if skb['skbaddr'] == skbaddr: + skb['free_t'] = time + tx_free_list.append(skb) + del tx_xmit_list[i] + return + +def handle_skb_copy_datagram_iovec(event_info): + (name, context, cpu, time, pid, comm, skbaddr, skblen) = event_info + for i in range(len(rx_skb_list)): + rec_data = rx_skb_list[i] + if skbaddr == rec_data['skbaddr']: + rec_data.update({'handle':"skb_copy_datagram_iovec", + 'comm':comm, 'pid':pid, 'comm_t':time}) + del rx_skb_list[i] + return diff --git a/tools/perf/scripts/python/sched-migration.py b/tools/perf/scripts/python/sched-migration.py new file mode 100644 index 000000000000..b934383c3364 --- /dev/null +++ b/tools/perf/scripts/python/sched-migration.py @@ -0,0 +1,461 @@ +#!/usr/bin/python +# +# Cpu task migration overview toy +# +# Copyright (C) 2010 Frederic Weisbecker <fweisbec@gmail.com> +# +# perf trace event handlers have been generated by perf trace -g python +# +# This software is distributed under the terms of the GNU General +# Public License ("GPL") version 2 as published by the Free Software +# Foundation. + + +import os +import sys + +from collections import defaultdict +from UserList import UserList + +sys.path.append(os.environ['PERF_EXEC_PATH'] + \ + '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') +sys.path.append('scripts/python/Perf-Trace-Util/lib/Perf/Trace') + +from perf_trace_context import * +from Core import * +from SchedGui import * + + +threads = { 0 : "idle"} + +def thread_name(pid): + return "%s:%d" % (threads[pid], pid) + +class RunqueueEventUnknown: + @staticmethod + def color(): + return None + + def __repr__(self): + return "unknown" + +class RunqueueEventSleep: + @staticmethod + def color(): + return (0, 0, 0xff) + + def __init__(self, sleeper): + self.sleeper = sleeper + + def __repr__(self): + return "%s gone to sleep" % thread_name(self.sleeper) + +class RunqueueEventWakeup: + @staticmethod + def color(): + return (0xff, 0xff, 0) + + def __init__(self, wakee): + self.wakee = wakee + + def __repr__(self): + return "%s woke up" % thread_name(self.wakee) + +class RunqueueEventFork: + @staticmethod + def color(): + return (0, 0xff, 0) + + def __init__(self, child): + self.child = child + + def __repr__(self): + return "new forked task %s" % thread_name(self.child) + +class RunqueueMigrateIn: + @staticmethod + def color(): + return (0, 0xf0, 0xff) + + def __init__(self, new): + self.new = new + + def __repr__(self): + return "task migrated in %s" % thread_name(self.new) + +class RunqueueMigrateOut: + @staticmethod + def color(): + return (0xff, 0, 0xff) + + def __init__(self, old): + self.old = old + + def __repr__(self): + return "task migrated out %s" % thread_name(self.old) + +class RunqueueSnapshot: + def __init__(self, tasks = [0], event = RunqueueEventUnknown()): + self.tasks = tuple(tasks) + self.event = event + + def sched_switch(self, prev, prev_state, next): + event = RunqueueEventUnknown() + + if taskState(prev_state) == "R" and next in self.tasks \ + and prev in self.tasks: + return self + + if taskState(prev_state) != "R": + event = RunqueueEventSleep(prev) + + next_tasks = list(self.tasks[:]) + if prev in self.tasks: + if taskState(prev_state) != "R": + next_tasks.remove(prev) + elif taskState(prev_state) == "R": + next_tasks.append(prev) + + if next not in next_tasks: + next_tasks.append(next) + + return RunqueueSnapshot(next_tasks, event) + + def migrate_out(self, old): + if old not in self.tasks: + return self + next_tasks = [task for task in self.tasks if task != old] + + return RunqueueSnapshot(next_tasks, RunqueueMigrateOut(old)) + + def __migrate_in(self, new, event): + if new in self.tasks: + self.event = event + return self + next_tasks = self.tasks[:] + tuple([new]) + + return RunqueueSnapshot(next_tasks, event) + + def migrate_in(self, new): + return self.__migrate_in(new, RunqueueMigrateIn(new)) + + def wake_up(self, new): + return self.__migrate_in(new, RunqueueEventWakeup(new)) + + def wake_up_new(self, new): + return self.__migrate_in(new, RunqueueEventFork(new)) + + def load(self): + """ Provide the number of tasks on the runqueue. + Don't count idle""" + return len(self.tasks) - 1 + + def __repr__(self): + ret = self.tasks.__repr__() + ret += self.origin_tostring() + + return ret + +class TimeSlice: + def __init__(self, start, prev): + self.start = start + self.prev = prev + self.end = start + # cpus that triggered the event + self.event_cpus = [] + if prev is not None: + self.total_load = prev.total_load + self.rqs = prev.rqs.copy() + else: + self.rqs = defaultdict(RunqueueSnapshot) + self.total_load = 0 + + def __update_total_load(self, old_rq, new_rq): + diff = new_rq.load() - old_rq.load() + self.total_load += diff + + def sched_switch(self, ts_list, prev, prev_state, next, cpu): + old_rq = self.prev.rqs[cpu] + new_rq = old_rq.sched_switch(prev, prev_state, next) + + if old_rq is new_rq: + return + + self.rqs[cpu] = new_rq + self.__update_total_load(old_rq, new_rq) + ts_list.append(self) + self.event_cpus = [cpu] + + def migrate(self, ts_list, new, old_cpu, new_cpu): + if old_cpu == new_cpu: + return + old_rq = self.prev.rqs[old_cpu] + out_rq = old_rq.migrate_out(new) + self.rqs[old_cpu] = out_rq + self.__update_total_load(old_rq, out_rq) + + new_rq = self.prev.rqs[new_cpu] + in_rq = new_rq.migrate_in(new) + self.rqs[new_cpu] = in_rq + self.__update_total_load(new_rq, in_rq) + + ts_list.append(self) + + if old_rq is not out_rq: + self.event_cpus.append(old_cpu) + self.event_cpus.append(new_cpu) + + def wake_up(self, ts_list, pid, cpu, fork): + old_rq = self.prev.rqs[cpu] + if fork: + new_rq = old_rq.wake_up_new(pid) + else: + new_rq = old_rq.wake_up(pid) + + if new_rq is old_rq: + return + self.rqs[cpu] = new_rq + self.__update_total_load(old_rq, new_rq) + ts_list.append(self) + self.event_cpus = [cpu] + + def next(self, t): + self.end = t + return TimeSlice(t, self) + +class TimeSliceList(UserList): + def __init__(self, arg = []): + self.data = arg + + def get_time_slice(self, ts): + if len(self.data) == 0: + slice = TimeSlice(ts, TimeSlice(-1, None)) + else: + slice = self.data[-1].next(ts) + return slice + + def find_time_slice(self, ts): + start = 0 + end = len(self.data) + found = -1 + searching = True + while searching: + if start == end or start == end - 1: + searching = False + + i = (end + start) / 2 + if self.data[i].start <= ts and self.data[i].end >= ts: + found = i + end = i + continue + + if self.data[i].end < ts: + start = i + + elif self.data[i].start > ts: + end = i + + return found + + def set_root_win(self, win): + self.root_win = win + + def mouse_down(self, cpu, t): + idx = self.find_time_slice(t) + if idx == -1: + return + + ts = self[idx] + rq = ts.rqs[cpu] + raw = "CPU: %d\n" % cpu + raw += "Last event : %s\n" % rq.event.__repr__() + raw += "Timestamp : %d.%06d\n" % (ts.start / (10 ** 9), (ts.start % (10 ** 9)) / 1000) + raw += "Duration : %6d us\n" % ((ts.end - ts.start) / (10 ** 6)) + raw += "Load = %d\n" % rq.load() + for t in rq.tasks: + raw += "%s \n" % thread_name(t) + + self.root_win.update_summary(raw) + + def update_rectangle_cpu(self, slice, cpu): + rq = slice.rqs[cpu] + + if slice.total_load != 0: + load_rate = rq.load() / float(slice.total_load) + else: + load_rate = 0 + + red_power = int(0xff - (0xff * load_rate)) + color = (0xff, red_power, red_power) + + top_color = None + + if cpu in slice.event_cpus: + top_color = rq.event.color() + + self.root_win.paint_rectangle_zone(cpu, color, top_color, slice.start, slice.end) + + def fill_zone(self, start, end): + i = self.find_time_slice(start) + if i == -1: + return + + for i in xrange(i, len(self.data)): + timeslice = self.data[i] + if timeslice.start > end: + return + + for cpu in timeslice.rqs: + self.update_rectangle_cpu(timeslice, cpu) + + def interval(self): + if len(self.data) == 0: + return (0, 0) + + return (self.data[0].start, self.data[-1].end) + + def nr_rectangles(self): + last_ts = self.data[-1] + max_cpu = 0 + for cpu in last_ts.rqs: + if cpu > max_cpu: + max_cpu = cpu + return max_cpu + + +class SchedEventProxy: + def __init__(self): + self.current_tsk = defaultdict(lambda : -1) + self.timeslices = TimeSliceList() + + def sched_switch(self, headers, prev_comm, prev_pid, prev_prio, prev_state, + next_comm, next_pid, next_prio): + """ Ensure the task we sched out this cpu is really the one + we logged. Otherwise we may have missed traces """ + + on_cpu_task = self.current_tsk[headers.cpu] + + if on_cpu_task != -1 and on_cpu_task != prev_pid: + print "Sched switch event rejected ts: %s cpu: %d prev: %s(%d) next: %s(%d)" % \ + (headers.ts_format(), headers.cpu, prev_comm, prev_pid, next_comm, next_pid) + + threads[prev_pid] = prev_comm + threads[next_pid] = next_comm + self.current_tsk[headers.cpu] = next_pid + + ts = self.timeslices.get_time_slice(headers.ts()) + ts.sched_switch(self.timeslices, prev_pid, prev_state, next_pid, headers.cpu) + + def migrate(self, headers, pid, prio, orig_cpu, dest_cpu): + ts = self.timeslices.get_time_slice(headers.ts()) + ts.migrate(self.timeslices, pid, orig_cpu, dest_cpu) + + def wake_up(self, headers, comm, pid, success, target_cpu, fork): + if success == 0: + return + ts = self.timeslices.get_time_slice(headers.ts()) + ts.wake_up(self.timeslices, pid, target_cpu, fork) + + +def trace_begin(): + global parser + parser = SchedEventProxy() + +def trace_end(): + app = wx.App(False) + timeslices = parser.timeslices + frame = RootFrame(timeslices, "Migration") + app.MainLoop() + +def sched__sched_stat_runtime(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, runtime, vruntime): + pass + +def sched__sched_stat_iowait(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, delay): + pass + +def sched__sched_stat_sleep(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, delay): + pass + +def sched__sched_stat_wait(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, delay): + pass + +def sched__sched_process_fork(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + parent_comm, parent_pid, child_comm, child_pid): + pass + +def sched__sched_process_wait(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, prio): + pass + +def sched__sched_process_exit(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, prio): + pass + +def sched__sched_process_free(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, prio): + pass + +def sched__sched_migrate_task(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, prio, orig_cpu, + dest_cpu): + headers = EventHeaders(common_cpu, common_secs, common_nsecs, + common_pid, common_comm) + parser.migrate(headers, pid, prio, orig_cpu, dest_cpu) + +def sched__sched_switch(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + prev_comm, prev_pid, prev_prio, prev_state, + next_comm, next_pid, next_prio): + + headers = EventHeaders(common_cpu, common_secs, common_nsecs, + common_pid, common_comm) + parser.sched_switch(headers, prev_comm, prev_pid, prev_prio, prev_state, + next_comm, next_pid, next_prio) + +def sched__sched_wakeup_new(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, prio, success, + target_cpu): + headers = EventHeaders(common_cpu, common_secs, common_nsecs, + common_pid, common_comm) + parser.wake_up(headers, comm, pid, success, target_cpu, 1) + +def sched__sched_wakeup(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, prio, success, + target_cpu): + headers = EventHeaders(common_cpu, common_secs, common_nsecs, + common_pid, common_comm) + parser.wake_up(headers, comm, pid, success, target_cpu, 0) + +def sched__sched_wait_task(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid, prio): + pass + +def sched__sched_kthread_stop_ret(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + ret): + pass + +def sched__sched_kthread_stop(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + comm, pid): + pass + +def trace_unhandled(event_name, context, common_cpu, common_secs, common_nsecs, + common_pid, common_comm): + pass diff --git a/tools/perf/scripts/python/sctop.py b/tools/perf/scripts/python/sctop.py index 6cafad40c296..7a6ec2c7d8ab 100644 --- a/tools/perf/scripts/python/sctop.py +++ b/tools/perf/scripts/python/sctop.py @@ -8,10 +8,7 @@ # will be refreshed every [interval] seconds. The default interval is # 3 seconds. -import thread -import time -import os -import sys +import os, sys, thread, time sys.path.append(os.environ['PERF_EXEC_PATH'] + \ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') @@ -20,7 +17,7 @@ from perf_trace_context import * from Core import * from Util import * -usage = "perf trace -s syscall-counts.py [comm] [interval]\n"; +usage = "perf trace -s sctop.py [comm] [interval]\n"; for_comm = None default_interval = 3 @@ -71,7 +68,7 @@ def print_syscall_totals(interval): for id, val in sorted(syscalls.iteritems(), key = lambda(k, v): (v, k), \ reverse = True): try: - print "%-40d %10d\n" % (id, val), + print "%-40s %10d\n" % (syscall_name(id), val), except TypeError: pass syscalls.clear() diff --git a/tools/perf/scripts/python/syscall-counts-by-pid.py b/tools/perf/scripts/python/syscall-counts-by-pid.py index af722d6a4b3f..d1ee3ec10cf2 100644 --- a/tools/perf/scripts/python/syscall-counts-by-pid.py +++ b/tools/perf/scripts/python/syscall-counts-by-pid.py @@ -5,29 +5,33 @@ # Displays system-wide system call totals, broken down by syscall. # If a [comm] arg is specified, only syscalls called by [comm] are displayed. -import os -import sys +import os, 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 syscall_name usage = "perf trace -s syscall-counts-by-pid.py [comm]\n"; for_comm = None +for_pid = None if len(sys.argv) > 2: sys.exit(usage) if len(sys.argv) > 1: - for_comm = sys.argv[1] + try: + for_pid = int(sys.argv[1]) + except: + for_comm = sys.argv[1] syscalls = autodict() def trace_begin(): - pass + print "Press control+C to stop and show the summary" def trace_end(): print_syscall_totals() @@ -35,9 +39,10 @@ def trace_end(): def raw_syscalls__sys_enter(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, id, args): - if for_comm is not None: - if common_comm != for_comm: - return + + if (for_comm and common_comm != for_comm) or \ + (for_pid and common_pid != for_pid ): + return try: syscalls[common_comm][common_pid][id] += 1 except TypeError: @@ -61,4 +66,4 @@ def print_syscall_totals(): id_keys = syscalls[comm][pid].keys() for id, val in sorted(syscalls[comm][pid].iteritems(), \ key = lambda(k, v): (v, k), reverse = True): - print " %-38d %10d\n" % (id, val), + print " %-38s %10d\n" % (syscall_name(id), val), diff --git a/tools/perf/scripts/python/syscall-counts.py b/tools/perf/scripts/python/syscall-counts.py index f977e85ff049..ea183dc82d29 100644 --- a/tools/perf/scripts/python/syscall-counts.py +++ b/tools/perf/scripts/python/syscall-counts.py @@ -13,6 +13,7 @@ sys.path.append(os.environ['PERF_EXEC_PATH'] + \ from perf_trace_context import * from Core import * +from Util import syscall_name usage = "perf trace -s syscall-counts.py [comm]\n"; @@ -27,7 +28,7 @@ if len(sys.argv) > 1: syscalls = autodict() def trace_begin(): - pass + print "Press control+C to stop and show the summary" def trace_end(): print_syscall_totals() @@ -55,4 +56,4 @@ def print_syscall_totals(): for id, val in sorted(syscalls.iteritems(), key = lambda(k, v): (v, k), \ reverse = True): - print "%-40d %10d\n" % (id, val), + print "%-40s %10d\n" % (syscall_name(id), val), diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c index 70c5cf87d020..e437edb72417 100644 --- a/tools/perf/util/build-id.c +++ b/tools/perf/util/build-id.c @@ -12,6 +12,7 @@ #include "event.h" #include "symbol.h" #include <linux/kernel.h> +#include "debug.h" static int build_id__mark_dso_hit(event_t *event, struct perf_session *session) { @@ -34,28 +35,43 @@ static int build_id__mark_dso_hit(event_t *event, struct perf_session *session) return 0; } +static int event__exit_del_thread(event_t *self, struct perf_session *session) +{ + struct thread *thread = perf_session__findnew(session, self->fork.tid); + + dump_printf("(%d:%d):(%d:%d)\n", self->fork.pid, self->fork.tid, + self->fork.ppid, self->fork.ptid); + + if (thread) { + rb_erase(&thread->rb_node, &session->threads); + session->last_match = NULL; + thread__delete(thread); + } + + return 0; +} + struct perf_event_ops build_id__mark_dso_hit_ops = { .sample = build_id__mark_dso_hit, .mmap = event__process_mmap, .fork = event__process_task, + .exit = event__exit_del_thread, }; char *dso__build_id_filename(struct dso *self, char *bf, size_t size) { char build_id_hex[BUILD_ID_SIZE * 2 + 1]; - const char *home; if (!self->has_build_id) return NULL; build_id__sprintf(self->build_id, sizeof(self->build_id), build_id_hex); - home = getenv("HOME"); if (bf == NULL) { - if (asprintf(&bf, "%s/%s/.build-id/%.2s/%s", home, - DEBUG_CACHE_DIR, build_id_hex, build_id_hex + 2) < 0) + if (asprintf(&bf, "%s/.build-id/%.2s/%s", buildid_dir, + build_id_hex, build_id_hex + 2) < 0) return NULL; } else - snprintf(bf, size, "%s/%s/.build-id/%.2s/%s", home, - DEBUG_CACHE_DIR, build_id_hex, build_id_hex + 2); + snprintf(bf, size, "%s/.build-id/%.2s/%s", buildid_dir, + build_id_hex, build_id_hex + 2); return bf; } diff --git a/tools/perf/util/cache.h b/tools/perf/util/cache.h index 65fe664fddf6..a7729797fd96 100644 --- a/tools/perf/util/cache.h +++ b/tools/perf/util/cache.h @@ -23,6 +23,7 @@ extern int perf_config(config_fn_t fn, void *); extern int perf_config_int(const char *, const char *); extern int perf_config_bool(const char *, const char *); extern int config_error_nonbool(const char *); +extern const char *perf_config_dirname(const char *, const char *); /* pager.c */ extern void setup_pager(void); @@ -81,6 +82,8 @@ extern char *perf_path(const char *fmt, ...) __attribute__((format (printf, 1, 2 extern char *perf_pathdup(const char *fmt, ...) __attribute__((format (printf, 1, 2))); +#ifdef NO_STRLCPY extern size_t strlcpy(char *dest, const char *src, size_t size); +#endif #endif /* __PERF_CACHE_H */ diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 52c777e451ed..e12d539417b2 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -18,7 +18,7 @@ #include "util.h" #include "callchain.h" -bool ip_callchain__valid(struct ip_callchain *chain, event_t *event) +bool ip_callchain__valid(struct ip_callchain *chain, const event_t *event) { unsigned int chain_size = event->header.size; chain_size -= (unsigned long)&event->ip.__more_data - (unsigned long)event; @@ -28,6 +28,9 @@ bool ip_callchain__valid(struct ip_callchain *chain, event_t *event) #define chain_for_each_child(child, parent) \ list_for_each_entry(child, &parent->children, brothers) +#define chain_for_each_child_safe(child, next, parent) \ + list_for_each_entry_safe(child, next, &parent->children, brothers) + static void rb_insert_callchain(struct rb_root *root, struct callchain_node *chain, enum chain_mode mode) @@ -86,10 +89,10 @@ __sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node, * sort them by hit */ static void -sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node, +sort_chain_flat(struct rb_root *rb_root, struct callchain_root *root, u64 min_hit, struct callchain_param *param __used) { - __sort_chain_flat(rb_root, node, min_hit); + __sort_chain_flat(rb_root, &root->node, min_hit); } static void __sort_chain_graph_abs(struct callchain_node *node, @@ -108,11 +111,11 @@ static void __sort_chain_graph_abs(struct callchain_node *node, } static void -sort_chain_graph_abs(struct rb_root *rb_root, struct callchain_node *chain_root, +sort_chain_graph_abs(struct rb_root *rb_root, struct callchain_root *chain_root, u64 min_hit, struct callchain_param *param __used) { - __sort_chain_graph_abs(chain_root, min_hit); - rb_root->rb_node = chain_root->rb_root.rb_node; + __sort_chain_graph_abs(&chain_root->node, min_hit); + rb_root->rb_node = chain_root->node.rb_root.rb_node; } static void __sort_chain_graph_rel(struct callchain_node *node, @@ -133,11 +136,11 @@ static void __sort_chain_graph_rel(struct callchain_node *node, } static void -sort_chain_graph_rel(struct rb_root *rb_root, struct callchain_node *chain_root, +sort_chain_graph_rel(struct rb_root *rb_root, struct callchain_root *chain_root, u64 min_hit __used, struct callchain_param *param) { - __sort_chain_graph_rel(chain_root, param->min_percent / 100.0); - rb_root->rb_node = chain_root->rb_root.rb_node; + __sort_chain_graph_rel(&chain_root->node, param->min_percent / 100.0); + rb_root->rb_node = chain_root->node.rb_root.rb_node; } int register_callchain_param(struct callchain_param *param) @@ -284,19 +287,18 @@ split_add_child(struct callchain_node *parent, struct resolved_chain *chain, } static int -__append_chain(struct callchain_node *root, struct resolved_chain *chain, - unsigned int start, u64 period); +append_chain(struct callchain_node *root, struct resolved_chain *chain, + unsigned int start, u64 period); static void -__append_chain_children(struct callchain_node *root, - struct resolved_chain *chain, - unsigned int start, u64 period) +append_chain_children(struct callchain_node *root, struct resolved_chain *chain, + unsigned int start, u64 period) { struct callchain_node *rnode; /* lookup in childrens */ chain_for_each_child(rnode, root) { - unsigned int ret = __append_chain(rnode, chain, start, period); + unsigned int ret = append_chain(rnode, chain, start, period); if (!ret) goto inc_children_hit; @@ -309,8 +311,8 @@ inc_children_hit: } static int -__append_chain(struct callchain_node *root, struct resolved_chain *chain, - unsigned int start, u64 period) +append_chain(struct callchain_node *root, struct resolved_chain *chain, + unsigned int start, u64 period) { struct callchain_list *cnode; unsigned int i = start; @@ -357,7 +359,7 @@ __append_chain(struct callchain_node *root, struct resolved_chain *chain, } /* We match the node and still have a part remaining */ - __append_chain_children(root, chain, i, period); + append_chain_children(root, chain, i, period); return 0; } @@ -380,8 +382,8 @@ static void filter_context(struct ip_callchain *old, struct resolved_chain *new, } -int append_chain(struct callchain_node *root, struct ip_callchain *chain, - struct map_symbol *syms, u64 period) +int callchain_append(struct callchain_root *root, struct ip_callchain *chain, + struct map_symbol *syms, u64 period) { struct resolved_chain *filtered; @@ -398,9 +400,65 @@ int append_chain(struct callchain_node *root, struct ip_callchain *chain, if (!filtered->nr) goto end; - __append_chain_children(root, filtered, 0, period); + append_chain_children(&root->node, filtered, 0, period); + + if (filtered->nr > root->max_depth) + root->max_depth = filtered->nr; end: free(filtered); return 0; } + +static int +merge_chain_branch(struct callchain_node *dst, struct callchain_node *src, + struct resolved_chain *chain) +{ + struct callchain_node *child, *next_child; + struct callchain_list *list, *next_list; + int old_pos = chain->nr; + int err = 0; + + list_for_each_entry_safe(list, next_list, &src->val, list) { + chain->ips[chain->nr].ip = list->ip; + chain->ips[chain->nr].ms = list->ms; + chain->nr++; + list_del(&list->list); + free(list); + } + + if (src->hit) + append_chain_children(dst, chain, 0, src->hit); + + chain_for_each_child_safe(child, next_child, src) { + err = merge_chain_branch(dst, child, chain); + if (err) + break; + + list_del(&child->brothers); + free(child); + } + + chain->nr = old_pos; + + return err; +} + +int callchain_merge(struct callchain_root *dst, struct callchain_root *src) +{ + struct resolved_chain *chain; + int err; + + chain = malloc(sizeof(*chain) + + src->max_depth * sizeof(struct resolved_ip)); + if (!chain) + return -ENOMEM; + + chain->nr = 0; + + err = merge_chain_branch(&dst->node, &src->node, chain); + + free(chain); + + return err; +} diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index f2e9ee164bd8..c15fb8c24ad2 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -26,9 +26,14 @@ struct callchain_node { u64 children_hit; }; +struct callchain_root { + u64 max_depth; + struct callchain_node node; +}; + struct callchain_param; -typedef void (*sort_chain_func_t)(struct rb_root *, struct callchain_node *, +typedef void (*sort_chain_func_t)(struct rb_root *, struct callchain_root *, u64, struct callchain_param *); struct callchain_param { @@ -44,14 +49,16 @@ struct callchain_list { struct list_head list; }; -static inline void callchain_init(struct callchain_node *node) +static inline void callchain_init(struct callchain_root *root) { - INIT_LIST_HEAD(&node->brothers); - INIT_LIST_HEAD(&node->children); - INIT_LIST_HEAD(&node->val); + INIT_LIST_HEAD(&root->node.brothers); + INIT_LIST_HEAD(&root->node.children); + INIT_LIST_HEAD(&root->node.val); - node->parent = NULL; - node->hit = 0; + root->node.parent = NULL; + root->node.hit = 0; + root->node.children_hit = 0; + root->max_depth = 0; } static inline u64 cumul_hits(struct callchain_node *node) @@ -60,8 +67,9 @@ static inline u64 cumul_hits(struct callchain_node *node) } int register_callchain_param(struct callchain_param *param); -int append_chain(struct callchain_node *root, struct ip_callchain *chain, - struct map_symbol *syms, u64 period); +int callchain_append(struct callchain_root *root, struct ip_callchain *chain, + struct map_symbol *syms, u64 period); +int callchain_merge(struct callchain_root *dst, struct callchain_root *src); -bool ip_callchain__valid(struct ip_callchain *chain, event_t *event); +bool ip_callchain__valid(struct ip_callchain *chain, const event_t *event); #endif /* __PERF_CALLCHAIN_H */ diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index dabe892d0e53..e02d78cae70f 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -11,6 +11,11 @@ #define MAXNAME (256) +#define DEBUG_CACHE_DIR ".debug" + + +char buildid_dir[MAXPATHLEN]; /* root dir for buildid, binary cache */ + static FILE *config_file; static const char *config_file_name; static int config_linenr; @@ -127,7 +132,7 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) break; if (!iskeychar(c)) break; - name[len++] = tolower(c); + name[len++] = c; if (len >= MAXNAME) return -1; } @@ -327,6 +332,13 @@ int perf_config_bool(const char *name, const char *value) return !!perf_config_bool_or_int(name, value, &discard); } +const char *perf_config_dirname(const char *name, const char *value) +{ + if (!name) + return NULL; + return 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. */ @@ -428,3 +440,53 @@ int config_error_nonbool(const char *var) { return error("Missing value for '%s'", var); } + +struct buildid_dir_config { + char *dir; +}; + +static int buildid_dir_command_config(const char *var, const char *value, + void *data) +{ + struct buildid_dir_config *c = data; + const char *v; + + /* same dir for all commands */ + if (!prefixcmp(var, "buildid.") && !strcmp(var + 8, "dir")) { + v = perf_config_dirname(var, value); + if (!v) + return -1; + strncpy(c->dir, v, MAXPATHLEN-1); + c->dir[MAXPATHLEN-1] = '\0'; + } + return 0; +} + +static void check_buildid_dir_config(void) +{ + struct buildid_dir_config c; + c.dir = buildid_dir; + perf_config(buildid_dir_command_config, &c); +} + +void set_buildid_dir(void) +{ + buildid_dir[0] = '\0'; + + /* try config file */ + check_buildid_dir_config(); + + /* default to $HOME/.debug */ + if (buildid_dir[0] == '\0') { + char *v = getenv("HOME"); + if (v) { + snprintf(buildid_dir, MAXPATHLEN-1, "%s/%s", + v, DEBUG_CACHE_DIR); + } else { + strncpy(buildid_dir, DEBUG_CACHE_DIR, MAXPATHLEN-1); + } + buildid_dir[MAXPATHLEN-1] = '\0'; + } + /* for communicating with external commands */ + setenv("PERF_BUILDID_DIR", buildid_dir, 1); +} diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c index 4e01490e51e5..0f9b8d7a7d7e 100644 --- a/tools/perf/util/cpumap.c +++ b/tools/perf/util/cpumap.c @@ -20,7 +20,7 @@ static int default_cpu_map(void) return nr_cpus; } -int read_cpu_map(void) +static int read_all_cpu_map(void) { FILE *onlnf; int nr_cpus = 0; @@ -57,3 +57,58 @@ int read_cpu_map(void) return default_cpu_map(); } + +int read_cpu_map(const char *cpu_list) +{ + unsigned long start_cpu, end_cpu = 0; + char *p = NULL; + int i, nr_cpus = 0; + + if (!cpu_list) + return read_all_cpu_map(); + + if (!isdigit(*cpu_list)) + goto invalid; + + while (isdigit(*cpu_list)) { + p = NULL; + start_cpu = strtoul(cpu_list, &p, 0); + if (start_cpu >= INT_MAX + || (*p != '\0' && *p != ',' && *p != '-')) + goto invalid; + + if (*p == '-') { + cpu_list = ++p; + p = NULL; + end_cpu = strtoul(cpu_list, &p, 0); + + if (end_cpu >= INT_MAX || (*p != '\0' && *p != ',')) + goto invalid; + + if (end_cpu < start_cpu) + goto invalid; + } else { + end_cpu = start_cpu; + } + + for (; start_cpu <= end_cpu; start_cpu++) { + /* check for duplicates */ + for (i = 0; i < nr_cpus; i++) + if (cpumap[i] == (int)start_cpu) + goto invalid; + + assert(nr_cpus < MAX_NR_CPUS); + cpumap[nr_cpus++] = (int)start_cpu; + } + if (*p) + ++p; + + cpu_list = p; + } + if (nr_cpus > 0) + return nr_cpus; + + return default_cpu_map(); +invalid: + return -1; +} diff --git a/tools/perf/util/cpumap.h b/tools/perf/util/cpumap.h index 86c78bb33098..3e60f56e490e 100644 --- a/tools/perf/util/cpumap.h +++ b/tools/perf/util/cpumap.h @@ -1,7 +1,7 @@ #ifndef __PERF_CPUMAP_H #define __PERF_CPUMAP_H -extern int read_cpu_map(void); +extern int read_cpu_map(const char *cpu_list); extern int cpumap[]; #endif /* __PERF_CPUMAP_H */ diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 6cddff2bc970..c8d81b00089d 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -12,8 +12,8 @@ #include "debug.h" #include "util.h" -int verbose = 0; -bool dump_trace = false; +int verbose; +bool dump_trace = false, quiet = false; int eprintf(int level, const char *fmt, ...) { @@ -23,7 +23,7 @@ int eprintf(int level, const char *fmt, ...) if (verbose >= level) { va_start(args, fmt); if (use_browser > 0) - ret = browser__show_help(fmt, args); + ret = ui_helpline__show_help(fmt, args); else ret = vfprintf(stderr, fmt, args); va_end(args); @@ -86,12 +86,10 @@ void trace_event(event_t *event) dump_printf_color(" ", color); for (j = 0; j < 15-(i & 15); j++) dump_printf_color(" ", color); - for (j = 0; j < (i & 15); j++) { - if (isprint(raw_event[i-15+j])) - dump_printf_color("%c", color, - raw_event[i-15+j]); - else - dump_printf_color(".", color); + for (j = i & ~15; j <= i; j++) { + dump_printf_color("%c", color, + isprint(raw_event[j]) ? + raw_event[j] : '.'); } dump_printf_color("\n", color); } diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index 047ac3324ebe..7b514082bbaf 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -6,7 +6,7 @@ #include "event.h" extern int verbose; -extern bool dump_trace; +extern bool quiet, dump_trace; int dump_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); void trace_event(event_t *event); @@ -14,7 +14,7 @@ void trace_event(event_t *event); struct ui_progress; #ifdef NO_NEWT_SUPPORT -static inline int browser__show_help(const char *format __used, va_list ap __used) +static inline int ui_helpline__show_help(const char *format __used, va_list ap __used) { return 0; } @@ -30,10 +30,9 @@ static inline void ui_progress__update(struct ui_progress *self __used, static inline void ui_progress__delete(struct ui_progress *self __used) {} #else -int browser__show_help(const char *format, va_list ap); -struct ui_progress *ui_progress__new(const char *title, u64 total); -void ui_progress__update(struct ui_progress *self, u64 curr); -void ui_progress__delete(struct ui_progress *self); +extern char ui_helpline__last_msg[]; +int ui_helpline__show_help(const char *format, va_list ap); +#include "ui/progress.h" #endif #endif /* __PERF_DEBUG_H */ diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index 2fbf6a463c81..dab9e754a281 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -151,7 +151,6 @@ static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, continue; pbf += n + 3; if (*pbf == 'x') { /* vm_exec */ - u64 vm_pgoff; char *execname = strchr(bf, '/'); /* Catch VDSO */ @@ -162,12 +161,7 @@ static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, continue; pbf += 3; - n = hex2u64(pbf, &vm_pgoff); - /* pgoff is in bytes, not pages */ - if (n >= 0) - ev.mmap.pgoff = vm_pgoff << getpagesize(); - else - ev.mmap.pgoff = 0; + n = hex2u64(pbf, &ev.mmap.pgoff); size = strlen(execname); execname[size - 1] = '\0'; /* Remove \n */ @@ -340,30 +334,29 @@ int event__synthesize_kernel_mmap(event__handler_t process, return process(&ev, session); } -static void thread__comm_adjust(struct thread *self) +static void thread__comm_adjust(struct thread *self, struct hists *hists) { char *comm = self->comm; if (!symbol_conf.col_width_list_str && !symbol_conf.field_sep && (!symbol_conf.comm_list || strlist__has_entry(symbol_conf.comm_list, comm))) { - unsigned int slen = strlen(comm); + u16 slen = strlen(comm); - if (slen > comms__col_width) { - comms__col_width = slen; - threads__col_width = slen + 6; - } + if (hists__new_col_len(hists, HISTC_COMM, slen)) + hists__set_col_len(hists, HISTC_THREAD, slen + 6); } } -static int thread__set_comm_adjust(struct thread *self, const char *comm) +static int thread__set_comm_adjust(struct thread *self, const char *comm, + struct hists *hists) { int ret = thread__set_comm(self, comm); if (ret) return ret; - thread__comm_adjust(self); + thread__comm_adjust(self, hists); return 0; } @@ -374,7 +367,8 @@ int event__process_comm(event_t *self, struct perf_session *session) dump_printf(": %s:%d\n", self->comm.comm, self->comm.tid); - if (thread == NULL || thread__set_comm_adjust(thread, self->comm.comm)) { + if (thread == NULL || thread__set_comm_adjust(thread, self->comm.comm, + &session->hists)) { dump_printf("problem processing PERF_RECORD_COMM, skipping event.\n"); return -1; } @@ -456,6 +450,7 @@ static int event__process_kernel_mmap(event_t *self, goto out_problem; map->dso->short_name = name; + map->dso->sname_alloc = 1; map->end = map->start + self->mmap.len; } else if (is_kernel_mmap) { const char *symbol_name = (self->mmap.filename + @@ -514,12 +509,13 @@ int event__process_mmap(event_t *self, struct perf_session *session) if (machine == NULL) goto out_problem; thread = perf_session__findnew(session, self->mmap.pid); + if (thread == NULL) + goto out_problem; map = map__new(&machine->user_dsos, self->mmap.start, self->mmap.len, self->mmap.pgoff, self->mmap.pid, self->mmap.filename, - MAP__FUNCTION, session->cwd, session->cwdlen); - - if (thread == NULL || map == NULL) + MAP__FUNCTION); + if (map == NULL) goto out_problem; thread__insert_map(thread, map); @@ -552,6 +548,26 @@ int event__process_task(event_t *self, struct perf_session *session) return 0; } +int event__process(event_t *event, struct perf_session *session) +{ + switch (event->header.type) { + case PERF_RECORD_COMM: + event__process_comm(event, session); + break; + case PERF_RECORD_MMAP: + event__process_mmap(event, session); + break; + case PERF_RECORD_FORK: + case PERF_RECORD_EXIT: + event__process_task(event, session); + break; + default: + break; + } + + return 0; +} + void thread__find_addr_map(struct thread *self, struct perf_session *session, u8 cpumode, enum map_type type, pid_t pid, u64 addr, @@ -641,27 +657,49 @@ void thread__find_addr_location(struct thread *self, al->sym = NULL; } -static void dso__calc_col_width(struct dso *self) +static void dso__calc_col_width(struct dso *self, struct hists *hists) { if (!symbol_conf.col_width_list_str && !symbol_conf.field_sep && (!symbol_conf.dso_list || strlist__has_entry(symbol_conf.dso_list, self->name))) { - u16 slen = self->short_name_len; - if (verbose) - slen = self->long_name_len; - if (dsos__col_width < slen) - dsos__col_width = slen; + u16 slen = dso__name_len(self); + hists__new_col_len(hists, HISTC_DSO, slen); } self->slen_calculated = 1; } int event__preprocess_sample(const event_t *self, struct perf_session *session, - struct addr_location *al, symbol_filter_t filter) + struct addr_location *al, struct sample_data *data, + symbol_filter_t filter) { u8 cpumode = self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - struct thread *thread = perf_session__findnew(session, self->ip.pid); + struct thread *thread; + event__parse_sample(self, session->sample_type, data); + + dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld cpu:%d\n", + self->header.misc, data->pid, data->tid, data->ip, + data->period, data->cpu); + + if (session->sample_type & PERF_SAMPLE_CALLCHAIN) { + unsigned int i; + + dump_printf("... chain: nr:%Lu\n", data->callchain->nr); + + if (!ip_callchain__valid(data->callchain, self)) { + pr_debug("call-chain problem with event, " + "skipping it.\n"); + goto out_filtered; + } + + if (dump_trace) { + for (i = 0; i < data->callchain->nr; i++) + dump_printf("..... %2d: %016Lx\n", + i, data->callchain->ips[i]); + } + } + thread = perf_session__findnew(session, self->ip.pid); if (thread == NULL) return -1; @@ -687,6 +725,7 @@ int event__preprocess_sample(const event_t *self, struct perf_session *session, al->map ? al->map->dso->long_name : al->level == 'H' ? "[hypervisor]" : "<not found>"); al->sym = NULL; + al->cpu = data->cpu; if (al->map) { if (symbol_conf.dso_list && @@ -703,16 +742,17 @@ int event__preprocess_sample(const event_t *self, struct perf_session *session, * sampled. */ if (!sort_dso.elide && !al->map->dso->slen_calculated) - dso__calc_col_width(al->map->dso); + dso__calc_col_width(al->map->dso, &session->hists); al->sym = map__find_symbol(al->map, al->addr, filter); } else { const unsigned int unresolved_col_width = BITS_PER_LONG / 4; - if (dsos__col_width < unresolved_col_width && + if (hists__col_len(&session->hists, HISTC_DSO) < unresolved_col_width && !symbol_conf.col_width_list_str && !symbol_conf.field_sep && !symbol_conf.dso_list) - dsos__col_width = unresolved_col_width; + hists__set_col_len(&session->hists, HISTC_DSO, + unresolved_col_width); } if (symbol_conf.sym_list && al->sym && @@ -726,9 +766,9 @@ out_filtered: return 0; } -int event__parse_sample(event_t *event, u64 type, struct sample_data *data) +int event__parse_sample(const event_t *event, u64 type, struct sample_data *data) { - u64 *array = event->sample.array; + const u64 *array = event->sample.array; if (type & PERF_SAMPLE_IP) { data->ip = event->ip.ip; @@ -767,7 +807,8 @@ int event__parse_sample(event_t *event, u64 type, struct sample_data *data) u32 *p = (u32 *)array; data->cpu = *p; array++; - } + } else + data->cpu = -1; if (type & PERF_SAMPLE_PERIOD) { data->period = *array; diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index 8577085db067..8e790dae7026 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -154,11 +154,13 @@ int event__process_comm(event_t *self, struct perf_session *session); int event__process_lost(event_t *self, struct perf_session *session); int event__process_mmap(event_t *self, struct perf_session *session); int event__process_task(event_t *self, struct perf_session *session); +int event__process(event_t *event, struct perf_session *session); struct addr_location; int event__preprocess_sample(const event_t *self, struct perf_session *session, - struct addr_location *al, symbol_filter_t filter); -int event__parse_sample(event_t *event, u64 type, struct sample_data *data); + struct addr_location *al, struct sample_data *data, + symbol_filter_t filter); +int event__parse_sample(const event_t *event, u64 type, struct sample_data *data); extern const char *event__name[]; diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 1f62435f96c2..d7e67b167ea3 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -16,6 +16,8 @@ #include "symbol.h" #include "debug.h" +static bool no_buildid_cache = false; + /* * Create new perf.data header attribute: */ @@ -385,8 +387,7 @@ static int perf_session__cache_build_ids(struct perf_session *self) int ret; char debugdir[PATH_MAX]; - snprintf(debugdir, sizeof(debugdir), "%s/%s", getenv("HOME"), - DEBUG_CACHE_DIR); + snprintf(debugdir, sizeof(debugdir), "%s", buildid_dir); if (mkdir(debugdir, 0755) != 0 && errno != EEXIST) return -1; @@ -471,7 +472,8 @@ static int perf_header__adds_write(struct perf_header *self, int fd) } buildid_sec->size = lseek(fd, 0, SEEK_CUR) - buildid_sec->offset; - perf_session__cache_build_ids(session); + if (!no_buildid_cache) + perf_session__cache_build_ids(session); } lseek(fd, sec_start, SEEK_SET); @@ -1190,3 +1192,8 @@ int event__process_build_id(event_t *self, session); return 0; } + +void disable_buildid_cache(void) +{ + no_buildid_cache = true; +} diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 784ee0bdda77..2022e8740994 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -5,11 +5,61 @@ #include "sort.h" #include <math.h> +enum hist_filter { + HIST_FILTER__DSO, + HIST_FILTER__THREAD, + HIST_FILTER__PARENT, +}; + struct callchain_param callchain_param = { .mode = CHAIN_GRAPH_REL, .min_percent = 0.5 }; +u16 hists__col_len(struct hists *self, enum hist_column col) +{ + return self->col_len[col]; +} + +void hists__set_col_len(struct hists *self, enum hist_column col, u16 len) +{ + self->col_len[col] = len; +} + +bool hists__new_col_len(struct hists *self, enum hist_column col, u16 len) +{ + if (len > hists__col_len(self, col)) { + hists__set_col_len(self, col, len); + return true; + } + return false; +} + +static void hists__reset_col_len(struct hists *self) +{ + enum hist_column col; + + for (col = 0; col < HISTC_NR_COLS; ++col) + hists__set_col_len(self, col, 0); +} + +static void hists__calc_col_len(struct hists *self, struct hist_entry *h) +{ + u16 len; + + if (h->ms.sym) + hists__new_col_len(self, HISTC_SYMBOL, h->ms.sym->namelen); + + 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 (h->ms.map) { + len = dso__name_len(h->ms.map->dso); + hists__new_col_len(self, HISTC_DSO, len); + } +} + static void hist_entry__add_cpumode_period(struct hist_entry *self, unsigned int cpumode, u64 period) { @@ -37,12 +87,14 @@ static void hist_entry__add_cpumode_period(struct hist_entry *self, static struct hist_entry *hist_entry__new(struct hist_entry *template) { - size_t callchain_size = symbol_conf.use_callchain ? sizeof(struct callchain_node) : 0; + size_t callchain_size = symbol_conf.use_callchain ? sizeof(struct callchain_root) : 0; struct hist_entry *self = malloc(sizeof(*self) + callchain_size); if (self != NULL) { *self = *template; self->nr_events = 1; + if (self->ms.map) + self->ms.map->referenced = true; if (symbol_conf.use_callchain) callchain_init(self->callchain); } @@ -50,11 +102,19 @@ 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 *entry) +static void hists__inc_nr_entries(struct hists *self, struct hist_entry *h) { - if (entry->ms.sym && self->max_sym_namelen < entry->ms.sym->namelen) - self->max_sym_namelen = entry->ms.sym->namelen; - ++self->nr_entries; + if (!h->filtered) { + hists__calc_col_len(self, h); + ++self->nr_entries; + } +} + +static u8 symbol__parent_filter(const struct symbol *parent) +{ + if (symbol_conf.exclude_other && parent == NULL) + return 1 << HIST_FILTER__PARENT; + return 0; } struct hist_entry *__hists__add_entry(struct hists *self, @@ -70,10 +130,12 @@ struct hist_entry *__hists__add_entry(struct hists *self, .map = al->map, .sym = al->sym, }, + .cpu = al->cpu, .ip = al->addr, .level = al->level, .period = period, .parent = sym_parent, + .filtered = symbol__parent_filter(sym_parent), }; int cmp; @@ -164,6 +226,8 @@ static bool collapse__insert_entry(struct rb_root *root, struct hist_entry *he) if (!cmp) { iter->period += he->period; + if (symbol_conf.use_callchain) + callchain_merge(iter->callchain, he->callchain); hist_entry__free(he); return false; } @@ -191,7 +255,7 @@ void hists__collapse_resort(struct hists *self) tmp = RB_ROOT; next = rb_first(&self->entries); self->nr_entries = 0; - self->max_sym_namelen = 0; + hists__reset_col_len(self); while (next) { n = rb_entry(next, struct hist_entry, rb_node); @@ -248,7 +312,7 @@ void hists__output_resort(struct hists *self) next = rb_first(&self->entries); self->nr_entries = 0; - self->max_sym_namelen = 0; + hists__reset_col_len(self); while (next) { n = rb_entry(next, struct hist_entry, rb_node); @@ -515,8 +579,9 @@ static size_t hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, } int hist_entry__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 hists *hists, 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; @@ -620,29 +685,25 @@ int hist_entry__snprintf(struct hist_entry *self, char *s, size_t size, ret += snprintf(s + ret, size - ret, "%s", sep ?: " "); ret += se->se_snprintf(self, s + ret, size - ret, - se->se_width ? *se->se_width : 0); + hists__col_len(hists, se->se_width_idx)); } return ret; } -int hist_entry__fprintf(struct hist_entry *self, struct hists *pair_hists, - bool show_displacement, long displacement, FILE *fp, - u64 session_total) +int hist_entry__fprintf(struct hist_entry *self, struct hists *hists, + struct hists *pair_hists, bool show_displacement, + long displacement, FILE *fp, u64 session_total) { char bf[512]; - int ret; - - ret = hist_entry__snprintf(self, bf, sizeof(bf), pair_hists, - show_displacement, displacement, - true, session_total); - if (!ret) - return 0; - + hist_entry__snprintf(self, bf, sizeof(bf), hists, pair_hists, + show_displacement, displacement, + true, session_total); return fprintf(fp, "%s\n", bf); } -static size_t hist_entry__fprintf_callchain(struct hist_entry *self, FILE *fp, +static size_t hist_entry__fprintf_callchain(struct hist_entry *self, + struct hists *hists, FILE *fp, u64 session_total) { int left_margin = 0; @@ -650,7 +711,7 @@ static size_t hist_entry__fprintf_callchain(struct hist_entry *self, FILE *fp, if (sort__first_dimension == SORT_COMM) { struct sort_entry *se = list_first_entry(&hist_entry__sort_list, typeof(*se), list); - left_margin = se->se_width ? *se->se_width : 0; + left_margin = hists__col_len(hists, se->se_width_idx); left_margin -= thread__comm_len(self->thread); } @@ -721,17 +782,17 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, continue; } width = strlen(se->se_header); - if (se->se_width) { - if (symbol_conf.col_width_list_str) { - if (col_width) { - *se->se_width = atoi(col_width); - col_width = strchr(col_width, ','); - if (col_width) - ++col_width; - } + if (symbol_conf.col_width_list_str) { + if (col_width) { + hists__set_col_len(self, se->se_width_idx, + atoi(col_width)); + col_width = strchr(col_width, ','); + if (col_width) + ++col_width; } - width = *se->se_width = max(*se->se_width, width); } + if (!hists__new_col_len(self, se->se_width_idx, width)) + width = hists__col_len(self, se->se_width_idx); fprintf(fp, " %*s", width, se->se_header); } fprintf(fp, "\n"); @@ -754,9 +815,8 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, continue; fprintf(fp, " "); - if (se->se_width) - width = *se->se_width; - else + width = hists__col_len(self, se->se_width_idx); + if (width == 0) width = strlen(se->se_header); for (i = 0; i < width; i++) fprintf(fp, "."); @@ -767,7 +827,6 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, print_entries: for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - int cnt; if (show_displacement) { if (h->pair != NULL) @@ -777,17 +836,12 @@ print_entries: displacement = 0; ++position; } - cnt = hist_entry__fprintf(h, pair, show_displacement, - displacement, fp, self->stats.total_period); - /* Ignore those that didn't match the parent filter */ - if (!cnt) - continue; - - ret += cnt; + ret += hist_entry__fprintf(h, self, pair, show_displacement, + displacement, fp, self->stats.total_period); if (symbol_conf.use_callchain) - ret += hist_entry__fprintf_callchain(h, fp, self->stats.total_period); - + ret += hist_entry__fprintf_callchain(h, self, fp, + self->stats.total_period); if (h->ms.map == NULL && verbose > 1) { __map_groups__fprintf_maps(&h->thread->mg, MAP__FUNCTION, verbose, fp); @@ -800,10 +854,52 @@ print_entries: return ret; } -enum hist_filter { - HIST_FILTER__DSO, - HIST_FILTER__THREAD, -}; +/* + * See hists__fprintf to match the column widths + */ +unsigned int hists__sort_list_width(struct hists *self) +{ + struct sort_entry *se; + int ret = 9; /* total % */ + + if (symbol_conf.show_cpu_utilization) { + ret += 7; /* count_sys % */ + ret += 6; /* count_us % */ + if (perf_guest) { + ret += 13; /* count_guest_sys % */ + ret += 12; /* count_guest_us % */ + } + } + + if (symbol_conf.show_nr_samples) + ret += 11; + + list_for_each_entry(se, &hist_entry__sort_list, list) + if (!se->elide) + ret += 2 + hists__col_len(self, se->se_width_idx); + + if (verbose) /* Addr + origin */ + ret += 3 + BITS_PER_LONG / 4; + + return ret; +} + +static void hists__remove_entry_filter(struct hists *self, struct hist_entry *h, + enum hist_filter filter) +{ + h->filtered &= ~(1 << filter); + if (h->filtered) + return; + + ++self->nr_entries; + if (h->ms.unfolded) + self->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__calc_col_len(self, h); +} void hists__filter_by_dso(struct hists *self, const struct dso *dso) { @@ -811,7 +907,7 @@ void hists__filter_by_dso(struct hists *self, const struct dso *dso) self->nr_entries = self->stats.total_period = 0; self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; - self->max_sym_namelen = 0; + hists__reset_col_len(self); for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); @@ -824,15 +920,7 @@ void hists__filter_by_dso(struct hists *self, const struct dso *dso) continue; } - h->filtered &= ~(1 << HIST_FILTER__DSO); - if (!h->filtered) { - ++self->nr_entries; - self->stats.total_period += h->period; - self->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events; - if (h->ms.sym && - self->max_sym_namelen < h->ms.sym->namelen) - self->max_sym_namelen = h->ms.sym->namelen; - } + hists__remove_entry_filter(self, h, HIST_FILTER__DSO); } } @@ -842,7 +930,7 @@ void hists__filter_by_thread(struct hists *self, const struct thread *thread) self->nr_entries = self->stats.total_period = 0; self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; - self->max_sym_namelen = 0; + hists__reset_col_len(self); for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); @@ -851,15 +939,8 @@ void hists__filter_by_thread(struct hists *self, const struct thread *thread) h->filtered |= (1 << HIST_FILTER__THREAD); continue; } - h->filtered &= ~(1 << HIST_FILTER__THREAD); - if (!h->filtered) { - ++self->nr_entries; - self->stats.total_period += h->period; - self->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events; - if (h->ms.sym && - self->max_sym_namelen < h->ms.sym->namelen) - self->max_sym_namelen = h->ms.sym->namelen; - } + + hists__remove_entry_filter(self, h, HIST_FILTER__THREAD); } } @@ -904,9 +985,9 @@ int hist_entry__inc_addr_samples(struct hist_entry *self, u64 ip) return 0; } -static struct objdump_line *objdump_line__new(s64 offset, char *line) +static struct objdump_line *objdump_line__new(s64 offset, char *line, size_t privsize) { - struct objdump_line *self = malloc(sizeof(*self)); + struct objdump_line *self = malloc(sizeof(*self) + privsize); if (self != NULL) { self->offset = offset; @@ -938,7 +1019,7 @@ struct objdump_line *objdump__get_next_ip_line(struct list_head *head, } static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, - struct list_head *head) + struct list_head *head, size_t privsize) { struct symbol *sym = self->ms.sym; struct objdump_line *objdump_line; @@ -989,7 +1070,7 @@ static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, offset = -1; } - objdump_line = objdump_line__new(offset, line); + objdump_line = objdump_line__new(offset, line, privsize); if (objdump_line == NULL) { free(line); return -1; @@ -999,7 +1080,8 @@ static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, return 0; } -int hist_entry__annotate(struct hist_entry *self, struct list_head *head) +int hist_entry__annotate(struct hist_entry *self, struct list_head *head, + size_t privsize) { struct symbol *sym = self->ms.sym; struct map *map = self->ms.map; @@ -1052,7 +1134,7 @@ fallback: dso, dso->long_name, sym, sym->name); snprintf(command, sizeof(command), - "objdump --start-address=0x%016Lx --stop-address=0x%016Lx -dS %s|grep -v %s|expand", + "objdump --start-address=0x%016Lx --stop-address=0x%016Lx -dS -C %s|grep -v %s|expand", map__rip_2objdump(map, sym->start), map__rip_2objdump(map, sym->end), filename, filename); @@ -1064,7 +1146,7 @@ fallback: goto out_free_filename; while (!feof(file)) - if (hist_entry__parse_objdump_line(self, file, head) < 0) + if (hist_entry__parse_objdump_line(self, file, head, privsize) < 0) break; pclose(file); diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 83fa33a7b38b..587d375d3430 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -56,6 +56,16 @@ struct events_stats { u32 nr_unknown_events; }; +enum hist_column { + HISTC_SYMBOL, + HISTC_DSO, + HISTC_THREAD, + HISTC_COMM, + HISTC_PARENT, + HISTC_CPU, + HISTC_NR_COLS, /* Last entry */ +}; + struct hists { struct rb_node rb_node; struct rb_root entries; @@ -64,7 +74,7 @@ struct hists { u64 config; u64 event_stream; u32 type; - u32 max_sym_namelen; + u16 col_len[HISTC_NR_COLS]; }; struct hist_entry *__hists__add_entry(struct hists *self, @@ -72,12 +82,13 @@ struct hist_entry *__hists__add_entry(struct hists *self, 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 *pair_hists, - bool show_displacement, long displacement, FILE *fp, - u64 total); +int hist_entry__fprintf(struct hist_entry *self, struct hists *hists, + struct hists *pair_hists, bool show_displacement, + long displacement, FILE *fp, u64 total); int hist_entry__snprintf(struct hist_entry *self, char *bf, size_t size, - struct hists *pair_hists, bool show_displacement, - long displacement, bool color, u64 total); + struct hists *hists, struct hists *pair_hists, + bool show_displacement, long displacement, + bool color, u64 total); void hist_entry__free(struct hist_entry *); void hists__output_resort(struct hists *self); @@ -90,11 +101,16 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, bool show_displacement, FILE *fp); int hist_entry__inc_addr_samples(struct hist_entry *self, u64 ip); -int hist_entry__annotate(struct hist_entry *self, struct list_head *head); +int hist_entry__annotate(struct hist_entry *self, struct list_head *head, + 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); +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); +bool hists__new_col_len(struct hists *self, enum hist_column col, u16 len); + #ifdef NO_NEWT_SUPPORT static inline int hists__browse(struct hists *self __used, const char *helpline __used, @@ -126,4 +142,7 @@ int hist_entry__tui_annotate(struct hist_entry *self); int hists__tui_browse_tree(struct rb_root *self, const char *help); #endif + +unsigned int hists__sort_list_width(struct hists *self); + #endif /* __PERF_HIST_H */ diff --git a/tools/perf/util/include/linux/list.h b/tools/perf/util/include/linux/list.h index dbe4b814382a..f5ca26e53fbb 100644 --- a/tools/perf/util/include/linux/list.h +++ b/tools/perf/util/include/linux/list.h @@ -15,4 +15,12 @@ static inline void list_del_range(struct list_head *begin, begin->prev->next = end->next; end->next->prev = begin->prev; } + +/** + * list_for_each_from - iterate over a list from one of its nodes + * @pos: the &struct list_head to use as a loop cursor, from where to start + * @head: the head for your list. + */ +#define list_for_each_from(pos, head) \ + for (; prefetch(pos->next), pos != (head); pos = pos->next) #endif diff --git a/tools/perf/util/include/linux/types.h b/tools/perf/util/include/linux/types.h index 196862a81a21..12de3b8112f9 100644 --- a/tools/perf/util/include/linux/types.h +++ b/tools/perf/util/include/linux/types.h @@ -6,4 +6,16 @@ #define DECLARE_BITMAP(name,bits) \ unsigned long name[BITS_TO_LONGS(bits)] +struct list_head { + struct list_head *next, *prev; +}; + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + #endif diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index e672f2fef65b..3a7eb6ec0eec 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c @@ -17,16 +17,6 @@ static inline int is_anon_memory(const char *filename) return strcmp(filename, "//anon") == 0; } -static int strcommon(const char *pathname, char *cwd, int cwdlen) -{ - int n = 0; - - while (n < cwdlen && pathname[n] == cwd[n]) - ++n; - - return n; -} - void map__init(struct map *self, enum map_type type, u64 start, u64 end, u64 pgoff, struct dso *dso) { @@ -39,11 +29,12 @@ void map__init(struct map *self, enum map_type type, self->unmap_ip = map__unmap_ip; RB_CLEAR_NODE(&self->rb_node); self->groups = NULL; + self->referenced = false; } struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, u64 pgoff, u32 pid, char *filename, - enum map_type type, char *cwd, int cwdlen) + enum map_type type) { struct map *self = malloc(sizeof(*self)); @@ -52,16 +43,6 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, struct dso *dso; int anon; - if (cwd) { - int n = strcommon(filename, cwd, cwdlen); - - if (n == cwdlen) { - snprintf(newfilename, sizeof(newfilename), - ".%s", filename + n); - filename = newfilename; - } - } - anon = is_anon_memory(filename); if (anon) { @@ -248,6 +229,39 @@ void map_groups__init(struct map_groups *self) self->machine = NULL; } +static void maps__delete(struct rb_root *self) +{ + struct rb_node *next = rb_first(self); + + while (next) { + struct map *pos = rb_entry(next, struct map, rb_node); + + next = rb_next(&pos->rb_node); + rb_erase(&pos->rb_node, self); + map__delete(pos); + } +} + +static void maps__delete_removed(struct list_head *self) +{ + struct map *pos, *n; + + list_for_each_entry_safe(pos, n, self, node) { + list_del(&pos->node); + map__delete(pos); + } +} + +void map_groups__exit(struct map_groups *self) +{ + int i; + + for (i = 0; i < MAP__NR_TYPES; ++i) { + maps__delete(&self->maps[i]); + maps__delete_removed(&self->removed_maps[i]); + } +} + void map_groups__flush(struct map_groups *self) { int type; @@ -374,6 +388,7 @@ int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, { struct rb_root *root = &self->maps[map->type]; struct rb_node *next = rb_first(root); + int err = 0; while (next) { struct map *pos = rb_entry(next, struct map, rb_node); @@ -390,20 +405,16 @@ int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, rb_erase(&pos->rb_node, root); /* - * We may have references to this map, for instance in some - * hist_entry instances, so just move them to a separate - * list. - */ - list_add_tail(&pos->node, &self->removed_maps[map->type]); - /* * Now check if we need to create new maps for areas not * overlapped by the new map: */ if (map->start > pos->start) { struct map *before = map__clone(pos); - if (before == NULL) - return -ENOMEM; + if (before == NULL) { + err = -ENOMEM; + goto move_map; + } before->end = map->start - 1; map_groups__insert(self, before); @@ -414,14 +425,27 @@ int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, if (map->end < pos->end) { struct map *after = map__clone(pos); - if (after == NULL) - return -ENOMEM; + if (after == NULL) { + err = -ENOMEM; + goto move_map; + } after->start = map->end + 1; map_groups__insert(self, after); if (verbose >= 2) map__fprintf(after, fp); } +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]); + else + map__delete(pos); + + if (err) + return err; } return 0; @@ -493,6 +517,11 @@ void maps__insert(struct rb_root *maps, struct map *map) rb_insert_color(&map->rb_node, maps); } +void maps__remove(struct rb_root *self, struct map *map) +{ + rb_erase(&map->rb_node, self); +} + struct map *maps__find(struct rb_root *maps, u64 ip) { struct rb_node **p = &maps->rb_node; @@ -526,6 +555,31 @@ int machine__init(struct machine *self, const char *root_dir, pid_t pid) return self->root_dir == NULL ? -ENOMEM : 0; } +static void dsos__delete(struct list_head *self) +{ + struct dso *pos, *n; + + list_for_each_entry_safe(pos, n, self, node) { + list_del(&pos->node); + dso__delete(pos); + } +} + +void machine__exit(struct machine *self) +{ + map_groups__exit(&self->kmaps); + dsos__delete(&self->user_dsos); + dsos__delete(&self->kernel_dsos); + free(self->root_dir); + self->root_dir = NULL; +} + +void machine__delete(struct machine *self) +{ + machine__exit(self); + free(self); +} + struct machine *machines__add(struct rb_root *self, pid_t pid, const char *root_dir) { diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index f39134512829..b397c0383728 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -29,7 +29,8 @@ struct map { }; u64 start; u64 end; - enum map_type type; + u8 /* enum map_type */ type; + bool referenced; u32 priv; u64 pgoff; @@ -106,7 +107,7 @@ void map__init(struct map *self, enum map_type type, u64 start, u64 end, u64 pgoff, struct dso *dso); struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, u64 pgoff, u32 pid, char *filename, - enum map_type type, char *cwd, int cwdlen); + enum map_type type); void map__delete(struct map *self); struct map *map__clone(struct map *self); int map__overlap(struct map *l, struct map *r); @@ -125,8 +126,10 @@ void map__reloc_vmlinux(struct map *self); size_t __map_groups__fprintf_maps(struct map_groups *self, 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); 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, struct map_groups *parent, enum map_type type); size_t map_groups__fprintf(struct map_groups *self, int verbose, FILE *fp); @@ -142,6 +145,8 @@ struct machine *machines__find(struct rb_root *self, pid_t pid); struct machine *machines__findnew(struct rb_root *self, pid_t pid); char *machine__mmap_name(struct machine *self, char *bf, size_t size); int machine__init(struct machine *self, const char *root_dir, pid_t pid); +void machine__exit(struct machine *self); +void machine__delete(struct machine *self); /* * Default guest kernel is defined by parameter --guestkallsyms @@ -163,6 +168,11 @@ static inline void map_groups__insert(struct map_groups *self, struct map *map) map->groups = self; } +static inline void map_groups__remove(struct map_groups *self, struct map *map) +{ + maps__remove(&self->maps[map->type], map); +} + static inline struct map *map_groups__find(struct map_groups *self, enum map_type type, u64 addr) { @@ -205,6 +215,16 @@ struct symbol *map_groups__find_function_by_name(struct map_groups *self, return map_groups__find_symbol_by_name(self, MAP__FUNCTION, name, mapp, filter); } +static inline +struct symbol *machine__find_kernel_function_by_name(struct machine *self, + const char *name, + struct map **mapp, + symbol_filter_t filter) +{ + return map_groups__find_function_by_name(&self->kmaps, name, mapp, + filter); +} + int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, int verbose, FILE *fp); diff --git a/tools/perf/util/newt.c b/tools/perf/util/newt.c deleted file mode 100644 index 7537ca15900b..000000000000 --- a/tools/perf/util/newt.c +++ /dev/null @@ -1,1178 +0,0 @@ -#define _GNU_SOURCE -#include <stdio.h> -#undef _GNU_SOURCE -/* - * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks - * the build if it isn't defined. Use the equivalent one that glibc - * has on features.h. - */ -#include <features.h> -#ifndef HAVE_LONG_LONG -#define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG -#endif -#include <slang.h> -#include <stdlib.h> -#include <newt.h> -#include <sys/ttydefaults.h> - -#include "cache.h" -#include "hist.h" -#include "pstack.h" -#include "session.h" -#include "sort.h" -#include "symbol.h" - -#if SLANG_VERSION < 20104 -#define slsmg_printf(msg, args...) SLsmg_printf((char *)msg, ##args) -#define slsmg_write_nstring(msg, len) SLsmg_write_nstring((char *)msg, len) -#define sltt_set_color(obj, name, fg, bg) SLtt_set_color(obj,(char *)name,\ - (char *)fg, (char *)bg) -#else -#define slsmg_printf SLsmg_printf -#define slsmg_write_nstring SLsmg_write_nstring -#define sltt_set_color SLtt_set_color -#endif - -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) -{ - /* - * 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); -} - -static void ui_helpline__pop(void) -{ - newtPopHelpLine(); -} - -static void ui_helpline__push(const char *msg) -{ - newtPushHelpLine(msg); -} - -static void ui_helpline__vpush(const char *fmt, va_list ap) -{ - char *s; - - if (vasprintf(&s, fmt, ap) < 0) - vfprintf(stderr, fmt, ap); - else { - ui_helpline__push(s); - free(s); - } -} - -static void ui_helpline__fpush(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - ui_helpline__vpush(fmt, ap); - va_end(ap); -} - -static void ui_helpline__puts(const char *msg) -{ - ui_helpline__pop(); - ui_helpline__push(msg); -} - -static char browser__last_msg[1024]; - -int browser__show_help(const char *format, va_list ap) -{ - int ret; - static int backlog; - - ret = vsnprintf(browser__last_msg + backlog, - sizeof(browser__last_msg) - backlog, format, ap); - backlog += ret; - - if (browser__last_msg[backlog - 1] == '\n') { - ui_helpline__puts(browser__last_msg); - newtRefresh(); - backlog = 0; - } - - return ret; -} - -static void newt_form__set_exit_keys(newtComponent self) -{ - newtFormAddHotKey(self, NEWT_KEY_LEFT); - newtFormAddHotKey(self, NEWT_KEY_ESCAPE); - newtFormAddHotKey(self, 'Q'); - newtFormAddHotKey(self, 'q'); - newtFormAddHotKey(self, CTRL('c')); -} - -static newtComponent newt_form__new(void) -{ - newtComponent self = newtForm(NULL, NULL, 0); - if (self) - newt_form__set_exit_keys(self); - return self; -} - -static int popup_menu(int argc, char * const argv[]) -{ - struct newtExitStruct es; - int i, rc = -1, max_len = 5; - newtComponent listbox, form = newt_form__new(); - - if (form == NULL) - return -1; - - listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT); - if (listbox == NULL) - goto out_destroy_form; - - newtFormAddComponent(form, listbox); - - for (i = 0; i < argc; ++i) { - int len = strlen(argv[i]); - if (len > max_len) - max_len = len; - if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i)) - goto out_destroy_form; - } - - newtCenteredWindow(max_len, argc, NULL); - newtFormRun(form, &es); - rc = newtListboxGetCurrent(listbox) - NULL; - if (es.reason == NEWT_EXIT_HOTKEY) - rc = -1; - newtPopWindow(); -out_destroy_form: - newtFormDestroy(form); - return rc; -} - -static int ui__help_window(const char *text) -{ - struct newtExitStruct es; - newtComponent tb, form = newt_form__new(); - int rc = -1; - int max_len = 0, nr_lines = 0; - const char *t; - - if (form == NULL) - return -1; - - t = text; - while (1) { - const char *sep = strchr(t, '\n'); - int len; - - if (sep == NULL) - sep = strchr(t, '\0'); - len = sep - t; - if (max_len < len) - max_len = len; - ++nr_lines; - if (*sep == '\0') - break; - t = sep + 1; - } - - tb = newtTextbox(0, 0, max_len, nr_lines, 0); - if (tb == NULL) - goto out_destroy_form; - - newtTextboxSetText(tb, text); - newtFormAddComponent(form, tb); - newtCenteredWindow(max_len, nr_lines, NULL); - newtFormRun(form, &es); - newtPopWindow(); - rc = 0; -out_destroy_form: - newtFormDestroy(form); - return rc; -} - -static bool dialog_yesno(const char *msg) -{ - /* newtWinChoice should really be accepting const char pointers... */ - char yes[] = "Yes", no[] = "No"; - return newtWinChoice(NULL, yes, no, (char *)msg) == 1; -} - -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); -} - -#define HE_COLORSET_TOP 50 -#define HE_COLORSET_MEDIUM 51 -#define HE_COLORSET_NORMAL 52 -#define HE_COLORSET_SELECTED 53 -#define HE_COLORSET_CODE 54 - -static int ui_browser__percent_color(double percent, bool current) -{ - if (current) - return HE_COLORSET_SELECTED; - if (percent >= MIN_RED) - return HE_COLORSET_TOP; - if (percent >= MIN_GREEN) - return HE_COLORSET_MEDIUM; - return HE_COLORSET_NORMAL; -} - -struct ui_browser { - newtComponent form, sb; - u64 index, first_visible_entry_idx; - void *first_visible_entry, *entries; - u16 top, left, width, height; - void *priv; - u32 nr_entries; -}; - -static void ui_browser__refresh_dimensions(struct ui_browser *self) -{ - int cols, rows; - newtGetScreenSize(&cols, &rows); - - if (self->width > cols - 4) - self->width = cols - 4; - self->height = rows - 5; - if (self->height > self->nr_entries) - self->height = self->nr_entries; - self->top = (rows - self->height) / 2; - self->left = (cols - self->width) / 2; -} - -static void ui_browser__reset_index(struct ui_browser *self) -{ - self->index = self->first_visible_entry_idx = 0; - self->first_visible_entry = NULL; -} - -static int objdump_line__show(struct objdump_line *self, struct list_head *head, - int width, struct hist_entry *he, int len, - bool current_entry) -{ - if (self->offset != -1) { - struct symbol *sym = he->ms.sym; - unsigned int hits = 0; - double percent = 0.0; - int color; - struct sym_priv *priv = symbol__priv(sym); - struct sym_ext *sym_ext = priv->ext; - struct sym_hist *h = priv->hist; - s64 offset = self->offset; - struct objdump_line *next = objdump__get_next_ip_line(head, self); - - while (offset < (s64)len && - (next == NULL || offset < next->offset)) { - if (sym_ext) { - percent += sym_ext[offset].percent; - } else - hits += h->ip[offset]; - - ++offset; - } - - if (sym_ext == NULL && h->sum) - percent = 100.0 * hits / h->sum; - - color = ui_browser__percent_color(percent, current_entry); - SLsmg_set_color(color); - slsmg_printf(" %7.2f ", percent); - if (!current_entry) - SLsmg_set_color(HE_COLORSET_CODE); - } else { - int color = ui_browser__percent_color(0, current_entry); - SLsmg_set_color(color); - slsmg_write_nstring(" ", 9); - } - - SLsmg_write_char(':'); - slsmg_write_nstring(" ", 8); - if (!*self->line) - slsmg_write_nstring(" ", width - 18); - else - slsmg_write_nstring(self->line, width - 18); - - return 0; -} - -static int ui_browser__refresh_entries(struct ui_browser *self) -{ - struct objdump_line *pos; - struct list_head *head = self->entries; - struct hist_entry *he = self->priv; - int row = 0; - int len = he->ms.sym->end - he->ms.sym->start; - - if (self->first_visible_entry == NULL || self->first_visible_entry == self->entries) - self->first_visible_entry = head->next; - - pos = list_entry(self->first_visible_entry, struct objdump_line, node); - - list_for_each_entry_from(pos, head, node) { - bool current_entry = (self->first_visible_entry_idx + row) == self->index; - SLsmg_gotorc(self->top + row, self->left); - objdump_line__show(pos, head, self->width, - he, len, current_entry); - if (++row == self->height) - break; - } - - SLsmg_set_color(HE_COLORSET_NORMAL); - SLsmg_fill_region(self->top + row, self->left, - self->height - row, self->width, ' '); - - return 0; -} - -static int ui_browser__run(struct ui_browser *self, const char *title, - struct newtExitStruct *es) -{ - if (self->form) { - newtFormDestroy(self->form); - newtPopWindow(); - } - - ui_browser__refresh_dimensions(self); - newtCenteredWindow(self->width + 2, self->height, title); - self->form = newt_form__new(); - if (self->form == NULL) - return -1; - - self->sb = newtVerticalScrollbar(self->width + 1, 0, self->height, - HE_COLORSET_NORMAL, - HE_COLORSET_SELECTED); - if (self->sb == NULL) - return -1; - - newtFormAddHotKey(self->form, NEWT_KEY_UP); - newtFormAddHotKey(self->form, NEWT_KEY_DOWN); - newtFormAddHotKey(self->form, NEWT_KEY_PGUP); - newtFormAddHotKey(self->form, NEWT_KEY_PGDN); - newtFormAddHotKey(self->form, ' '); - newtFormAddHotKey(self->form, NEWT_KEY_HOME); - newtFormAddHotKey(self->form, NEWT_KEY_END); - newtFormAddHotKey(self->form, NEWT_KEY_TAB); - newtFormAddHotKey(self->form, NEWT_KEY_RIGHT); - - if (ui_browser__refresh_entries(self) < 0) - return -1; - newtFormAddComponent(self->form, self->sb); - - while (1) { - unsigned int offset; - - newtFormRun(self->form, es); - - if (es->reason != NEWT_EXIT_HOTKEY) - break; - if (is_exit_key(es->u.key)) - return es->u.key; - switch (es->u.key) { - case NEWT_KEY_DOWN: - if (self->index == self->nr_entries - 1) - break; - ++self->index; - if (self->index == self->first_visible_entry_idx + self->height) { - struct list_head *pos = self->first_visible_entry; - ++self->first_visible_entry_idx; - self->first_visible_entry = pos->next; - } - break; - case NEWT_KEY_UP: - if (self->index == 0) - break; - --self->index; - if (self->index < self->first_visible_entry_idx) { - struct list_head *pos = self->first_visible_entry; - --self->first_visible_entry_idx; - self->first_visible_entry = pos->prev; - } - break; - case NEWT_KEY_PGDN: - case ' ': - if (self->first_visible_entry_idx + self->height > self->nr_entries - 1) - break; - - offset = self->height; - if (self->index + offset > self->nr_entries - 1) - offset = self->nr_entries - 1 - self->index; - self->index += offset; - self->first_visible_entry_idx += offset; - - while (offset--) { - struct list_head *pos = self->first_visible_entry; - self->first_visible_entry = pos->next; - } - - break; - case NEWT_KEY_PGUP: - if (self->first_visible_entry_idx == 0) - break; - - if (self->first_visible_entry_idx < self->height) - offset = self->first_visible_entry_idx; - else - offset = self->height; - - self->index -= offset; - self->first_visible_entry_idx -= offset; - - while (offset--) { - struct list_head *pos = self->first_visible_entry; - self->first_visible_entry = pos->prev; - } - break; - case NEWT_KEY_HOME: - ui_browser__reset_index(self); - break; - case NEWT_KEY_END: { - struct list_head *head = self->entries; - offset = self->height - 1; - - if (offset > self->nr_entries) - offset = self->nr_entries; - - self->index = self->first_visible_entry_idx = self->nr_entries - 1 - offset; - self->first_visible_entry = head->prev; - while (offset-- != 0) { - struct list_head *pos = self->first_visible_entry; - self->first_visible_entry = pos->prev; - } - } - break; - case NEWT_KEY_RIGHT: - case NEWT_KEY_LEFT: - case NEWT_KEY_TAB: - return es->u.key; - default: - continue; - } - if (ui_browser__refresh_entries(self) < 0) - return -1; - } - return 0; -} - -/* - * When debugging newt problems it was useful to be able to "unroll" - * the calls to newtCheckBoxTreeAdd{Array,Item}, so that we can generate - * a source file with the sequence of calls to these methods, to then - * tweak the arrays to get the intended results, so I'm keeping this code - * here, may be useful again in the future. - */ -#undef NEWT_DEBUG - -static void newt_checkbox_tree__add(newtComponent tree, const char *str, - void *priv, int *indexes) -{ -#ifdef NEWT_DEBUG - /* Print the newtCheckboxTreeAddArray to tinker with its index arrays */ - int i = 0, len = 40 - strlen(str); - - fprintf(stderr, - "\tnewtCheckboxTreeAddItem(tree, %*.*s\"%s\", (void *)%p, 0, ", - len, len, " ", str, priv); - while (indexes[i] != NEWT_ARG_LAST) { - if (indexes[i] != NEWT_ARG_APPEND) - fprintf(stderr, " %d,", indexes[i]); - else - fprintf(stderr, " %s,", "NEWT_ARG_APPEND"); - ++i; - } - fprintf(stderr, " %s", " NEWT_ARG_LAST);\n"); - fflush(stderr); -#endif - newtCheckboxTreeAddArray(tree, str, priv, 0, indexes); -} - -static char *callchain_list__sym_name(struct callchain_list *self, - char *bf, size_t bfsize) -{ - if (self->ms.sym) - return self->ms.sym->name; - - snprintf(bf, bfsize, "%#Lx", self->ip); - return bf; -} - -static void __callchain__append_graph_browser(struct callchain_node *self, - newtComponent tree, u64 total, - int *indexes, int depth) -{ - struct rb_node *node; - u64 new_total, remaining; - int idx = 0; - - if (callchain_param.mode == CHAIN_GRAPH_REL) - new_total = self->children_hit; - else - new_total = total; - - remaining = new_total; - node = rb_first(&self->rb_root); - while (node) { - struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); - struct rb_node *next = rb_next(node); - u64 cumul = cumul_hits(child); - struct callchain_list *chain; - int first = true, printed = 0; - int chain_idx = -1; - remaining -= cumul; - - indexes[depth] = NEWT_ARG_APPEND; - indexes[depth + 1] = NEWT_ARG_LAST; - - list_for_each_entry(chain, &child->val, list) { - char ipstr[BITS_PER_LONG / 4 + 1], - *alloc_str = NULL; - const char *str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); - - if (first) { - double percent = cumul * 100.0 / new_total; - - first = false; - if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) - str = "Not enough memory!"; - else - str = alloc_str; - } else { - indexes[depth] = idx; - indexes[depth + 1] = NEWT_ARG_APPEND; - indexes[depth + 2] = NEWT_ARG_LAST; - ++chain_idx; - } - newt_checkbox_tree__add(tree, str, &chain->ms, indexes); - free(alloc_str); - ++printed; - } - - indexes[depth] = idx; - if (chain_idx != -1) - indexes[depth + 1] = chain_idx; - if (printed != 0) - ++idx; - __callchain__append_graph_browser(child, tree, new_total, indexes, - depth + (chain_idx != -1 ? 2 : 1)); - node = next; - } -} - -static void callchain__append_graph_browser(struct callchain_node *self, - newtComponent tree, u64 total, - int *indexes, int parent_idx) -{ - struct callchain_list *chain; - int i = 0; - - indexes[1] = NEWT_ARG_APPEND; - indexes[2] = NEWT_ARG_LAST; - - list_for_each_entry(chain, &self->val, list) { - char ipstr[BITS_PER_LONG / 4 + 1], *str; - - if (chain->ip >= PERF_CONTEXT_MAX) - continue; - - if (!i++ && sort__first_dimension == SORT_SYM) - continue; - - str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); - newt_checkbox_tree__add(tree, str, &chain->ms, indexes); - } - - indexes[1] = parent_idx; - indexes[2] = NEWT_ARG_APPEND; - indexes[3] = NEWT_ARG_LAST; - __callchain__append_graph_browser(self, tree, total, indexes, 2); -} - -static void hist_entry__append_callchain_browser(struct hist_entry *self, - newtComponent tree, u64 total, int parent_idx) -{ - struct rb_node *rb_node; - int indexes[1024] = { [0] = parent_idx, }; - int idx = 0; - struct callchain_node *chain; - - rb_node = rb_first(&self->sorted_chain); - while (rb_node) { - chain = rb_entry(rb_node, struct callchain_node, rb_node); - switch (callchain_param.mode) { - case CHAIN_FLAT: - break; - case CHAIN_GRAPH_ABS: /* falldown */ - case CHAIN_GRAPH_REL: - callchain__append_graph_browser(chain, tree, total, indexes, idx++); - break; - case CHAIN_NONE: - default: - break; - } - rb_node = rb_next(rb_node); - } -} - -static size_t hist_entry__append_browser(struct hist_entry *self, - newtComponent tree, u64 total) -{ - char s[256]; - size_t ret; - - if (symbol_conf.exclude_other && !self->parent) - return 0; - - ret = hist_entry__snprintf(self, s, sizeof(s), NULL, - false, 0, false, total); - if (symbol_conf.use_callchain) { - int indexes[2]; - - indexes[0] = NEWT_ARG_APPEND; - indexes[1] = NEWT_ARG_LAST; - newt_checkbox_tree__add(tree, s, &self->ms, indexes); - } else - newtListboxAppendEntry(tree, s, &self->ms); - - return ret; -} - -int hist_entry__tui_annotate(struct hist_entry *self) -{ - struct ui_browser browser; - struct newtExitStruct es; - struct objdump_line *pos, *n; - LIST_HEAD(head); - int ret; - - if (self->ms.sym == NULL) - return -1; - - if (self->ms.map->dso->annotate_warned) - return -1; - - if (hist_entry__annotate(self, &head) < 0) { - ui__error_window(browser__last_msg); - return -1; - } - - ui_helpline__push("Press <- or ESC to exit"); - - memset(&browser, 0, sizeof(browser)); - browser.entries = &head; - browser.priv = self; - list_for_each_entry(pos, &head, node) { - size_t line_len = strlen(pos->line); - if (browser.width < line_len) - browser.width = line_len; - ++browser.nr_entries; - } - - browser.width += 18; /* Percentage */ - ret = ui_browser__run(&browser, self->ms.sym->name, &es); - newtFormDestroy(browser.form); - newtPopWindow(); - list_for_each_entry_safe(pos, n, &head, node) { - list_del(&pos->node); - objdump_line__free(pos); - } - ui_helpline__pop(); - return ret; -} - -static const void *newt__symbol_tree_get_current(newtComponent self) -{ - if (symbol_conf.use_callchain) - return newtCheckboxTreeGetCurrent(self); - return newtListboxGetCurrent(self); -} - -static void hist_browser__selection(newtComponent self, void *data) -{ - const struct map_symbol **symbol_ptr = data; - *symbol_ptr = newt__symbol_tree_get_current(self); -} - -struct hist_browser { - newtComponent form, tree; - const struct map_symbol *selection; -}; - -static struct hist_browser *hist_browser__new(void) -{ - struct hist_browser *self = malloc(sizeof(*self)); - - if (self != NULL) - self->form = NULL; - - return self; -} - -static void hist_browser__delete(struct hist_browser *self) -{ - newtFormDestroy(self->form); - newtPopWindow(); - free(self); -} - -static int hist_browser__populate(struct hist_browser *self, struct hists *hists, - const char *title) -{ - int max_len = 0, idx, cols, rows; - struct ui_progress *progress; - struct rb_node *nd; - u64 curr_hist = 0; - char seq[] = ".", unit; - char str[256]; - unsigned long nr_events = hists->stats.nr_events[PERF_RECORD_SAMPLE]; - - if (self->form) { - newtFormDestroy(self->form); - newtPopWindow(); - } - - nr_events = convert_unit(nr_events, &unit); - snprintf(str, sizeof(str), "Events: %lu%c ", - nr_events, unit); - newtDrawRootText(0, 0, str); - - newtGetScreenSize(NULL, &rows); - - if (symbol_conf.use_callchain) - self->tree = newtCheckboxTreeMulti(0, 0, rows - 5, seq, - NEWT_FLAG_SCROLL); - else - self->tree = newtListbox(0, 0, rows - 5, - (NEWT_FLAG_SCROLL | - NEWT_FLAG_RETURNEXIT)); - - newtComponentAddCallback(self->tree, hist_browser__selection, - &self->selection); - - progress = ui_progress__new("Adding entries to the browser...", - hists->nr_entries); - if (progress == NULL) - return -1; - - idx = 0; - for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { - struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - int len; - - if (h->filtered) - continue; - - len = hist_entry__append_browser(h, self->tree, hists->stats.total_period); - if (len > max_len) - max_len = len; - if (symbol_conf.use_callchain) - hist_entry__append_callchain_browser(h, self->tree, - hists->stats.total_period, idx++); - ++curr_hist; - if (curr_hist % 5) - ui_progress__update(progress, curr_hist); - } - - ui_progress__delete(progress); - - newtGetScreenSize(&cols, &rows); - - if (max_len > cols) - max_len = cols - 3; - - if (!symbol_conf.use_callchain) - newtListboxSetWidth(self->tree, max_len); - - newtCenteredWindow(max_len + (symbol_conf.use_callchain ? 5 : 0), - rows - 5, title); - self->form = newt_form__new(); - if (self->form == NULL) - return -1; - - newtFormAddHotKey(self->form, 'A'); - newtFormAddHotKey(self->form, 'a'); - newtFormAddHotKey(self->form, 'D'); - newtFormAddHotKey(self->form, 'd'); - newtFormAddHotKey(self->form, 'T'); - newtFormAddHotKey(self->form, 't'); - newtFormAddHotKey(self->form, '?'); - newtFormAddHotKey(self->form, 'H'); - newtFormAddHotKey(self->form, 'h'); - newtFormAddHotKey(self->form, NEWT_KEY_F1); - newtFormAddHotKey(self->form, NEWT_KEY_RIGHT); - newtFormAddHotKey(self->form, NEWT_KEY_TAB); - newtFormAddHotKey(self->form, NEWT_KEY_UNTAB); - newtFormAddComponents(self->form, self->tree, NULL); - self->selection = newt__symbol_tree_get_current(self->tree); - - return 0; -} - -static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) -{ - int *indexes; - - if (!symbol_conf.use_callchain) - goto out; - - indexes = newtCheckboxTreeFindItem(self->tree, (void *)self->selection); - if (indexes) { - bool is_hist_entry = indexes[1] == NEWT_ARG_LAST; - free(indexes); - if (is_hist_entry) - goto out; - } - return NULL; -out: - return container_of(self->selection, struct hist_entry, ms); -} - -static struct thread *hist_browser__selected_thread(struct hist_browser *self) -{ - struct hist_entry *he = hist_browser__selected_entry(self); - return he ? he->thread : NULL; -} - -static int hist_browser__title(char *bf, size_t size, const char *ev_name, - const struct dso *dso, const struct thread *thread) -{ - int printed = 0; - - if (thread) - printed += snprintf(bf + printed, size - printed, - "Thread: %s(%d)", - (thread->comm_set ? thread->comm : ""), - thread->pid); - if (dso) - printed += snprintf(bf + printed, size - printed, - "%sDSO: %s", thread ? " " : "", - dso->short_name); - return printed ?: snprintf(bf, size, "Event: %s", ev_name); -} - -int hists__browse(struct hists *self, const char *helpline, const char *ev_name) -{ - struct hist_browser *browser = hist_browser__new(); - struct pstack *fstack; - const struct thread *thread_filter = NULL; - const struct dso *dso_filter = NULL; - struct newtExitStruct es; - char msg[160]; - int key = -1; - - if (browser == NULL) - return -1; - - fstack = pstack__new(2); - if (fstack == NULL) - goto out; - - ui_helpline__push(helpline); - - hist_browser__title(msg, sizeof(msg), ev_name, - dso_filter, thread_filter); - if (hist_browser__populate(browser, self, msg) < 0) - goto out_free_stack; - - while (1) { - const struct thread *thread; - const struct dso *dso; - char *options[16]; - int nr_options = 0, choice = 0, i, - annotate = -2, zoom_dso = -2, zoom_thread = -2; - - newtFormRun(browser->form, &es); - - thread = hist_browser__selected_thread(browser); - dso = browser->selection->map ? browser->selection->map->dso : NULL; - - if (es.reason == NEWT_EXIT_HOTKEY) { - key = es.u.key; - - switch (key) { - case NEWT_KEY_F1: - goto do_help; - case NEWT_KEY_TAB: - case NEWT_KEY_UNTAB: - /* - * Exit the browser, let hists__browser_tree - * go to the next or previous - */ - goto out_free_stack; - default:; - } - - key = toupper(key); - switch (key) { - case 'A': - if (browser->selection->map == NULL && - browser->selection->map->dso->annotate_warned) - continue; - goto do_annotate; - case 'D': - goto zoom_dso; - case 'T': - goto zoom_thread; - case 'H': - case '?': -do_help: - ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n" - "<- Zoom out\n" - "a Annotate current symbol\n" - "h/?/F1 Show this window\n" - "d Zoom into current DSO\n" - "t Zoom into current Thread\n" - "q/CTRL+C Exit browser"); - continue; - default:; - } - if (is_exit_key(key)) { - if (key == NEWT_KEY_ESCAPE) { - if (dialog_yesno("Do you really want to exit?")) - break; - else - continue; - } else - break; - } - - if (es.u.key == NEWT_KEY_LEFT) { - const void *top; - - if (pstack__empty(fstack)) - continue; - top = pstack__pop(fstack); - if (top == &dso_filter) - goto zoom_out_dso; - if (top == &thread_filter) - goto zoom_out_thread; - continue; - } - } - - if (browser->selection->sym != NULL && - !browser->selection->map->dso->annotate_warned && - asprintf(&options[nr_options], "Annotate %s", - browser->selection->sym->name) > 0) - annotate = nr_options++; - - if (thread != NULL && - asprintf(&options[nr_options], "Zoom %s %s(%d) thread", - (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"), - (dso->kernel ? "the Kernel" : dso->short_name)) > 0) - zoom_dso = nr_options++; - - options[nr_options++] = (char *)"Exit"; - - choice = popup_menu(nr_options, options); - - for (i = 0; i < nr_options - 1; ++i) - free(options[i]); - - if (choice == nr_options - 1) - break; - - if (choice == -1) - continue; - - if (choice == annotate) { - struct hist_entry *he; -do_annotate: - if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) { - browser->selection->map->dso->annotate_warned = 1; - ui_helpline__puts("No vmlinux file found, can't " - "annotate with just a " - "kallsyms file"); - continue; - } - - he = hist_browser__selected_entry(browser); - if (he == NULL) - continue; - - hist_entry__tui_annotate(he); - } else if (choice == zoom_dso) { -zoom_dso: - if (dso_filter) { - pstack__remove(fstack, &dso_filter); -zoom_out_dso: - ui_helpline__pop(); - dso_filter = NULL; - } 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); - } - hists__filter_by_dso(self, dso_filter); - hist_browser__title(msg, sizeof(msg), ev_name, - dso_filter, thread_filter); - if (hist_browser__populate(browser, self, msg) < 0) - goto out; - } else if (choice == zoom_thread) { -zoom_thread: - if (thread_filter) { - pstack__remove(fstack, &thread_filter); -zoom_out_thread: - ui_helpline__pop(); - thread_filter = NULL; - } 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); - } - hists__filter_by_thread(self, thread_filter); - hist_browser__title(msg, sizeof(msg), ev_name, - dso_filter, thread_filter); - if (hist_browser__populate(browser, self, msg) < 0) - goto out; - } - } -out_free_stack: - pstack__delete(fstack); -out: - hist_browser__delete(browser); - return key; -} - -int hists__tui_browse_tree(struct rb_root *self, const char *help) -{ - struct rb_node *first = rb_first(self), *nd = first, *next; - int key = 0; - - while (nd) { - struct hists *hists = rb_entry(nd, struct hists, rb_node); - const char *ev_name = __event_name(hists->type, hists->config); - - key = hists__browse(hists, help, ev_name); - - if (is_exit_key(key)) - break; - - switch (key) { - case NEWT_KEY_TAB: - next = rb_next(nd); - if (next) - nd = next; - break; - case NEWT_KEY_UNTAB: - if (nd == first) - continue; - nd = rb_prev(nd); - default: - break; - } - } - - return key; -} - -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", -}; - -void setup_browser(void) -{ - struct newtPercentTreeColors *c = &defaultPercentTreeColors; - - if (!isatty(1) || !use_browser || dump_trace) { - use_browser = 0; - setup_pager(); - return; - } - - use_browser = 1; - newtInit(); - newtCls(); - ui_helpline__puts(" "); - 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); -} - -void exit_browser(bool wait_for_ok) -{ - if (use_browser > 0) { - if (wait_for_ok) { - char title[] = "Fatal Error", ok[] = "Ok"; - newtWinMessage(title, ok, browser__last_msg); - } - newtFinished(); - } -} diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 9bf0f402ca73..4af5bd59cfd1 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -602,8 +602,15 @@ parse_breakpoint_event(const char **strp, struct perf_event_attr *attr) return EVT_FAILED; } - /* We should find a nice way to override the access type */ - attr->bp_len = HW_BREAKPOINT_LEN_4; + /* + * We should find a nice way to override the access length + * Provide some defaults for now + */ + if (attr->bp_type == HW_BREAKPOINT_X) + attr->bp_len = sizeof(long); + else + attr->bp_len = HW_BREAKPOINT_LEN_4; + attr->type = PERF_TYPE_BREAKPOINT; return EVT_HANDLED; diff --git a/tools/perf/util/path.c b/tools/perf/util/path.c index 58a470d036dd..bd7497711424 100644 --- a/tools/perf/util/path.c +++ b/tools/perf/util/path.c @@ -22,6 +22,7 @@ static const char *get_perf_dir(void) return "."; } +#ifdef NO_STRLCPY size_t strlcpy(char *dest, const char *src, size_t size) { size_t ret = strlen(src); @@ -33,7 +34,7 @@ size_t strlcpy(char *dest, const char *src, size_t size) } return ret; } - +#endif static char *get_pathname(void) { diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 914c67095d96..3b6a5297bf16 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -1,5 +1,5 @@ /* - * probe-event.c : perf-probe definition to kprobe_events format converter + * probe-event.c : perf-probe definition to probe_events format converter * * Written by Masami Hiramatsu <mhiramat@redhat.com> * @@ -74,10 +74,9 @@ static int e_snprintf(char *str, size_t size, const char *format, ...) static char *synthesize_perf_probe_point(struct perf_probe_point *pp); static struct machine machine; -/* Initialize symbol maps and path of vmlinux */ +/* Initialize symbol maps and path of vmlinux/modules */ static int init_vmlinux(void) { - struct dso *kernel; int ret; symbol_conf.sort_by_name = true; @@ -91,51 +90,81 @@ static int init_vmlinux(void) goto out; } - ret = machine__init(&machine, "/", 0); + ret = machine__init(&machine, "", HOST_KERNEL_ID); if (ret < 0) goto out; - kernel = dso__new_kernel(symbol_conf.vmlinux_name); - if (kernel == NULL) - die("Failed to create kernel dso."); - - ret = __machine__create_kernel_maps(&machine, kernel); - if (ret < 0) - pr_debug("Failed to create kernel maps.\n"); - + if (machine__create_kernel_maps(&machine) < 0) { + pr_debug("machine__create_kernel_maps "); + goto out; + } out: if (ret < 0) pr_warning("Failed to init vmlinux path.\n"); return ret; } +static struct symbol *__find_kernel_function_by_name(const char *name, + struct map **mapp) +{ + return machine__find_kernel_function_by_name(&machine, name, mapp, + NULL); +} + +const char *kernel_get_module_path(const char *module) +{ + struct dso *dso; + + if (module) { + list_for_each_entry(dso, &machine.kernel_dsos, node) { + if (strncmp(dso->short_name + 1, module, + dso->short_name_len - 2) == 0) + goto found; + } + pr_debug("Failed to find module %s.\n", module); + return NULL; + } else { + dso = machine.vmlinux_maps[MAP__FUNCTION]->dso; + if (dso__load_vmlinux_path(dso, + machine.vmlinux_maps[MAP__FUNCTION], NULL) < 0) { + pr_debug("Failed to load kernel map.\n"); + return NULL; + } + } +found: + return dso->long_name; +} + #ifdef DWARF_SUPPORT -static int open_vmlinux(void) +static int open_vmlinux(const char *module) { - if (map__load(machine.vmlinux_maps[MAP__FUNCTION], NULL) < 0) { - pr_debug("Failed to load kernel map.\n"); - return -EINVAL; + const char *path = kernel_get_module_path(module); + if (!path) { + pr_err("Failed to find path of %s module", module ?: "kernel"); + return -ENOENT; } - pr_debug("Try to open %s\n", machine.vmlinux_maps[MAP__FUNCTION]->dso->long_name); - return open(machine.vmlinux_maps[MAP__FUNCTION]->dso->long_name, O_RDONLY); + pr_debug("Try to open %s\n", path); + return open(path, O_RDONLY); } -/* Convert trace point to probe point with debuginfo */ -static int convert_to_perf_probe_point(struct kprobe_trace_point *tp, - struct perf_probe_point *pp) +/* + * Convert trace point to probe point with debuginfo + * Currently only handles kprobes. + */ +static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, + struct perf_probe_point *pp) { struct symbol *sym; - int fd, ret = -ENOENT; + struct map *map; + u64 addr; + int ret = -ENOENT; - sym = map__find_symbol_by_name(machine.vmlinux_maps[MAP__FUNCTION], - tp->symbol, NULL); + sym = __find_kernel_function_by_name(tp->symbol, &map); if (sym) { - fd = open_vmlinux(); - if (fd >= 0) { - ret = find_perf_probe_point(fd, - sym->start + tp->offset, pp); - close(fd); - } + addr = map->unmap_ip(map, sym->start + tp->offset); + pr_debug("try to find %s+%ld@%llx\n", tp->symbol, + tp->offset, addr); + ret = find_perf_probe_point((unsigned long)addr, pp); } if (ret <= 0) { pr_debug("Failed to find corresponding probes from " @@ -151,14 +180,14 @@ static int convert_to_perf_probe_point(struct kprobe_trace_point *tp, } /* Try to find perf_probe_event with debuginfo */ -static int try_to_find_kprobe_trace_events(struct perf_probe_event *pev, - struct kprobe_trace_event **tevs, - int max_tevs) +static int try_to_find_probe_trace_events(struct perf_probe_event *pev, + struct probe_trace_event **tevs, + int max_tevs, const char *module) { bool need_dwarf = perf_probe_event_need_dwarf(pev); int fd, ntevs; - fd = open_vmlinux(); + fd = open_vmlinux(module); if (fd < 0) { if (need_dwarf) { pr_warning("Failed to open debuginfo file.\n"); @@ -169,11 +198,11 @@ static int try_to_find_kprobe_trace_events(struct perf_probe_event *pev, } /* Searching trace events corresponding to probe event */ - ntevs = find_kprobe_trace_events(fd, pev, tevs, max_tevs); + ntevs = find_probe_trace_events(fd, pev, tevs, max_tevs); close(fd); if (ntevs > 0) { /* Succeeded to find trace events */ - pr_debug("find %d kprobe_trace_events.\n", ntevs); + pr_debug("find %d probe_trace_events.\n", ntevs); return ntevs; } @@ -195,6 +224,65 @@ static int try_to_find_kprobe_trace_events(struct perf_probe_event *pev, return ntevs; } +/* + * Find a src file from a DWARF tag path. Prepend optional source path prefix + * and chop off leading directories that do not exist. Result is passed back as + * a newly allocated path on success. + * Return 0 if file was found and readable, -errno otherwise. + */ +static int get_real_path(const char *raw_path, const char *comp_dir, + char **new_path) +{ + const char *prefix = symbol_conf.source_prefix; + + if (!prefix) { + if (raw_path[0] != '/' && comp_dir) + /* If not an absolute path, try to use comp_dir */ + prefix = comp_dir; + else { + if (access(raw_path, R_OK) == 0) { + *new_path = strdup(raw_path); + return 0; + } else + return -errno; + } + } + + *new_path = malloc((strlen(prefix) + strlen(raw_path) + 2)); + if (!*new_path) + return -ENOMEM; + + for (;;) { + sprintf(*new_path, "%s/%s", prefix, raw_path); + + if (access(*new_path, R_OK) == 0) + return 0; + + if (!symbol_conf.source_prefix) + /* In case of searching comp_dir, don't retry */ + return -errno; + + switch (errno) { + case ENAMETOOLONG: + case ENOENT: + case EROFS: + case EFAULT: + raw_path = strchr(++raw_path, '/'); + if (!raw_path) { + free(*new_path); + *new_path = NULL; + return -ENOENT; + } + continue; + + default: + free(*new_path); + *new_path = NULL; + return -errno; + } + } +} + #define LINEBUF_SIZE 256 #define NR_ADDITIONAL_LINES 2 @@ -238,19 +326,20 @@ error: * Show line-range always requires debuginfo to find source file and * line number. */ -int show_line_range(struct line_range *lr) +int show_line_range(struct line_range *lr, const char *module) { int l = 1; struct line_node *ln; FILE *fp; int fd, ret; + char *tmp; /* Search a line range */ ret = init_vmlinux(); if (ret < 0) return ret; - fd = open_vmlinux(); + fd = open_vmlinux(module); if (fd < 0) { pr_warning("Failed to open debuginfo file.\n"); return fd; @@ -266,6 +355,15 @@ int show_line_range(struct line_range *lr) return ret; } + /* Convert source file path */ + tmp = lr->path; + ret = get_real_path(tmp, lr->comp_dir, &lr->path); + free(tmp); /* Free old path */ + if (ret < 0) { + pr_warning("Failed to find source file. (%d)\n", ret); + return ret; + } + setup_pager(); if (lr->function) @@ -306,11 +404,84 @@ end: return ret; } +static int show_available_vars_at(int fd, struct perf_probe_event *pev, + int max_vls, bool externs) +{ + char *buf; + int ret, i; + struct str_node *node; + struct variable_list *vls = NULL, *vl; + + buf = synthesize_perf_probe_point(&pev->point); + if (!buf) + return -EINVAL; + pr_debug("Searching variables at %s\n", buf); + + ret = find_available_vars_at(fd, pev, &vls, max_vls, externs); + if (ret > 0) { + /* Some variables were found */ + fprintf(stdout, "Available variables at %s\n", buf); + for (i = 0; i < ret; i++) { + vl = &vls[i]; + /* + * A probe point might be converted to + * several trace points. + */ + fprintf(stdout, "\t@<%s+%lu>\n", vl->point.symbol, + vl->point.offset); + free(vl->point.symbol); + if (vl->vars) { + strlist__for_each(node, vl->vars) + fprintf(stdout, "\t\t%s\n", node->s); + strlist__delete(vl->vars); + } else + fprintf(stdout, "(No variables)\n"); + } + free(vls); + } else + pr_err("Failed to find variables at %s (%d)\n", buf, ret); + + free(buf); + return ret; +} + +/* Show available variables on given probe point */ +int show_available_vars(struct perf_probe_event *pevs, int npevs, + int max_vls, const char *module, bool externs) +{ + int i, fd, ret = 0; + + ret = init_vmlinux(); + if (ret < 0) + return ret; + + fd = open_vmlinux(module); + if (fd < 0) { + pr_warning("Failed to open debuginfo file.\n"); + return fd; + } + + setup_pager(); + + for (i = 0; i < npevs && ret >= 0; i++) + ret = show_available_vars_at(fd, &pevs[i], max_vls, externs); + + close(fd); + return ret; +} + #else /* !DWARF_SUPPORT */ -static int convert_to_perf_probe_point(struct kprobe_trace_point *tp, +static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, struct perf_probe_point *pp) { + struct symbol *sym; + + sym = __find_kernel_function_by_name(tp->symbol, NULL); + if (!sym) { + pr_err("Failed to find symbol %s in kernel.\n", tp->symbol); + return -ENOENT; + } pp->function = strdup(tp->symbol); if (pp->function == NULL) return -ENOMEM; @@ -320,9 +491,9 @@ static int convert_to_perf_probe_point(struct kprobe_trace_point *tp, return 0; } -static int try_to_find_kprobe_trace_events(struct perf_probe_event *pev, - struct kprobe_trace_event **tevs __unused, - int max_tevs __unused) +static int try_to_find_probe_trace_events(struct perf_probe_event *pev, + struct probe_trace_event **tevs __unused, + int max_tevs __unused, const char *mod __unused) { if (perf_probe_event_need_dwarf(pev)) { pr_warning("Debuginfo-analysis is not supported.\n"); @@ -331,12 +502,19 @@ static int try_to_find_kprobe_trace_events(struct perf_probe_event *pev, return 0; } -int show_line_range(struct line_range *lr __unused) +int show_line_range(struct line_range *lr __unused, const char *module __unused) { pr_warning("Debuginfo-analysis is not supported.\n"); return -ENOSYS; } +int show_available_vars(struct perf_probe_event *pevs __unused, + int npevs __unused, int max_vls __unused, + const char *module __unused, bool externs __unused) +{ + pr_warning("Debuginfo-analysis is not supported.\n"); + return -ENOSYS; +} #endif int parse_line_range_desc(const char *arg, struct line_range *lr) @@ -557,7 +735,7 @@ static int parse_perf_probe_point(char *arg, struct perf_probe_event *pev) /* Parse perf-probe event argument */ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) { - char *tmp; + char *tmp, *goodname; struct perf_probe_arg_field **fieldp; pr_debug("parsing arg: %s into ", str); @@ -580,7 +758,7 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) pr_debug("type:%s ", arg->type); } - tmp = strpbrk(str, "-."); + tmp = strpbrk(str, "-.["); if (!is_c_varname(str) || !tmp) { /* A variable, register, symbol or special value */ arg->var = strdup(str); @@ -590,10 +768,11 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) return 0; } - /* Structure fields */ + /* Structure fields or array element */ arg->var = strndup(str, tmp - str); if (arg->var == NULL) return -ENOMEM; + goodname = arg->var; pr_debug("%s, ", arg->var); fieldp = &arg->field; @@ -601,22 +780,38 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) *fieldp = zalloc(sizeof(struct perf_probe_arg_field)); if (*fieldp == NULL) return -ENOMEM; - if (*tmp == '.') { - str = tmp + 1; - (*fieldp)->ref = false; - } else if (tmp[1] == '>') { - str = tmp + 2; + if (*tmp == '[') { /* Array */ + str = tmp; + (*fieldp)->index = strtol(str + 1, &tmp, 0); (*fieldp)->ref = true; - } else { - semantic_error("Argument parse error: %s\n", str); - return -EINVAL; + if (*tmp != ']' || tmp == str + 1) { + semantic_error("Array index must be a" + " number.\n"); + return -EINVAL; + } + tmp++; + if (*tmp == '\0') + tmp = NULL; + } else { /* Structure */ + if (*tmp == '.') { + str = tmp + 1; + (*fieldp)->ref = false; + } else if (tmp[1] == '>') { + str = tmp + 2; + (*fieldp)->ref = true; + } else { + semantic_error("Argument parse error: %s\n", + str); + return -EINVAL; + } + tmp = strpbrk(str, "-.["); } - - tmp = strpbrk(str, "-."); if (tmp) { (*fieldp)->name = strndup(str, tmp - str); if ((*fieldp)->name == NULL) return -ENOMEM; + if (*str != '[') + goodname = (*fieldp)->name; pr_debug("%s(%d), ", (*fieldp)->name, (*fieldp)->ref); fieldp = &(*fieldp)->next; } @@ -624,11 +819,13 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) (*fieldp)->name = strdup(str); if ((*fieldp)->name == NULL) return -ENOMEM; + if (*str != '[') + goodname = (*fieldp)->name; pr_debug("%s(%d)\n", (*fieldp)->name, (*fieldp)->ref); - /* If no name is specified, set the last field name */ + /* If no name is specified, set the last field name (not array index)*/ if (!arg->name) { - arg->name = strdup((*fieldp)->name); + arg->name = strdup(goodname); if (arg->name == NULL) return -ENOMEM; } @@ -693,16 +890,17 @@ bool perf_probe_event_need_dwarf(struct perf_probe_event *pev) return false; } -/* Parse kprobe_events event into struct probe_point */ -int parse_kprobe_trace_command(const char *cmd, struct kprobe_trace_event *tev) +/* Parse probe_events event into struct probe_point */ +static int parse_probe_trace_command(const char *cmd, + struct probe_trace_event *tev) { - struct kprobe_trace_point *tp = &tev->point; + struct probe_trace_point *tp = &tev->point; char pr; char *p; int ret, i, argc; char **argv; - pr_debug("Parsing kprobe_events: %s\n", cmd); + pr_debug("Parsing probe_events: %s\n", cmd); argv = argv_split(cmd, &argc); if (!argv) { pr_debug("Failed to split arguments.\n"); @@ -734,7 +932,7 @@ int parse_kprobe_trace_command(const char *cmd, struct kprobe_trace_event *tev) tp->offset = 0; tev->nargs = argc - 2; - tev->args = zalloc(sizeof(struct kprobe_trace_arg) * tev->nargs); + tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); if (tev->args == NULL) { ret = -ENOMEM; goto out; @@ -776,8 +974,11 @@ int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf, size_t len) len -= ret; while (field) { - ret = e_snprintf(tmp, len, "%s%s", field->ref ? "->" : ".", - field->name); + if (field->name[0] == '[') + ret = e_snprintf(tmp, len, "%s", field->name); + else + ret = e_snprintf(tmp, len, "%s%s", + field->ref ? "->" : ".", field->name); if (ret <= 0) goto error; tmp += ret; @@ -877,13 +1078,13 @@ char *synthesize_perf_probe_command(struct perf_probe_event *pev) } #endif -static int __synthesize_kprobe_trace_arg_ref(struct kprobe_trace_arg_ref *ref, +static int __synthesize_probe_trace_arg_ref(struct probe_trace_arg_ref *ref, char **buf, size_t *buflen, int depth) { int ret; if (ref->next) { - depth = __synthesize_kprobe_trace_arg_ref(ref->next, buf, + depth = __synthesize_probe_trace_arg_ref(ref->next, buf, buflen, depth + 1); if (depth < 0) goto out; @@ -901,9 +1102,10 @@ out: } -static int synthesize_kprobe_trace_arg(struct kprobe_trace_arg *arg, +static int synthesize_probe_trace_arg(struct probe_trace_arg *arg, char *buf, size_t buflen) { + struct probe_trace_arg_ref *ref = arg->ref; int ret, depth = 0; char *tmp = buf; @@ -917,16 +1119,24 @@ static int synthesize_kprobe_trace_arg(struct kprobe_trace_arg *arg, buf += ret; buflen -= ret; + /* Special case: @XXX */ + if (arg->value[0] == '@' && arg->ref) + ref = ref->next; + /* Dereferencing arguments */ - if (arg->ref) { - depth = __synthesize_kprobe_trace_arg_ref(arg->ref, &buf, + if (ref) { + depth = __synthesize_probe_trace_arg_ref(ref, &buf, &buflen, 1); if (depth < 0) return depth; } /* Print argument value */ - ret = e_snprintf(buf, buflen, "%s", arg->value); + if (arg->value[0] == '@' && arg->ref) + ret = e_snprintf(buf, buflen, "%s%+ld", arg->value, + arg->ref->offset); + else + ret = e_snprintf(buf, buflen, "%s", arg->value); if (ret < 0) return ret; buf += ret; @@ -951,9 +1161,9 @@ static int synthesize_kprobe_trace_arg(struct kprobe_trace_arg *arg, return buf - tmp; } -char *synthesize_kprobe_trace_command(struct kprobe_trace_event *tev) +char *synthesize_probe_trace_command(struct probe_trace_event *tev) { - struct kprobe_trace_point *tp = &tev->point; + struct probe_trace_point *tp = &tev->point; char *buf; int i, len, ret; @@ -969,7 +1179,7 @@ char *synthesize_kprobe_trace_command(struct kprobe_trace_event *tev) goto error; for (i = 0; i < tev->nargs; i++) { - ret = synthesize_kprobe_trace_arg(&tev->args[i], buf + len, + ret = synthesize_probe_trace_arg(&tev->args[i], buf + len, MAX_CMDLEN - len); if (ret <= 0) goto error; @@ -982,8 +1192,8 @@ error: return NULL; } -int convert_to_perf_probe_event(struct kprobe_trace_event *tev, - struct perf_probe_event *pev) +static int convert_to_perf_probe_event(struct probe_trace_event *tev, + struct perf_probe_event *pev) { char buf[64] = ""; int i, ret; @@ -995,7 +1205,7 @@ int convert_to_perf_probe_event(struct kprobe_trace_event *tev, return -ENOMEM; /* Convert trace_point to probe_point */ - ret = convert_to_perf_probe_point(&tev->point, &pev->point); + ret = kprobe_convert_to_perf_probe(&tev->point, &pev->point); if (ret < 0) return ret; @@ -1008,7 +1218,7 @@ int convert_to_perf_probe_event(struct kprobe_trace_event *tev, if (tev->args[i].name) pev->args[i].name = strdup(tev->args[i].name); else { - ret = synthesize_kprobe_trace_arg(&tev->args[i], + ret = synthesize_probe_trace_arg(&tev->args[i], buf, 64); pev->args[i].name = strdup(buf); } @@ -1059,9 +1269,9 @@ void clear_perf_probe_event(struct perf_probe_event *pev) memset(pev, 0, sizeof(*pev)); } -void clear_kprobe_trace_event(struct kprobe_trace_event *tev) +static void clear_probe_trace_event(struct probe_trace_event *tev) { - struct kprobe_trace_arg_ref *ref, *next; + struct probe_trace_arg_ref *ref, *next; int i; if (tev->event) @@ -1122,7 +1332,7 @@ static int open_kprobe_events(bool readwrite) } /* Get raw string list of current kprobe_events */ -static struct strlist *get_kprobe_trace_command_rawlist(int fd) +static struct strlist *get_probe_trace_command_rawlist(int fd) { int ret, idx; FILE *fp; @@ -1190,7 +1400,7 @@ static int show_perf_probe_event(struct perf_probe_event *pev) int show_perf_probe_events(void) { int fd, ret; - struct kprobe_trace_event tev; + struct probe_trace_event tev; struct perf_probe_event pev; struct strlist *rawlist; struct str_node *ent; @@ -1207,20 +1417,20 @@ int show_perf_probe_events(void) if (fd < 0) return fd; - rawlist = get_kprobe_trace_command_rawlist(fd); + rawlist = get_probe_trace_command_rawlist(fd); close(fd); if (!rawlist) return -ENOENT; strlist__for_each(ent, rawlist) { - ret = parse_kprobe_trace_command(ent->s, &tev); + ret = parse_probe_trace_command(ent->s, &tev); if (ret >= 0) { ret = convert_to_perf_probe_event(&tev, &pev); if (ret >= 0) ret = show_perf_probe_event(&pev); } clear_perf_probe_event(&pev); - clear_kprobe_trace_event(&tev); + clear_probe_trace_event(&tev); if (ret < 0) break; } @@ -1230,20 +1440,19 @@ int show_perf_probe_events(void) } /* Get current perf-probe event names */ -static struct strlist *get_kprobe_trace_event_names(int fd, bool include_group) +static struct strlist *get_probe_trace_event_names(int fd, bool include_group) { char buf[128]; struct strlist *sl, *rawlist; struct str_node *ent; - struct kprobe_trace_event tev; + struct probe_trace_event tev; int ret = 0; memset(&tev, 0, sizeof(tev)); - - rawlist = get_kprobe_trace_command_rawlist(fd); + rawlist = get_probe_trace_command_rawlist(fd); sl = strlist__new(true, NULL); strlist__for_each(ent, rawlist) { - ret = parse_kprobe_trace_command(ent->s, &tev); + ret = parse_probe_trace_command(ent->s, &tev); if (ret < 0) break; if (include_group) { @@ -1253,7 +1462,7 @@ static struct strlist *get_kprobe_trace_event_names(int fd, bool include_group) ret = strlist__add(sl, buf); } else ret = strlist__add(sl, tev.event); - clear_kprobe_trace_event(&tev); + clear_probe_trace_event(&tev); if (ret < 0) break; } @@ -1266,13 +1475,13 @@ static struct strlist *get_kprobe_trace_event_names(int fd, bool include_group) return sl; } -static int write_kprobe_trace_event(int fd, struct kprobe_trace_event *tev) +static int write_probe_trace_event(int fd, struct probe_trace_event *tev) { int ret = 0; - char *buf = synthesize_kprobe_trace_command(tev); + char *buf = synthesize_probe_trace_command(tev); if (!buf) { - pr_debug("Failed to synthesize kprobe trace event.\n"); + pr_debug("Failed to synthesize probe trace event.\n"); return -EINVAL; } @@ -1325,12 +1534,12 @@ static int get_new_event_name(char *buf, size_t len, const char *base, return ret; } -static int __add_kprobe_trace_events(struct perf_probe_event *pev, - struct kprobe_trace_event *tevs, +static int __add_probe_trace_events(struct perf_probe_event *pev, + struct probe_trace_event *tevs, int ntevs, bool allow_suffix) { int i, fd, ret; - struct kprobe_trace_event *tev = NULL; + struct probe_trace_event *tev = NULL; char buf[64]; const char *event, *group; struct strlist *namelist; @@ -1339,7 +1548,7 @@ static int __add_kprobe_trace_events(struct perf_probe_event *pev, if (fd < 0) return fd; /* Get current event names */ - namelist = get_kprobe_trace_event_names(fd, false); + namelist = get_probe_trace_event_names(fd, false); if (!namelist) { pr_debug("Failed to get current event list.\n"); return -EIO; @@ -1374,7 +1583,7 @@ static int __add_kprobe_trace_events(struct perf_probe_event *pev, ret = -ENOMEM; break; } - ret = write_kprobe_trace_event(fd, tev); + ret = write_probe_trace_event(fd, tev); if (ret < 0) break; /* Add added event name to namelist */ @@ -1411,21 +1620,21 @@ static int __add_kprobe_trace_events(struct perf_probe_event *pev, return ret; } -static int convert_to_kprobe_trace_events(struct perf_probe_event *pev, - struct kprobe_trace_event **tevs, - int max_tevs) +static int convert_to_probe_trace_events(struct perf_probe_event *pev, + struct probe_trace_event **tevs, + int max_tevs, const char *module) { struct symbol *sym; int ret = 0, i; - struct kprobe_trace_event *tev; + struct probe_trace_event *tev; /* Convert perf_probe_event with debuginfo */ - ret = try_to_find_kprobe_trace_events(pev, tevs, max_tevs); + ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, module); if (ret != 0) return ret; /* Allocate trace event buffer */ - tev = *tevs = zalloc(sizeof(struct kprobe_trace_event)); + tev = *tevs = zalloc(sizeof(struct probe_trace_event)); if (tev == NULL) return -ENOMEM; @@ -1436,9 +1645,10 @@ static int convert_to_kprobe_trace_events(struct perf_probe_event *pev, goto error; } tev->point.offset = pev->point.offset; + tev->point.retprobe = pev->point.retprobe; tev->nargs = pev->nargs; if (tev->nargs) { - tev->args = zalloc(sizeof(struct kprobe_trace_arg) + tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); if (tev->args == NULL) { ret = -ENOMEM; @@ -1468,8 +1678,7 @@ static int convert_to_kprobe_trace_events(struct perf_probe_event *pev, } /* Currently just checking function name from symbol map */ - sym = map__find_symbol_by_name(machine.vmlinux_maps[MAP__FUNCTION], - tev->point.symbol, NULL); + sym = __find_kernel_function_by_name(tev->point.symbol, NULL); if (!sym) { pr_warning("Kernel symbol \'%s\' not found.\n", tev->point.symbol); @@ -1479,7 +1688,7 @@ static int convert_to_kprobe_trace_events(struct perf_probe_event *pev, return 1; error: - clear_kprobe_trace_event(tev); + clear_probe_trace_event(tev); free(tev); *tevs = NULL; return ret; @@ -1487,12 +1696,12 @@ error: struct __event_package { struct perf_probe_event *pev; - struct kprobe_trace_event *tevs; + struct probe_trace_event *tevs; int ntevs; }; int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, - bool force_add, int max_tevs) + int max_tevs, const char *module, bool force_add) { int i, j, ret; struct __event_package *pkgs; @@ -1503,15 +1712,19 @@ int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, /* Init vmlinux path */ ret = init_vmlinux(); - if (ret < 0) + if (ret < 0) { + free(pkgs); return ret; + } /* Loop 1: convert all events */ for (i = 0; i < npevs; i++) { pkgs[i].pev = &pevs[i]; /* Convert with or without debuginfo */ - ret = convert_to_kprobe_trace_events(pkgs[i].pev, - &pkgs[i].tevs, max_tevs); + ret = convert_to_probe_trace_events(pkgs[i].pev, + &pkgs[i].tevs, + max_tevs, + module); if (ret < 0) goto end; pkgs[i].ntevs = ret; @@ -1519,24 +1732,27 @@ int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, /* Loop 2: add all events */ for (i = 0; i < npevs && ret >= 0; i++) - ret = __add_kprobe_trace_events(pkgs[i].pev, pkgs[i].tevs, + ret = __add_probe_trace_events(pkgs[i].pev, pkgs[i].tevs, pkgs[i].ntevs, force_add); end: - /* Loop 3: cleanup trace events */ - for (i = 0; i < npevs; i++) + /* Loop 3: cleanup and free trace events */ + for (i = 0; i < npevs; i++) { for (j = 0; j < pkgs[i].ntevs; j++) - clear_kprobe_trace_event(&pkgs[i].tevs[j]); + clear_probe_trace_event(&pkgs[i].tevs[j]); + free(pkgs[i].tevs); + } + free(pkgs); return ret; } -static int __del_trace_kprobe_event(int fd, struct str_node *ent) +static int __del_trace_probe_event(int fd, struct str_node *ent) { char *p; char buf[128]; int ret; - /* Convert from perf-probe event to trace-kprobe event */ + /* Convert from perf-probe event to trace-probe event */ ret = e_snprintf(buf, 128, "-:%s", ent->s); if (ret < 0) goto error; @@ -1562,7 +1778,7 @@ error: return ret; } -static int del_trace_kprobe_event(int fd, const char *group, +static int del_trace_probe_event(int fd, const char *group, const char *event, struct strlist *namelist) { char buf[128]; @@ -1579,7 +1795,7 @@ static int del_trace_kprobe_event(int fd, const char *group, strlist__for_each_safe(ent, n, namelist) if (strglobmatch(ent->s, buf)) { found++; - ret = __del_trace_kprobe_event(fd, ent); + ret = __del_trace_probe_event(fd, ent); if (ret < 0) break; strlist__remove(namelist, ent); @@ -1588,7 +1804,7 @@ static int del_trace_kprobe_event(int fd, const char *group, ent = strlist__find(namelist, buf); if (ent) { found++; - ret = __del_trace_kprobe_event(fd, ent); + ret = __del_trace_probe_event(fd, ent); if (ret >= 0) strlist__remove(namelist, ent); } @@ -1612,7 +1828,7 @@ int del_perf_probe_events(struct strlist *dellist) return fd; /* Get current event names */ - namelist = get_kprobe_trace_event_names(fd, true); + namelist = get_probe_trace_event_names(fd, true); if (namelist == NULL) return -EINVAL; @@ -1633,7 +1849,7 @@ int del_perf_probe_events(struct strlist *dellist) event = str; } pr_debug("Group: %s, Event: %s\n", group, event); - ret = del_trace_kprobe_event(fd, group, event, namelist); + ret = del_trace_probe_event(fd, group, event, namelist); free(str); if (ret < 0) break; diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h index e9db1a214ca4..5accbedfea37 100644 --- a/tools/perf/util/probe-event.h +++ b/tools/perf/util/probe-event.h @@ -7,33 +7,33 @@ extern bool probe_event_dry_run; /* kprobe-tracer tracing point */ -struct kprobe_trace_point { +struct probe_trace_point { char *symbol; /* Base symbol */ unsigned long offset; /* Offset from symbol */ bool retprobe; /* Return probe flag */ }; -/* kprobe-tracer tracing argument referencing offset */ -struct kprobe_trace_arg_ref { - struct kprobe_trace_arg_ref *next; /* Next reference */ +/* probe-tracer tracing argument referencing offset */ +struct probe_trace_arg_ref { + struct probe_trace_arg_ref *next; /* Next reference */ long offset; /* Offset value */ }; /* kprobe-tracer tracing argument */ -struct kprobe_trace_arg { +struct probe_trace_arg { char *name; /* Argument name */ char *value; /* Base value */ char *type; /* Type name */ - struct kprobe_trace_arg_ref *ref; /* Referencing offset */ + struct probe_trace_arg_ref *ref; /* Referencing offset */ }; /* kprobe-tracer tracing event (point + arg) */ -struct kprobe_trace_event { +struct probe_trace_event { char *event; /* Event name */ char *group; /* Group name */ - struct kprobe_trace_point point; /* Trace point */ + struct probe_trace_point point; /* Trace point */ int nargs; /* Number of args */ - struct kprobe_trace_arg *args; /* Arguments */ + struct probe_trace_arg *args; /* Arguments */ }; /* Perf probe probing point */ @@ -50,6 +50,7 @@ struct perf_probe_point { struct perf_probe_arg_field { struct perf_probe_arg_field *next; /* Next field */ char *name; /* Name of the field */ + long index; /* Array index number */ bool ref; /* Referencing flag */ }; @@ -85,41 +86,47 @@ struct line_range { int end; /* End line number */ int offset; /* Start line offset */ char *path; /* Real path name */ + char *comp_dir; /* Compile directory */ struct list_head line_list; /* Visible lines */ }; +/* List of variables */ +struct variable_list { + struct probe_trace_point point; /* Actual probepoint */ + struct strlist *vars; /* Available variables */ +}; + /* Command string to events */ extern int parse_perf_probe_command(const char *cmd, struct perf_probe_event *pev); -extern int parse_kprobe_trace_command(const char *cmd, - struct kprobe_trace_event *tev); /* Events to command string */ extern char *synthesize_perf_probe_command(struct perf_probe_event *pev); -extern char *synthesize_kprobe_trace_command(struct kprobe_trace_event *tev); +extern char *synthesize_probe_trace_command(struct probe_trace_event *tev); extern int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf, size_t len); /* Check the perf_probe_event needs debuginfo */ extern bool perf_probe_event_need_dwarf(struct perf_probe_event *pev); -/* Convert from kprobe_trace_event to perf_probe_event */ -extern int convert_to_perf_probe_event(struct kprobe_trace_event *tev, - struct perf_probe_event *pev); - /* Release event contents */ extern void clear_perf_probe_event(struct perf_probe_event *pev); -extern void clear_kprobe_trace_event(struct kprobe_trace_event *tev); /* Command string to line-range */ extern int parse_line_range_desc(const char *cmd, struct line_range *lr); +/* Internal use: Return kernel/module path */ +extern const char *kernel_get_module_path(const char *module); extern int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, - bool force_add, int max_probe_points); + int max_probe_points, const char *module, + bool force_add); extern int del_perf_probe_events(struct strlist *dellist); extern int show_perf_probe_events(void); -extern int show_line_range(struct line_range *lr); +extern int show_line_range(struct line_range *lr, const char *module); +extern int show_available_vars(struct perf_probe_event *pevs, int npevs, + int max_probe_points, const char *module, + bool externs); /* Maximum index number of event-name postfix */ diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index d964cb199c67..3991d73d1cff 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -33,10 +33,10 @@ #include <ctype.h> #include <dwarf-regs.h> -#include "string.h" #include "event.h" #include "debug.h" #include "util.h" +#include "symbol.h" #include "probe-finder.h" /* Kprobe tracer basic type is up to u64 */ @@ -116,6 +116,101 @@ static void line_list__free(struct list_head *head) } } +/* Dwarf FL wrappers */ + +static int __linux_kernel_find_elf(Dwfl_Module *mod, + void **userdata, + const char *module_name, + Dwarf_Addr base, + char **file_name, Elf **elfp) +{ + int fd; + const char *path = kernel_get_module_path(module_name); + + if (path) { + fd = open(path, O_RDONLY); + if (fd >= 0) { + *file_name = strdup(path); + return fd; + } + } + /* If failed, try to call standard method */ + return dwfl_linux_kernel_find_elf(mod, userdata, module_name, base, + file_name, elfp); +} + +static char *debuginfo_path; /* Currently dummy */ + +static const Dwfl_Callbacks offline_callbacks = { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + + .section_address = dwfl_offline_section_address, + + /* We use this table for core files too. */ + .find_elf = dwfl_build_id_find_elf, +}; + +static const Dwfl_Callbacks kernel_callbacks = { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + + .find_elf = __linux_kernel_find_elf, + .section_address = dwfl_linux_kernel_module_section_address, +}; + +/* Get a Dwarf from offline image */ +static Dwarf *dwfl_init_offline_dwarf(int fd, Dwfl **dwflp, Dwarf_Addr *bias) +{ + Dwfl_Module *mod; + Dwarf *dbg = NULL; + + if (!dwflp) + return NULL; + + *dwflp = dwfl_begin(&offline_callbacks); + if (!*dwflp) + return NULL; + + mod = dwfl_report_offline(*dwflp, "", "", fd); + if (!mod) + goto error; + + dbg = dwfl_module_getdwarf(mod, bias); + if (!dbg) { +error: + dwfl_end(*dwflp); + *dwflp = NULL; + } + return dbg; +} + +/* Get a Dwarf from live kernel image */ +static Dwarf *dwfl_init_live_kernel_dwarf(Dwarf_Addr addr, Dwfl **dwflp, + Dwarf_Addr *bias) +{ + Dwarf *dbg; + + if (!dwflp) + return NULL; + + *dwflp = dwfl_begin(&kernel_callbacks); + if (!*dwflp) + return NULL; + + /* Load the kernel dwarves: Don't care the result here */ + dwfl_linux_kernel_report_kernel(*dwflp); + dwfl_linux_kernel_report_modules(*dwflp); + + dbg = dwfl_addrdwarf(*dwflp, addr, bias); + /* Here, check whether we could get a real dwarf */ + if (!dbg) { + dwfl_end(*dwflp); + *dwflp = NULL; + } + return dbg; +} + /* Dwarf wrappers */ /* Find the realpath of the target file. */ @@ -143,34 +238,61 @@ static const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname) 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); +} + /* 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) : -1; + return name ? (strcmp(tname, name) == 0) : false; } -/* Get type die, but skip qualifiers and typedef */ -static Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) +/* 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 + 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 { - if (dwarf_attr(vr_die, DW_AT_type, &attr) == NULL || - dwarf_formref_die(&attr, die_mem) == NULL) - return NULL; - - tag = dwarf_tag(die_mem); - vr_die = die_mem; + 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 || - tag == DW_TAG_typedef); + 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 die_mem; + return vr_die; } static bool die_is_signed_type(Dwarf_Die *tp_die) @@ -311,25 +433,35 @@ static Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr, return die_find_child(sp_die, __die_find_inline_cb, &addr, die_mem); } +struct __find_variable_param { + const char *name; + Dwarf_Addr addr; +}; + static int __die_find_variable_cb(Dwarf_Die *die_mem, void *data) { - const char *name = 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, name) == 0)) + die_compare_name(die_mem, fvp->name)) return DIE_FIND_CB_FOUND; - return DIE_FIND_CB_CONTINUE; + if (dwarf_haspc(die_mem, fvp->addr)) + return DIE_FIND_CB_CONTINUE; + else + return DIE_FIND_CB_SIBLING; } -/* Find a variable called 'name' */ -static Dwarf_Die *die_find_variable(Dwarf_Die *sp_die, const char *name, - Dwarf_Die *die_mem) +/* 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) { - return die_find_child(sp_die, __die_find_variable_cb, (void *)name, + struct __find_variable_param fvp = { .name = name, .addr = addr}; + + return die_find_child(sp_die, __die_find_variable_cb, (void *)&fvp, die_mem); } @@ -338,7 +470,7 @@ 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) == 0)) + die_compare_name(die_mem, name)) return DIE_FIND_CB_FOUND; return DIE_FIND_CB_SIBLING; @@ -352,29 +484,124 @@ static Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *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; +} + /* * Probe finder related functions */ -/* Show a location */ -static int convert_location(Dwarf_Op *op, struct probe_finder *pf) +static struct probe_trace_arg_ref *alloc_trace_arg_ref(long offs) { + struct probe_trace_arg_ref *ref; + ref = zalloc(sizeof(struct probe_trace_arg_ref)); + if (ref != NULL) + ref->offset = offs; + return ref; +} + +/* + * Convert a location into trace_arg. + * If tvar == NULL, this just checks variable can be converted. + */ +static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr, + Dwarf_Op *fb_ops, + struct probe_trace_arg *tvar) +{ + Dwarf_Attribute attr; + Dwarf_Op *op; + size_t nops; unsigned int regn; Dwarf_Word offs = 0; bool ref = false; const char *regs; - struct kprobe_trace_arg *tvar = pf->tvar; + int ret; + + if (dwarf_attr(vr_die, DW_AT_external, &attr) != NULL) + goto static_var; + + /* TODO: handle more than 1 exprs */ + if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL || + dwarf_getlocation_addr(&attr, addr, &op, &nops, 1) <= 0 || + nops == 0) { + /* TODO: Support const_value */ + return -ENOENT; + } + + if (op->atom == DW_OP_addr) { +static_var: + if (!tvar) + return 0; + /* Static variables on memory (not stack), make @varname */ + ret = strlen(dwarf_diename(vr_die)); + tvar->value = zalloc(ret + 2); + if (tvar->value == NULL) + return -ENOMEM; + snprintf(tvar->value, ret + 2, "@%s", dwarf_diename(vr_die)); + tvar->ref = alloc_trace_arg_ref((long)offs); + if (tvar->ref == NULL) + return -ENOMEM; + return 0; + } /* If this is based on frame buffer, set the offset */ if (op->atom == DW_OP_fbreg) { - if (pf->fb_ops == NULL) { - pr_warning("The attribute of frame base is not " - "supported.\n"); + if (fb_ops == NULL) return -ENOTSUP; - } ref = true; offs = op->number; - op = &pf->fb_ops[0]; + op = &fb_ops[0]; } if (op->atom >= DW_OP_breg0 && op->atom <= DW_OP_breg31) { @@ -390,13 +617,18 @@ static int convert_location(Dwarf_Op *op, struct probe_finder *pf) } else if (op->atom == DW_OP_regx) { regn = op->number; } else { - pr_warning("DW_OP %x is not supported.\n", op->atom); + pr_debug("DW_OP %x is not supported.\n", op->atom); return -ENOTSUP; } + if (!tvar) + return 0; + regs = get_arch_regstr(regn); if (!regs) { - pr_warning("Mapping for DWARF register number %u missing on this architecture.", regn); + /* This should be a bug in DWARF or this tool */ + pr_warning("Mapping for DWARF register number %u " + "missing on this architecture.", regn); return -ERANGE; } @@ -405,27 +637,72 @@ static int convert_location(Dwarf_Op *op, struct probe_finder *pf) return -ENOMEM; if (ref) { - tvar->ref = zalloc(sizeof(struct kprobe_trace_arg_ref)); + tvar->ref = alloc_trace_arg_ref((long)offs); if (tvar->ref == NULL) return -ENOMEM; - tvar->ref->offset = (long)offs; } return 0; } static int convert_variable_type(Dwarf_Die *vr_die, - struct kprobe_trace_arg *targ) + struct probe_trace_arg *tvar, + const char *cast) { + struct probe_trace_arg_ref **ref_ptr = &tvar->ref; Dwarf_Die type; char buf[16]; int ret; + /* TODO: check all types */ + if (cast && strcmp(cast, "string") != 0) { + /* Non string type is OK */ + tvar->type = strdup(cast); + return (tvar->type == NULL) ? -ENOMEM : 0; + } + if (die_get_real_type(vr_die, &type) == NULL) { pr_warning("Failed to get a type information of %s.\n", dwarf_diename(vr_die)); return -ENOENT; } + pr_debug("%s type is %s.\n", + dwarf_diename(vr_die), dwarf_diename(&type)); + + if (cast && strcmp(cast, "string") == 0) { /* String type */ + ret = dwarf_tag(&type); + if (ret != DW_TAG_pointer_type && + ret != DW_TAG_array_type) { + pr_warning("Failed to cast into string: " + "%s(%s) is not a pointer nor array.", + dwarf_diename(vr_die), dwarf_diename(&type)); + return -EINVAL; + } + if (ret == DW_TAG_pointer_type) { + if (die_get_real_type(&type, &type) == NULL) { + pr_warning("Failed to get a type information."); + return -ENOENT; + } + while (*ref_ptr) + ref_ptr = &(*ref_ptr)->next; + /* Add new reference with offset +0 */ + *ref_ptr = zalloc(sizeof(struct probe_trace_arg_ref)); + if (*ref_ptr == NULL) { + pr_warning("Out of memory error\n"); + return -ENOMEM; + } + } + if (!die_compare_name(&type, "char") && + !die_compare_name(&type, "unsigned char")) { + pr_warning("Failed to cast into string: " + "%s is not (unsigned) char *.", + dwarf_diename(vr_die)); + return -EINVAL; + } + tvar->type = strdup(cast); + return (tvar->type == NULL) ? -ENOMEM : 0; + } + ret = die_get_byte_size(&type) * 8; if (ret) { /* Check the bitwidth */ @@ -445,8 +722,8 @@ static int convert_variable_type(Dwarf_Die *vr_die, strerror(-ret)); return ret; } - targ->type = strdup(buf); - if (targ->type == NULL) + tvar->type = strdup(buf); + if (tvar->type == NULL) return -ENOMEM; } return 0; @@ -454,22 +731,50 @@ static int convert_variable_type(Dwarf_Die *vr_die, static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, struct perf_probe_arg_field *field, - struct kprobe_trace_arg_ref **ref_ptr, + struct probe_trace_arg_ref **ref_ptr, Dwarf_Die *die_mem) { - struct kprobe_trace_arg_ref *ref = *ref_ptr; + struct probe_trace_arg_ref *ref = *ref_ptr; Dwarf_Die type; Dwarf_Word offs; - int ret; + int ret, tag; pr_debug("converting %s in %s\n", field->name, varname); if (die_get_real_type(vr_die, &type) == NULL) { pr_warning("Failed to get the type of %s.\n", varname); return -ENOENT; } - - /* Check the pointer and dereference */ - if (dwarf_tag(&type) == DW_TAG_pointer_type) { + pr_debug2("Var real type: (%x)\n", (unsigned)dwarf_dieoffset(&type)); + tag = dwarf_tag(&type); + + if (field->name[0] == '[' && + (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)) { + if (field->next) + /* Save original type for next field */ + memcpy(die_mem, &type, sizeof(*die_mem)); + /* Get the type of this array */ + if (die_get_real_type(&type, &type) == NULL) { + pr_warning("Failed to get the type of %s.\n", varname); + return -ENOENT; + } + pr_debug2("Array real type: (%x)\n", + (unsigned)dwarf_dieoffset(&type)); + if (tag == DW_TAG_pointer_type) { + ref = zalloc(sizeof(struct probe_trace_arg_ref)); + if (ref == NULL) + return -ENOMEM; + if (*ref_ptr) + (*ref_ptr)->next = ref; + else + *ref_ptr = ref; + } + ref->offset += die_get_byte_size(&type) * field->index; + if (!field->next) + /* Save vr_die for converting types */ + memcpy(die_mem, vr_die, sizeof(*die_mem)); + goto next; + } else if (tag == DW_TAG_pointer_type) { + /* Check the pointer and dereference */ if (!field->ref) { pr_err("Semantic error: %s must be referred by '->'\n", field->name); @@ -486,7 +791,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, return -EINVAL; } - ref = zalloc(sizeof(struct kprobe_trace_arg_ref)); + ref = zalloc(sizeof(struct probe_trace_arg_ref)); if (ref == NULL) return -ENOMEM; if (*ref_ptr) @@ -495,10 +800,15 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, *ref_ptr = ref; } else { /* Verify it is a data structure */ - if (dwarf_tag(&type) != DW_TAG_structure_type) { + if (tag != DW_TAG_structure_type) { pr_warning("%s is not a data structure.\n", varname); return -EINVAL; } + if (field->name[0] == '[') { + pr_err("Semantic error: %s is not a pointor nor array.", + varname); + return -EINVAL; + } if (field->ref) { pr_err("Semantic error: %s must be referred by '.'\n", field->name); @@ -525,6 +835,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, } ref->offset += (long)offs; +next: /* Converting next field */ if (field->next) return convert_variable_fields(die_mem, field->name, @@ -536,51 +847,57 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, /* Show a variables in kprobe event format */ static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf) { - Dwarf_Attribute attr; Dwarf_Die die_mem; - Dwarf_Op *expr; - size_t nexpr; int ret; - if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL) - goto error; - /* TODO: handle more than 1 exprs */ - ret = dwarf_getlocation_addr(&attr, pf->addr, &expr, &nexpr, 1); - if (ret <= 0 || nexpr == 0) - goto error; - - ret = convert_location(expr, pf); - if (ret == 0 && pf->pvar->field) { + pr_debug("Converting variable %s into trace event.\n", + dwarf_diename(vr_die)); + + ret = convert_variable_location(vr_die, pf->addr, pf->fb_ops, + pf->tvar); + if (ret == -ENOENT) + pr_err("Failed to find the location of %s at this address.\n" + " Perhaps, it has been optimized out.\n", pf->pvar->var); + else if (ret == -ENOTSUP) + pr_err("Sorry, we don't support this variable location yet.\n"); + else if (pf->pvar->field) { ret = convert_variable_fields(vr_die, pf->pvar->var, pf->pvar->field, &pf->tvar->ref, &die_mem); vr_die = &die_mem; } - if (ret == 0) { - if (pf->pvar->type) { - pf->tvar->type = strdup(pf->pvar->type); - if (pf->tvar->type == NULL) - ret = -ENOMEM; - } else - ret = convert_variable_type(vr_die, pf->tvar); - } + if (ret == 0) + ret = convert_variable_type(vr_die, pf->tvar, pf->pvar->type); /* *expr will be cached in libdw. Don't free it. */ return ret; -error: - /* TODO: Support const_value */ - pr_err("Failed to find the location of %s at this address.\n" - " Perhaps, it has been optimized out.\n", pf->pvar->var); - return -ENOENT; } /* Find a variable in a subprogram die */ static int find_variable(Dwarf_Die *sp_die, struct probe_finder *pf) { - Dwarf_Die vr_die; + Dwarf_Die vr_die, *scopes; char buf[32], *ptr; - int ret; + int ret, nscopes; + + if (!is_c_varname(pf->pvar->var)) { + /* Copy raw parameters */ + pf->tvar->value = strdup(pf->pvar->var); + if (pf->tvar->value == NULL) + return -ENOMEM; + if (pf->pvar->type) { + pf->tvar->type = strdup(pf->pvar->type); + if (pf->tvar->type == NULL) + return -ENOMEM; + } + if (pf->pvar->name) { + pf->tvar->name = strdup(pf->pvar->name); + if (pf->tvar->name == NULL) + return -ENOMEM; + } else + pf->tvar->name = NULL; + return 0; + } - /* TODO: Support arrays */ if (pf->pvar->name) pf->tvar->name = strdup(pf->pvar->name); else { @@ -595,54 +912,42 @@ static int find_variable(Dwarf_Die *sp_die, struct probe_finder *pf) if (pf->tvar->name == NULL) return -ENOMEM; - if (!is_c_varname(pf->pvar->var)) { - /* Copy raw parameters */ - pf->tvar->value = strdup(pf->pvar->var); - if (pf->tvar->value == NULL) - return -ENOMEM; - else - return 0; - } - pr_debug("Searching '%s' variable in context.\n", pf->pvar->var); /* Search child die for local variables and parameters. */ - if (!die_find_variable(sp_die, pf->pvar->var, &vr_die)) { + 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; + } +found: + if (ret < 0) pr_warning("Failed to find '%s' in this function.\n", pf->pvar->var); - return -ENOENT; - } - return convert_variable(&vr_die, pf); + return ret; } -/* Show a probe point to output buffer */ -static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) +/* Convert subprogram DIE to trace point */ +static int convert_to_trace_point(Dwarf_Die *sp_die, Dwarf_Addr paddr, + bool retprobe, struct probe_trace_point *tp) { - struct kprobe_trace_event *tev; Dwarf_Addr eaddr; - Dwarf_Die die_mem; const char *name; - int ret, i; - Dwarf_Attribute fb_attr; - size_t nops; - - if (pf->ntevs == pf->max_tevs) { - pr_warning("Too many( > %d) probe point found.\n", - pf->max_tevs); - return -ERANGE; - } - tev = &pf->tevs[pf->ntevs++]; - - /* 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) { - pr_warning("Failed to find probe point in any " - "functions.\n"); - return -ENOENT; - } - } /* Copy the name of probe point */ name = dwarf_diename(sp_die); @@ -652,16 +957,45 @@ static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) dwarf_diename(sp_die)); return -ENOENT; } - tev->point.symbol = strdup(name); - if (tev->point.symbol == NULL) + tp->symbol = strdup(name); + if (tp->symbol == NULL) return -ENOMEM; - tev->point.offset = (unsigned long)(pf->addr - eaddr); + tp->offset = (unsigned long)(paddr - eaddr); } else /* This function has no name. */ - tev->point.offset = (unsigned long)pf->addr; + tp->offset = (unsigned long)paddr; - pr_debug("Probe point found: %s+%lu\n", tev->point.symbol, - tev->point.offset); + /* Return probe must be on the head of a subprogram */ + if (retprobe) { + if (eaddr != paddr) { + pr_warning("Return probe must be on the head of" + " a real function\n"); + return -EINVAL; + } + tp->retprobe = true; + } + + return 0; +} + +/* Call probe_finder callback with real subprogram DIE */ +static int call_probe_finder(Dwarf_Die *sp_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) { + pr_warning("Failed to find probe point in any " + "functions.\n"); + return -ENOENT; + } + } /* Get the frame base attribute/ops */ dwarf_attr(sp_die, DW_AT_frame_base, &fb_attr); @@ -681,22 +1015,13 @@ static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) #endif } - /* Find each argument */ - tev->nargs = pf->pev->nargs; - tev->args = zalloc(sizeof(struct kprobe_trace_arg) * tev->nargs); - if (tev->args == NULL) - return -ENOMEM; - 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); - if (ret != 0) - return ret; - } + /* Call finder's callback handler */ + ret = pf->callback(sp_die, pf); /* *pf->fb_ops will be cached in libdw. Don't free it. */ pf->fb_ops = NULL; - return 0; + + return ret; } /* Find probe point from its line number */ @@ -732,7 +1057,7 @@ static int find_probe_point_by_line(struct probe_finder *pf) (int)i, lineno, (uintmax_t)addr); pf->addr = addr; - ret = convert_probe_point(NULL, pf); + ret = call_probe_finder(NULL, pf); /* Continuing, because target line might be inlined. */ } return ret; @@ -845,7 +1170,7 @@ static int find_probe_point_lazy(Dwarf_Die *sp_die, struct probe_finder *pf) (int)i, lineno, (unsigned long long)addr); pf->addr = addr; - ret = convert_probe_point(sp_die, pf); + ret = call_probe_finder(sp_die, pf); /* Continuing, because target line might be inlined. */ } /* TODO: deallocate lines, but how? */ @@ -880,7 +1205,7 @@ static int probe_point_inline_cb(Dwarf_Die *in_die, void *data) pr_debug("found inline addr: 0x%jx\n", (uintmax_t)pf->addr); - param->retval = convert_probe_point(in_die, pf); + param->retval = call_probe_finder(in_die, pf); if (param->retval < 0) return DWARF_CB_ABORT; } @@ -897,7 +1222,7 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) /* Check tag and diename */ if (dwarf_tag(sp_die) != DW_TAG_subprogram || - die_compare_name(sp_die, pp->function) != 0) + !die_compare_name(sp_die, pp->function)) return DWARF_CB_OK; pf->fname = dwarf_decl_file(sp_die); @@ -918,7 +1243,7 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) } pf->addr += pp->offset; /* TODO: Check the address in this function */ - param->retval = convert_probe_point(sp_die, pf); + param->retval = call_probe_finder(sp_die, pf); } } else { struct dwarf_callback_param _param = {.data = (void *)pf, @@ -940,90 +1265,276 @@ static int find_probe_point_by_func(struct probe_finder *pf) return _param.retval; } -/* Find kprobe_trace_events specified by perf_probe_event from debuginfo */ -int find_kprobe_trace_events(int fd, struct perf_probe_event *pev, - struct kprobe_trace_event **tevs, int max_tevs) +/* Find probe points from debuginfo */ +static int find_probes(int fd, struct probe_finder *pf) { - struct probe_finder pf = {.pev = pev, .max_tevs = max_tevs}; - struct perf_probe_point *pp = &pev->point; + struct perf_probe_point *pp = &pf->pev->point; Dwarf_Off off, noff; size_t cuhl; Dwarf_Die *diep; - Dwarf *dbg; + Dwarf *dbg = NULL; + Dwfl *dwfl; + Dwarf_Addr bias; /* Currently ignored */ int ret = 0; - pf.tevs = zalloc(sizeof(struct kprobe_trace_event) * max_tevs); - if (pf.tevs == NULL) - return -ENOMEM; - *tevs = pf.tevs; - pf.ntevs = 0; - - dbg = dwarf_begin(fd, DWARF_C_READ); + dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias); if (!dbg) { pr_warning("No dwarf info found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); - free(pf.tevs); - *tevs = NULL; return -EBADF; } #if _ELFUTILS_PREREQ(0, 142) /* Get the call frame information from this dwarf */ - pf.cfi = dwarf_getcfi(dbg); + pf->cfi = dwarf_getcfi(dbg); #endif off = 0; - line_list__init(&pf.lcache); + line_list__init(&pf->lcache); /* Loop on CUs (Compilation Unit) */ while (!dwarf_nextcu(dbg, off, &noff, &cuhl, NULL, NULL, NULL) && ret >= 0) { /* Get the DIE(Debugging Information Entry) of this CU */ - diep = dwarf_offdie(dbg, off + cuhl, &pf.cu_die); + diep = dwarf_offdie(dbg, off + cuhl, &pf->cu_die); if (!diep) continue; /* Check if target file is included. */ if (pp->file) - pf.fname = cu_find_realpath(&pf.cu_die, pp->file); + pf->fname = cu_find_realpath(&pf->cu_die, pp->file); else - pf.fname = NULL; + pf->fname = NULL; - if (!pp->file || pf.fname) { + if (!pp->file || pf->fname) { if (pp->function) - ret = find_probe_point_by_func(&pf); + ret = find_probe_point_by_func(pf); else if (pp->lazy_line) - ret = find_probe_point_lazy(NULL, &pf); + ret = find_probe_point_lazy(NULL, pf); else { - pf.lno = pp->line; - ret = find_probe_point_by_line(&pf); + pf->lno = pp->line; + ret = find_probe_point_by_line(pf); } } off = noff; } - line_list__free(&pf.lcache); - dwarf_end(dbg); + 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) +{ + struct trace_event_finder *tf = + container_of(pf, struct trace_event_finder, pf); + struct probe_trace_event *tev; + int ret, i; + + /* Check number of tevs */ + if (tf->ntevs == tf->max_tevs) { + pr_warning("Too many( > %d) probe point found.\n", + tf->max_tevs); + return -ERANGE; + } + tev = &tf->tevs[tf->ntevs++]; + + ret = convert_to_trace_point(sp_die, pf->addr, pf->pev->point.retprobe, + &tev->point); + if (ret < 0) + return ret; + + pr_debug("Probe point found: %s+%lu\n", tev->point.symbol, + tev->point.offset); + + /* Find each argument */ + tev->nargs = pf->pev->nargs; + tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); + if (tev->args == NULL) + return -ENOMEM; + 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); + if (ret != 0) + return ret; + } + + return 0; +} + +/* 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) +{ + struct trace_event_finder tf = { + .pf = {.pev = pev, .callback = add_probe_trace_event}, + .max_tevs = max_tevs}; + int ret; + + /* Allocate result tevs array */ + *tevs = zalloc(sizeof(struct probe_trace_event) * max_tevs); + if (*tevs == NULL) + return -ENOMEM; + + tf.tevs = *tevs; + tf.ntevs = 0; + + ret = find_probes(fd, &tf.pf); + if (ret < 0) { + free(*tevs); + *tevs = NULL; + return ret; + } - return (ret < 0) ? ret : pf.ntevs; + return (ret < 0) ? ret : tf.ntevs; +} + +#define MAX_VAR_LEN 64 + +/* Collect available variables in this scope */ +static int collect_variables_cb(Dwarf_Die *die_mem, void *data) +{ + struct available_var_finder *af = data; + struct variable_list *vl; + char buf[MAX_VAR_LEN]; + int tag, ret; + + vl = &af->vls[af->nvls - 1]; + + tag = dwarf_tag(die_mem); + if (tag == DW_TAG_formal_parameter || + tag == DW_TAG_variable) { + ret = convert_variable_location(die_mem, af->pf.addr, + af->pf.fb_ops, NULL); + if (ret == 0) { + ret = die_get_varname(die_mem, buf, MAX_VAR_LEN); + pr_debug2("Add new var: %s\n", buf); + if (ret > 0) + strlist__add(vl->vars, buf); + } + } + + if (af->child && dwarf_haspc(die_mem, af->pf.addr)) + return DIE_FIND_CB_CONTINUE; + else + return DIE_FIND_CB_SIBLING; +} + +/* Add a found vars into available variables list */ +static int add_available_vars(Dwarf_Die *sp_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; + + /* Check number of tevs */ + if (af->nvls == af->max_vls) { + pr_warning("Too many( > %d) probe point found.\n", af->max_vls); + return -ERANGE; + } + vl = &af->vls[af->nvls++]; + + ret = convert_to_trace_point(sp_die, pf->addr, pf->pev->point.retprobe, + &vl->point); + if (ret < 0) + return ret; + + pr_debug("Probe point found: %s+%lu\n", vl->point.symbol, + vl->point.offset); + + /* Find local variables */ + vl->vars = strlist__new(true, NULL); + if (vl->vars == NULL) + return -ENOMEM; + af->child = true; + die_find_child(sp_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); + +out: + if (strlist__empty(vl->vars)) { + strlist__delete(vl->vars); + vl->vars = NULL; + } + + return ret; +} + +/* 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) +{ + struct available_var_finder af = { + .pf = {.pev = pev, .callback = add_available_vars}, + .max_vls = max_vls, .externs = externs}; + int ret; + + /* Allocate result vls array */ + *vls = zalloc(sizeof(struct variable_list) * max_vls); + if (*vls == NULL) + return -ENOMEM; + + af.vls = *vls; + af.nvls = 0; + + ret = find_probes(fd, &af.pf); + if (ret < 0) { + /* Free vlist for error */ + while (af.nvls--) { + if (af.vls[af.nvls].point.symbol) + free(af.vls[af.nvls].point.symbol); + if (af.vls[af.nvls].vars) + strlist__delete(af.vls[af.nvls].vars); + } + free(af.vls); + *vls = NULL; + return ret; + } + + return (ret < 0) ? ret : af.nvls; } /* Reverse search */ -int find_perf_probe_point(int fd, unsigned long addr, - struct perf_probe_point *ppt) +int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt) { Dwarf_Die cudie, spdie, indie; - Dwarf *dbg; + Dwarf *dbg = NULL; + Dwfl *dwfl = NULL; Dwarf_Line *line; - Dwarf_Addr laddr, eaddr; + Dwarf_Addr laddr, eaddr, bias = 0; const char *tmp; int lineno, ret = 0; bool found = false; - dbg = dwarf_begin(fd, DWARF_C_READ); - if (!dbg) - return -EBADF; + /* Open the live linux kernel */ + dbg = dwfl_init_live_kernel_dwarf(addr, &dwfl, &bias); + if (!dbg) { + pr_warning("No dwarf info found in the vmlinux - " + "please rebuild with CONFIG_DEBUG_INFO=y.\n"); + ret = -EINVAL; + goto end; + } + /* Adjust address with bias */ + addr += bias; /* Find cu die */ - if (!dwarf_addrdie(dbg, (Dwarf_Addr)addr, &cudie)) { + if (!dwarf_addrdie(dbg, (Dwarf_Addr)addr - bias, &cudie)) { + pr_warning("No CU DIE is found at %lx\n", addr); ret = -EINVAL; goto end; } @@ -1086,7 +1597,8 @@ found: } end: - dwarf_end(dbg); + if (dwfl) + dwfl_end(dwfl); if (ret >= 0) ret = found ? 1 : 0; return ret; @@ -1096,7 +1608,7 @@ end: static int line_range_add_line(const char *src, unsigned int lineno, struct line_range *lr) { - /* Copy real path */ + /* Copy source path */ if (!lr->path) { lr->path = strdup(src); if (lr->path == NULL) @@ -1219,8 +1731,11 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data) struct line_finder *lf = param->data; struct line_range *lr = lf->lr; + pr_debug("find (%llx) %s\n", + (unsigned long long)dwarf_dieoffset(sp_die), + dwarf_diename(sp_die)); if (dwarf_tag(sp_die) == DW_TAG_subprogram && - die_compare_name(sp_die, lr->function) == 0) { + die_compare_name(sp_die, lr->function)) { lf->fname = dwarf_decl_file(sp_die); dwarf_decl_line(sp_die, &lr->offset); pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset); @@ -1262,9 +1777,12 @@ int find_line_range(int fd, struct line_range *lr) Dwarf_Off off = 0, noff; size_t cuhl; Dwarf_Die *diep; - Dwarf *dbg; + Dwarf *dbg = NULL; + Dwfl *dwfl; + Dwarf_Addr bias; /* Currently ignored */ + const char *comp_dir; - dbg = dwarf_begin(fd, DWARF_C_READ); + dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias); if (!dbg) { pr_warning("No dwarf info found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); @@ -1298,9 +1816,19 @@ int find_line_range(int fd, struct line_range *lr) } off = noff; } - pr_debug("path: %lx\n", (unsigned long)lr->path); - dwarf_end(dbg); + /* Store comp_dir */ + if (lf.found) { + comp_dir = cu_get_comp_dir(&lf.cu_die); + if (comp_dir) { + lr->comp_dir = strdup(comp_dir); + if (!lr->comp_dir) + ret = -ENOMEM; + } + } + + 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 e1f61dcd18ff..bba69d455699 100644 --- a/tools/perf/util/probe-finder.h +++ b/tools/perf/util/probe-finder.h @@ -16,26 +16,33 @@ static inline int is_c_varname(const char *name) } #ifdef DWARF_SUPPORT -/* Find kprobe_trace_events specified by perf_probe_event from debuginfo */ -extern int find_kprobe_trace_events(int fd, struct perf_probe_event *pev, - struct kprobe_trace_event **tevs, +/* 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); /* Find a perf_probe_point from debuginfo */ -extern int find_perf_probe_point(int fd, unsigned long addr, +extern int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt); +/* Find a line range */ extern int find_line_range(int fd, 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 <libdw.h> +#include <libdwfl.h> #include <version.h> struct probe_finder { struct perf_probe_event *pev; /* Target probe event */ - struct kprobe_trace_event *tevs; /* Result trace events */ - int ntevs; /* Number of trace events */ - int max_tevs; /* Max number of trace events */ + + /* Callback when a probe point is found */ + int (*callback)(Dwarf_Die *sp_die, struct probe_finder *pf); /* For function searching */ int lno; /* Line number */ @@ -50,7 +57,23 @@ struct probe_finder { #endif Dwarf_Op *fb_ops; /* Frame base attribute */ struct perf_probe_arg *pvar; /* Current target variable */ - struct kprobe_trace_arg *tvar; /* Current result variable */ + struct probe_trace_arg *tvar; /* Current result variable */ +}; + +struct trace_event_finder { + struct probe_finder pf; + struct probe_trace_event *tevs; /* Found trace events */ + int ntevs; /* Number of trace events */ + int max_tevs; /* Max number of trace events */ +}; + +struct available_var_finder { + struct probe_finder pf; + struct variable_list *vls; /* Found variable lists */ + int nvls; /* Number of variable lists */ + int max_vls; /* Max no. of variable lists */ + bool externs; /* Find external vars too */ + bool child; /* Search child scopes */ }; struct line_finder { diff --git a/tools/perf/util/pstack.h b/tools/perf/util/pstack.h index 5ad07023504b..4cedea59f518 100644 --- a/tools/perf/util/pstack.h +++ b/tools/perf/util/pstack.h @@ -1,6 +1,8 @@ #ifndef _PERF_PSTACK_ #define _PERF_PSTACK_ +#include <stdbool.h> + struct pstack; struct pstack *pstack__new(unsigned short max_nr_entries); void pstack__delete(struct pstack *self); diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index c422cd676313..fa9d652c2dc3 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -27,8 +27,10 @@ static int perf_session__open(struct perf_session *self, bool force) self->fd = open(self->filename, O_RDONLY); if (self->fd < 0) { - pr_err("failed to open file: %s", self->filename); - if (!strcmp(self->filename, "perf.data")) + int err = errno; + + pr_err("failed to open %s: %s", self->filename, strerror(err)); + if (err == ENOENT && !strcmp(self->filename, "perf.data")) pr_err(" (try 'perf record' first)"); pr_err("\n"); return -errno; @@ -77,6 +79,12 @@ int perf_session__create_kernel_maps(struct perf_session *self) return ret; } +static void perf_session__destroy_kernel_maps(struct perf_session *self) +{ + machine__destroy_kernel_maps(&self->host_machine); + machines__destroy_guest_kernel_maps(&self->machines); +} + struct perf_session *perf_session__new(const char *filename, int mode, bool force, bool repipe) { size_t len = filename ? strlen(filename) + 1 : 0; @@ -94,8 +102,6 @@ struct perf_session *perf_session__new(const char *filename, int mode, bool forc self->hists_tree = RB_ROOT; self->last_match = NULL; self->mmap_window = 32; - self->cwd = NULL; - self->cwdlen = 0; self->machines = RB_ROOT; self->repipe = repipe; INIT_LIST_HEAD(&self->ordered_samples.samples_head); @@ -124,16 +130,43 @@ out_delete: return NULL; } +static void perf_session__delete_dead_threads(struct perf_session *self) +{ + struct thread *n, *t; + + list_for_each_entry_safe(t, n, &self->dead_threads, node) { + list_del(&t->node); + thread__delete(t); + } +} + +static void perf_session__delete_threads(struct perf_session *self) +{ + struct rb_node *nd = rb_first(&self->threads); + + while (nd) { + struct thread *t = rb_entry(nd, struct thread, rb_node); + + rb_erase(&t->rb_node, &self->threads); + nd = rb_next(nd); + thread__delete(t); + } +} + void perf_session__delete(struct perf_session *self) { perf_header__exit(&self->header); + perf_session__destroy_kernel_maps(self); + perf_session__delete_dead_threads(self); + perf_session__delete_threads(self); + machine__exit(&self->host_machine); close(self->fd); - free(self->cwd); free(self); } void perf_session__remove_thread(struct perf_session *self, struct thread *th) { + self->last_match = NULL; rb_erase(&th->rb_node, &self->threads); /* * We may have references to this thread, for instance in some hist_entry @@ -830,23 +863,6 @@ int perf_session__process_events(struct perf_session *self, if (perf_session__register_idle_thread(self) == NULL) return -ENOMEM; - if (!symbol_conf.full_paths) { - char bf[PATH_MAX]; - - if (getcwd(bf, sizeof(bf)) == NULL) { - err = -errno; -out_getcwd_err: - pr_err("failed to get the current directory\n"); - goto out_err; - } - self->cwd = strdup(bf); - if (self->cwd == NULL) { - err = -ENOMEM; - goto out_getcwd_err; - } - self->cwdlen = strlen(self->cwd); - } - if (!self->fd_pipe) err = __perf_session__process_events(self, self->header.data_offset, @@ -854,7 +870,7 @@ out_getcwd_err: self->size, ops); else err = __perf_session__process_pipe_events(self, ops); -out_err: + return err; } diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 2316cb5a4116..b62a553cc67d 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -1,4 +1,5 @@ #include "sort.h" +#include "hist.h" regex_t parent_regex; const char default_parent_pattern[] = "^sys_|^do_page_fault"; @@ -10,10 +11,6 @@ int sort__has_parent = 0; enum sort_type sort__first_dimension; -unsigned int dsos__col_width; -unsigned int comms__col_width; -unsigned int threads__col_width; -static unsigned int parent_symbol__col_width; char * field_sep; LIST_HEAD(hist_entry__sort_list); @@ -28,12 +25,14 @@ 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 = &threads__col_width, + .se_width_idx = HISTC_THREAD, }; struct sort_entry sort_comm = { @@ -41,27 +40,35 @@ struct sort_entry sort_comm = { .se_cmp = sort__comm_cmp, .se_collapse = sort__comm_collapse, .se_snprintf = hist_entry__comm_snprintf, - .se_width = &comms__col_width, + .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 = &dsos__col_width, + .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 = &parent_symbol__col_width, + .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 { @@ -76,6 +83,7 @@ static struct sort_dimension sort_dimensions[] = { { .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) @@ -188,7 +196,8 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, if (verbose) { char o = self->ms.map ? dso__symtab_origin(self->ms.map->dso) : '!'; - ret += repsep_snprintf(bf, size, "%#018llx %c ", self->ip, o); + ret += repsep_snprintf(bf, size, "%*Lx %c ", + BITS_PER_LONG / 4, self->ip, o); } ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level); @@ -196,7 +205,8 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, ret += repsep_snprintf(bf + ret, size - ret, "%s", self->ms.sym->name); else - ret += repsep_snprintf(bf + ret, size - ret, "%#016llx", self->ip); + ret += repsep_snprintf(bf + ret, size - ret, "%*Lx", + BITS_PER_LONG / 4, self->ip); return ret; } @@ -242,6 +252,20 @@ static int hist_entry__parent_snprintf(struct hist_entry *self, char *bf, self->parent ? self->parent->name : "[other]"); } +/* --sort cpu */ + +int64_t +sort__cpu_cmp(struct hist_entry *left, struct hist_entry *right) +{ + return right->cpu - left->cpu; +} + +static int hist_entry__cpu_snprintf(struct hist_entry *self, char *bf, + size_t size, unsigned int width) +{ + return repsep_snprintf(bf, size, "%-*d", width, self->cpu); +} + int sort_dimension__add(const char *tok) { unsigned int i; @@ -281,6 +305,8 @@ int sort_dimension__add(const char *tok) sort__first_dimension = SORT_SYM; else if (!strcmp(sd->name, "parent")) sort__first_dimension = SORT_PARENT; + else if (!strcmp(sd->name, "cpu")) + sort__first_dimension = SORT_CPU; } list_add_tail(&sd->entry->list, &hist_entry__sort_list); diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 0d61c4082f43..0b91053a7d11 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -36,11 +36,14 @@ extern struct sort_entry sort_comm; extern struct sort_entry sort_dso; extern struct sort_entry sort_sym; extern struct sort_entry sort_parent; -extern unsigned int dsos__col_width; -extern unsigned int comms__col_width; -extern unsigned int threads__col_width; extern enum sort_type sort__first_dimension; +/** + * struct hist_entry - histogram entry + * + * @row_offset - offset from the first callchain expanded to appear on screen + * @nr_rows - rows expanded in callchain, recalculated on folding/unfolding + */ struct hist_entry { struct rb_node rb_node; u64 period; @@ -51,7 +54,14 @@ struct hist_entry { struct map_symbol ms; struct thread *thread; u64 ip; + s32 cpu; u32 nr_events; + + /* XXX These two should move to some tree widget lib */ + u16 row_offset; + u16 nr_rows; + + bool init_have_children; char level; u8 filtered; struct symbol *parent; @@ -60,7 +70,7 @@ struct hist_entry { struct hist_entry *pair; struct rb_root sorted_chain; }; - struct callchain_node callchain[0]; + struct callchain_root callchain[0]; }; enum sort_type { @@ -68,7 +78,8 @@ enum sort_type { SORT_COMM, SORT_DSO, SORT_SYM, - SORT_PARENT + SORT_PARENT, + SORT_CPU, }; /* @@ -84,7 +95,7 @@ struct sort_entry { int64_t (*se_collapse)(struct hist_entry *, struct hist_entry *); int (*se_snprintf)(struct hist_entry *self, char *bf, size_t size, unsigned int width); - unsigned int *se_width; + u8 se_width_idx; bool elide; }; @@ -104,6 +115,7 @@ 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, diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 5b276833e2bf..b39f499e575a 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -12,6 +12,7 @@ #include <fcntl.h> #include <unistd.h> #include "build-id.h" +#include "debug.h" #include "symbol.h" #include "strlist.h" @@ -25,6 +26,8 @@ #define NT_GNU_BUILD_ID 3 #endif +static bool dso__build_id_equal(const struct dso *self, u8 *build_id); +static int elf_read_build_id(Elf *elf, void *bf, size_t size); static void dsos__add(struct list_head *head, struct dso *dso); static struct map *map__new2(u64 start, struct dso *dso, enum map_type type); static int dso__load_kernel_sym(struct dso *self, struct map *map, @@ -40,6 +43,14 @@ struct symbol_conf symbol_conf = { .try_vmlinux_path = true, }; +int dso__name_len(const struct dso *self) +{ + if (verbose) + return self->long_name_len; + + return self->short_name_len; +} + bool dso__loaded(const struct dso *self, enum map_type type) { return self->loaded & (1 << type); @@ -120,7 +131,8 @@ static void map_groups__fixup_end(struct map_groups *self) __map_groups__fixup_end(self, i); } -static struct symbol *symbol__new(u64 start, u64 len, const char *name) +static struct symbol *symbol__new(u64 start, u64 len, u8 binding, + const char *name) { size_t namelen = strlen(name) + 1; struct symbol *self = calloc(1, (symbol_conf.priv_size + @@ -133,6 +145,7 @@ static struct symbol *symbol__new(u64 start, u64 len, const char *name) self->start = start; self->end = len ? start + len - 1 : start; + self->binding = binding; self->namelen = namelen - 1; pr_debug4("%s: %s %#Lx-%#Lx\n", __func__, name, start, self->end); @@ -149,8 +162,11 @@ void symbol__delete(struct symbol *self) static size_t symbol__fprintf(struct symbol *self, FILE *fp) { - return fprintf(fp, " %llx-%llx %s\n", - self->start, self->end, self->name); + return fprintf(fp, " %llx-%llx %c %s\n", + self->start, self->end, + self->binding == STB_GLOBAL ? 'g' : + self->binding == STB_LOCAL ? 'l' : 'w', + self->name); } void dso__set_long_name(struct dso *self, char *name) @@ -215,7 +231,9 @@ void dso__delete(struct dso *self) int i; for (i = 0; i < MAP__NR_TYPES; ++i) symbols__delete(&self->symbols[i]); - if (self->long_name != self->name) + if (self->sname_alloc) + free((char *)self->short_name); + if (self->lname_alloc) free(self->long_name); free(self); } @@ -370,6 +388,20 @@ size_t dso__fprintf_buildid(struct dso *self, FILE *fp) return fprintf(fp, "%s", sbuild_id); } +size_t dso__fprintf_symbols_by_name(struct dso *self, enum map_type type, FILE *fp) +{ + size_t ret = 0; + struct rb_node *nd; + struct symbol_name_rb_node *pos; + + for (nd = rb_first(&self->symbol_names[type]); nd; nd = rb_next(nd)) { + pos = rb_entry(nd, struct symbol_name_rb_node, rb_node); + fprintf(fp, "%s\n", pos->sym.name); + } + + return ret; +} + size_t dso__fprintf(struct dso *self, enum map_type type, FILE *fp) { struct rb_node *nd; @@ -440,6 +472,14 @@ struct process_kallsyms_args { struct dso *dso; }; +static u8 kallsyms2elf_type(char type) +{ + if (type == 'W') + return STB_WEAK; + + return isupper(type) ? STB_GLOBAL : STB_LOCAL; +} + static int map__process_kallsym_symbol(void *arg, const char *name, char type, u64 start) { @@ -453,7 +493,7 @@ static int map__process_kallsym_symbol(void *arg, const char *name, /* * Will fix up the end later, when we have all symbols sorted. */ - sym = symbol__new(start, 0, name); + sym = symbol__new(start, 0, kallsyms2elf_type(type), name); if (sym == NULL) return -ENOMEM; @@ -648,7 +688,7 @@ static int dso__load_perf_map(struct dso *self, struct map *map, if (len + 2 >= line_len) continue; - sym = symbol__new(start, size, line + len); + sym = symbol__new(start, size, STB_GLOBAL, line + len); if (sym == NULL) goto out_delete_line; @@ -860,7 +900,7 @@ static int dso__synthesize_plt_symbols(struct dso *self, struct map *map, "%s@plt", elf_sym__name(&sym, symstrs)); f = symbol__new(plt_offset, shdr_plt.sh_entsize, - sympltname); + STB_GLOBAL, sympltname); if (!f) goto out_elf_end; @@ -882,7 +922,7 @@ static int dso__synthesize_plt_symbols(struct dso *self, struct map *map, "%s@plt", elf_sym__name(&sym, symstrs)); f = symbol__new(plt_offset, shdr_plt.sh_entsize, - sympltname); + STB_GLOBAL, sympltname); if (!f) goto out_elf_end; @@ -933,8 +973,28 @@ static bool elf_sec__is_a(GElf_Shdr *self, Elf_Data *secstrs, enum map_type type } } +static size_t elf_addr_to_index(Elf *elf, GElf_Addr addr) +{ + Elf_Scn *sec = NULL; + GElf_Shdr shdr; + size_t cnt = 1; + + while ((sec = elf_nextscn(elf, sec)) != NULL) { + gelf_getshdr(sec, &shdr); + + if ((addr >= shdr.sh_addr) && + (addr < (shdr.sh_addr + shdr.sh_size))) + return cnt; + + ++cnt; + } + + return -1; +} + static int dso__load_sym(struct dso *self, struct map *map, const char *name, - int fd, symbol_filter_t filter, int kmodule) + int fd, symbol_filter_t filter, int kmodule, + int want_symtab) { struct kmap *kmap = self->kernel ? map__kmap(map) : NULL; struct map *curr_map = map; @@ -944,31 +1004,51 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, int err = -1; uint32_t idx; GElf_Ehdr ehdr; - GElf_Shdr shdr; - Elf_Data *syms; + GElf_Shdr shdr, opdshdr; + Elf_Data *syms, *opddata = NULL; GElf_Sym sym; - Elf_Scn *sec, *sec_strndx; + Elf_Scn *sec, *sec_strndx, *opdsec; Elf *elf; int nr = 0; + size_t opdidx = 0; elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); if (elf == NULL) { - pr_err("%s: cannot read %s ELF file.\n", __func__, name); + pr_debug("%s: cannot read %s ELF file.\n", __func__, name); goto out_close; } if (gelf_getehdr(elf, &ehdr) == NULL) { - pr_err("%s: cannot get elf header.\n", __func__); + pr_debug("%s: cannot get elf header.\n", __func__); goto out_elf_end; } + /* Always reject images with a mismatched build-id: */ + if (self->has_build_id) { + u8 build_id[BUILD_ID_SIZE]; + + if (elf_read_build_id(elf, build_id, + BUILD_ID_SIZE) != BUILD_ID_SIZE) + goto out_elf_end; + + if (!dso__build_id_equal(self, build_id)) + goto out_elf_end; + } + sec = elf_section_by_name(elf, &ehdr, &shdr, ".symtab", NULL); if (sec == NULL) { + if (want_symtab) + goto out_elf_end; + sec = elf_section_by_name(elf, &ehdr, &shdr, ".dynsym", NULL); if (sec == NULL) goto out_elf_end; } + opdsec = elf_section_by_name(elf, &ehdr, &opdshdr, ".opd", &opdidx); + if (opdsec) + opddata = elf_rawdata(opdsec, NULL); + syms = elf_getdata(sec, NULL); if (syms == NULL) goto out_elf_end; @@ -1013,6 +1093,23 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, if (!is_label && !elf_sym__is_a(&sym, map->type)) continue; + /* Reject ARM ELF "mapping symbols": these aren't unique and + * don't identify functions, so will confuse the profile + * output: */ + if (ehdr.e_machine == EM_ARM) { + if (!strcmp(elf_name, "$a") || + !strcmp(elf_name, "$d") || + !strcmp(elf_name, "$t")) + continue; + } + + if (opdsec && sym.st_shndx == opdidx) { + u32 offset = sym.st_value - opdshdr.sh_addr; + u64 *opd = opddata->d_buf + offset; + sym.st_value = *opd; + sym.st_shndx = elf_addr_to_index(elf, sym.st_value); + } + sec = elf_getscn(elf, sym.st_shndx); if (!sec) goto out_elf_end; @@ -1086,7 +1183,8 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, if (demangled != NULL) elf_name = demangled; new_symbol: - f = symbol__new(sym.st_value, sym.st_size, elf_name); + f = symbol__new(sym.st_value, sym.st_size, + GELF_ST_BIND(sym.st_info), elf_name); free(demangled); if (!f) goto out_elf_end; @@ -1151,37 +1249,26 @@ bool __dsos__read_build_ids(struct list_head *head, bool with_hits) */ #define NOTE_ALIGN(n) (((n) + 3) & -4U) -int filename__read_build_id(const char *filename, void *bf, size_t size) +static int elf_read_build_id(Elf *elf, void *bf, size_t size) { - int fd, err = -1; + int err = -1; GElf_Ehdr ehdr; GElf_Shdr shdr; Elf_Data *data; Elf_Scn *sec; Elf_Kind ek; void *ptr; - Elf *elf; if (size < BUILD_ID_SIZE) goto out; - fd = open(filename, O_RDONLY); - if (fd < 0) - goto out; - - elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); - if (elf == NULL) { - pr_debug2("%s: cannot read %s ELF file.\n", __func__, filename); - goto out_close; - } - ek = elf_kind(elf); if (ek != ELF_K_ELF) - goto out_elf_end; + goto out; if (gelf_getehdr(elf, &ehdr) == NULL) { pr_err("%s: cannot get elf header.\n", __func__); - goto out_elf_end; + goto out; } sec = elf_section_by_name(elf, &ehdr, &shdr, @@ -1190,12 +1277,12 @@ int filename__read_build_id(const char *filename, void *bf, size_t size) sec = elf_section_by_name(elf, &ehdr, &shdr, ".notes", NULL); if (sec == NULL) - goto out_elf_end; + goto out; } data = elf_getdata(sec, NULL); if (data == NULL) - goto out_elf_end; + goto out; ptr = data->d_buf; while (ptr < (data->d_buf + data->d_size)) { @@ -1217,7 +1304,31 @@ int filename__read_build_id(const char *filename, void *bf, size_t size) } ptr += descsz; } -out_elf_end: + +out: + return err; +} + +int filename__read_build_id(const char *filename, void *bf, size_t size) +{ + int fd, err = -1; + Elf *elf; + + if (size < BUILD_ID_SIZE) + goto out; + + fd = open(filename, O_RDONLY); + if (fd < 0) + goto out; + + elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); + if (elf == NULL) { + pr_debug2("%s: cannot read %s ELF file.\n", __func__, filename); + goto out_close; + } + + err = elf_read_build_id(elf, bf, size); + elf_end(elf); out_close: close(fd); @@ -1293,11 +1404,11 @@ int dso__load(struct dso *self, struct map *map, symbol_filter_t filter) { int size = PATH_MAX; char *name; - u8 build_id[BUILD_ID_SIZE]; int ret = -1; int fd; struct machine *machine; const char *root_dir; + int want_symtab; dso__set_loaded(self, map->type); @@ -1324,13 +1435,18 @@ int dso__load(struct dso *self, struct map *map, symbol_filter_t filter) return ret; } - self->origin = DSO__ORIG_BUILD_ID_CACHE; - if (dso__build_id_filename(self, name, size) != NULL) - goto open_file; -more: - do { - self->origin++; + /* Iterate over candidate debug images. + * On the first pass, only load images if they have a full symtab. + * Failing that, do a second pass where we accept .dynsym also + */ + for (self->origin = DSO__ORIG_BUILD_ID_CACHE, want_symtab = 1; + self->origin != DSO__ORIG_NOT_FOUND; + self->origin++) { switch (self->origin) { + case DSO__ORIG_BUILD_ID_CACHE: + if (dso__build_id_filename(self, name, size) == NULL) + continue; + break; case DSO__ORIG_FEDORA: snprintf(name, size, "/usr/lib/debug%s.debug", self->long_name); @@ -1339,21 +1455,20 @@ more: snprintf(name, size, "/usr/lib/debug%s", self->long_name); break; - case DSO__ORIG_BUILDID: - if (filename__read_build_id(self->long_name, build_id, - sizeof(build_id))) { - char build_id_hex[BUILD_ID_SIZE * 2 + 1]; - build_id__sprintf(build_id, sizeof(build_id), - build_id_hex); - snprintf(name, size, - "/usr/lib/debug/.build-id/%.2s/%s.debug", - build_id_hex, build_id_hex + 2); - if (self->has_build_id) - goto compare_build_id; - break; + case DSO__ORIG_BUILDID: { + char build_id_hex[BUILD_ID_SIZE * 2 + 1]; + + if (!self->has_build_id) + continue; + + build_id__sprintf(self->build_id, + sizeof(self->build_id), + build_id_hex); + snprintf(name, size, + "/usr/lib/debug/.build-id/%.2s/%s.debug", + build_id_hex, build_id_hex + 2); } - self->origin++; - /* Fall thru */ + break; case DSO__ORIG_DSO: snprintf(name, size, "%s", self->long_name); break; @@ -1366,36 +1481,41 @@ more: break; default: - goto out; + /* + * If we wanted a full symtab but no image had one, + * relax our requirements and repeat the search. + */ + if (want_symtab) { + want_symtab = 0; + self->origin = DSO__ORIG_BUILD_ID_CACHE; + } else + continue; } - if (self->has_build_id) { - if (filename__read_build_id(name, build_id, - sizeof(build_id)) < 0) - goto more; -compare_build_id: - if (!dso__build_id_equal(self, build_id)) - goto more; - } -open_file: + /* Name is now the name of the next image to try */ fd = open(name, O_RDONLY); - } while (fd < 0); + if (fd < 0) + continue; - ret = dso__load_sym(self, map, name, fd, filter, 0); - close(fd); + ret = dso__load_sym(self, map, name, fd, filter, 0, + want_symtab); + close(fd); - /* - * Some people seem to have debuginfo files _WITHOUT_ debug info!?!? - */ - if (!ret) - goto more; + /* + * Some people seem to have debuginfo files _WITHOUT_ debug + * info!?!? + */ + if (!ret) + continue; - if (ret > 0) { - int nr_plt = dso__synthesize_plt_symbols(self, map, filter); - if (nr_plt > 0) - ret += nr_plt; + if (ret > 0) { + int nr_plt = dso__synthesize_plt_symbols(self, map, filter); + if (nr_plt > 0) + ret += nr_plt; + break; + } } -out: + free(name); if (ret < 0 && strstr(self->name, " (deleted)") != NULL) return 0; @@ -1494,6 +1614,7 @@ static int map_groups__set_modules_path_dir(struct map_groups *self, goto out; } dso__set_long_name(map->dso, long_name); + map->dso->lname_alloc = 1; dso__kernel_module_get_build_id(map->dso, ""); } } @@ -1656,36 +1777,12 @@ static int dso__load_vmlinux(struct dso *self, struct map *map, { int err = -1, fd; - if (self->has_build_id) { - u8 build_id[BUILD_ID_SIZE]; - - if (filename__read_build_id(vmlinux, build_id, - sizeof(build_id)) < 0) { - pr_debug("No build_id in %s, ignoring it\n", vmlinux); - return -1; - } - if (!dso__build_id_equal(self, build_id)) { - char expected_build_id[BUILD_ID_SIZE * 2 + 1], - vmlinux_build_id[BUILD_ID_SIZE * 2 + 1]; - - build_id__sprintf(self->build_id, - sizeof(self->build_id), - expected_build_id); - build_id__sprintf(build_id, sizeof(build_id), - vmlinux_build_id); - pr_debug("build_id in %s is %s while expected is %s, " - "ignoring it\n", vmlinux, vmlinux_build_id, - expected_build_id); - return -1; - } - } - fd = open(vmlinux, O_RDONLY); if (fd < 0) return -1; dso__set_loaded(self, map->type); - err = dso__load_sym(self, map, vmlinux, fd, filter, 0); + err = dso__load_sym(self, map, vmlinux, fd, filter, 0, 0); close(fd); if (err > 0) @@ -2048,6 +2145,36 @@ int __machine__create_kernel_maps(struct machine *self, struct dso *kernel) return 0; } +void machine__destroy_kernel_maps(struct machine *self) +{ + enum map_type type; + + for (type = 0; type < MAP__NR_TYPES; ++type) { + struct kmap *kmap; + + if (self->vmlinux_maps[type] == NULL) + continue; + + kmap = map__kmap(self->vmlinux_maps[type]); + map_groups__remove(&self->kmaps, self->vmlinux_maps[type]); + if (kmap->ref_reloc_sym) { + /* + * ref_reloc_sym is shared among all maps, so free just + * on one of them. + */ + if (type == MAP__FUNCTION) { + free((char *)kmap->ref_reloc_sym->name); + kmap->ref_reloc_sym->name = NULL; + free(kmap->ref_reloc_sym); + } + kmap->ref_reloc_sym = NULL; + } + + map__delete(self->vmlinux_maps[type]); + self->vmlinux_maps[type] = NULL; + } +} + int machine__create_kernel_maps(struct machine *self) { struct dso *kernel = machine__create_kernel(self); @@ -2155,6 +2282,9 @@ static int setup_list(struct strlist **list, const char *list_str, int symbol__init(void) { + if (symbol_conf.initialized) + return 0; + elf_version(EV_CURRENT); if (symbol_conf.sort_by_name) symbol_conf.priv_size += (sizeof(struct symbol_name_rb_node) - @@ -2180,6 +2310,7 @@ int symbol__init(void) symbol_conf.sym_list_str, "symbol") < 0) goto out_free_comm_list; + symbol_conf.initialized = true; return 0; out_free_dso_list: @@ -2189,6 +2320,18 @@ out_free_comm_list: return -1; } +void symbol__exit(void) +{ + if (!symbol_conf.initialized) + return; + strlist__delete(symbol_conf.sym_list); + strlist__delete(symbol_conf.dso_list); + strlist__delete(symbol_conf.comm_list); + vmlinux_path__exit(); + symbol_conf.sym_list = symbol_conf.dso_list = symbol_conf.comm_list = NULL; + symbol_conf.initialized = false; +} + int machines__create_kernel_maps(struct rb_root *self, pid_t pid) { struct machine *machine = machines__findnew(self, pid); @@ -2283,6 +2426,19 @@ failure: return ret; } +void machines__destroy_guest_kernel_maps(struct rb_root *self) +{ + struct rb_node *next = rb_first(self); + + while (next) { + struct machine *pos = rb_entry(next, struct machine, rb_node); + + next = rb_next(&pos->rb_node); + rb_erase(&pos->rb_node, self); + machine__delete(pos); + } +} + int machine__load_kallsyms(struct machine *self, const char *filename, enum map_type type, symbol_filter_t filter) { diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 5e02d2c17154..038f2201ee09 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -9,8 +9,6 @@ #include <linux/rbtree.h> #include <stdio.h> -#define DEBUG_CACHE_DIR ".debug" - #ifdef HAVE_CPLUS_DEMANGLE extern char *cplus_demangle(const char *, int); @@ -55,6 +53,7 @@ struct symbol { u64 start; u64 end; u16 namelen; + u8 binding; char name[0]; }; @@ -70,9 +69,10 @@ struct symbol_conf { show_nr_samples, use_callchain, exclude_other, - full_paths, - show_cpu_utilization; + show_cpu_utilization, + initialized; const char *vmlinux_name, + *source_prefix, *field_sep; const char *default_guest_vmlinux_name, *default_guest_kallsyms, @@ -103,6 +103,8 @@ struct ref_reloc_sym { struct map_symbol { struct map *map; struct symbol *sym; + bool unfolded; + bool has_children; }; struct addr_location { @@ -112,7 +114,8 @@ struct addr_location { u64 addr; char level; bool filtered; - unsigned int cpumode; + u8 cpumode; + s32 cpu; }; enum dso_kernel_type { @@ -125,12 +128,14 @@ struct dso { struct list_head node; struct rb_root symbols[MAP__NR_TYPES]; struct rb_root symbol_names[MAP__NR_TYPES]; + enum dso_kernel_type kernel; u8 adjust_symbols:1; u8 slen_calculated:1; u8 has_build_id:1; - enum dso_kernel_type kernel; u8 hit:1; u8 annotate_warned:1; + u8 sname_alloc:1; + u8 lname_alloc:1; unsigned char origin; u8 sorted_by_name; u8 loaded; @@ -146,6 +151,8 @@ struct dso *dso__new(const char *name); struct dso *dso__new_kernel(const char *name); void dso__delete(struct dso *self); +int dso__name_len(const struct dso *self); + bool dso__loaded(const struct dso *self, enum map_type type); bool dso__sorted_by_name(const struct dso *self, enum map_type type); @@ -175,6 +182,7 @@ size_t machines__fprintf_dsos(struct rb_root *self, FILE *fp); size_t machines__fprintf_dsos_buildid(struct rb_root *self, FILE *fp, bool with_hits); size_t dso__fprintf_buildid(struct dso *self, FILE *fp); +size_t dso__fprintf_symbols_by_name(struct dso *self, enum map_type type, FILE *fp); size_t dso__fprintf(struct dso *self, enum map_type type, FILE *fp); enum dso_origin { @@ -207,13 +215,16 @@ int kallsyms__parse(const char *filename, void *arg, int (*process_symbol)(void *arg, const char *name, char type, u64 start)); +void machine__destroy_kernel_maps(struct machine *self); int __machine__create_kernel_maps(struct machine *self, struct dso *kernel); int machine__create_kernel_maps(struct machine *self); int machines__create_kernel_maps(struct rb_root *self, pid_t pid); int machines__create_guest_kernel_maps(struct rb_root *self); +void machines__destroy_guest_kernel_maps(struct rb_root *self); int symbol__init(void); +void symbol__exit(void); bool symbol_type__is_a(char symbol_type, enum map_type map_type); size_t machine__fprintf_vmlinux_path(struct machine *self, FILE *fp); diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index 9a448b47400c..8c72d888e449 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -62,6 +62,13 @@ static struct thread *thread__new(pid_t pid) return self; } +void thread__delete(struct thread *self) +{ + map_groups__exit(&self->mg); + free(self->comm); + free(self); +} + int thread__set_comm(struct thread *self, const char *comm) { int err; diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h index ee6bbcf277ca..688500ff826f 100644 --- a/tools/perf/util/thread.h +++ b/tools/perf/util/thread.h @@ -20,6 +20,8 @@ struct thread { struct perf_session; +void thread__delete(struct thread *self); + int find_all_tid(int pid, pid_t ** all_tid); int thread__set_comm(struct thread *self, const char *comm); int thread__comm_len(struct thread *self); diff --git a/tools/perf/util/trace-event-scripting.c b/tools/perf/util/trace-event-scripting.c index 7ea983acfaea..f7af2fca965d 100644 --- a/tools/perf/util/trace-event-scripting.c +++ b/tools/perf/util/trace-event-scripting.c @@ -97,7 +97,7 @@ void setup_python_scripting(void) register_python_scripting(&python_scripting_unsupported_ops); } #else -struct scripting_ops python_scripting_ops; +extern struct scripting_ops python_scripting_ops; void setup_python_scripting(void) { @@ -158,7 +158,7 @@ void setup_perl_scripting(void) register_perl_scripting(&perl_scripting_unsupported_ops); } #else -struct scripting_ops perl_scripting_ops; +extern struct scripting_ops perl_scripting_ops; void setup_perl_scripting(void) { diff --git a/tools/perf/util/ui/browser.c b/tools/perf/util/ui/browser.c new file mode 100644 index 000000000000..8bc010edca25 --- /dev/null +++ b/tools/perf/util/ui/browser.c @@ -0,0 +1,337 @@ +#include "libslang.h" +#include <linux/compiler.h> +#include <linux/list.h> +#include <linux/rbtree.h> +#include <stdlib.h> +#include <sys/ttydefaults.h> +#include "browser.h" +#include "helpline.h" +#include "../color.h" +#include "../util.h" +#include <stdio.h> + +static int ui_browser__percent_color(double percent, bool current) +{ + if (current) + return HE_COLORSET_SELECTED; + if (percent >= MIN_RED) + return HE_COLORSET_TOP; + if (percent >= MIN_GREEN) + return HE_COLORSET_MEDIUM; + return HE_COLORSET_NORMAL; +} + +void ui_browser__set_color(struct ui_browser *self __used, int color) +{ + SLsmg_set_color(color); +} + +void ui_browser__set_percent_color(struct ui_browser *self, + double percent, bool current) +{ + int color = ui_browser__percent_color(percent, current); + ui_browser__set_color(self, color); +} + +void ui_browser__gotorc(struct ui_browser *self, int y, int x) +{ + SLsmg_gotorc(self->y + y, self->x + x); +} + +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; + + switch (whence) { + case SEEK_SET: + pos = head->next; + break; + case SEEK_CUR: + pos = self->top; + break; + case SEEK_END: + pos = head->prev; + break; + default: + return; + } + + if (offset > 0) { + while (offset-- != 0) + pos = pos->next; + } else { + while (offset++ != 0) + pos = pos->prev; + } + + self->top = pos; +} + +void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence) +{ + struct rb_root *root = self->entries; + struct rb_node *nd; + + switch (whence) { + case SEEK_SET: + nd = rb_first(root); + break; + case SEEK_CUR: + nd = self->top; + break; + case SEEK_END: + nd = rb_last(root); + break; + default: + return; + } + + if (offset > 0) { + while (offset-- != 0) + nd = rb_next(nd); + } else { + while (offset++ != 0) + nd = rb_prev(nd); + } + + self->top = nd; +} + +unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self) +{ + struct rb_node *nd; + int row = 0; + + if (self->top == NULL) + self->top = rb_first(self->entries); + + nd = self->top; + + while (nd != NULL) { + ui_browser__gotorc(self, row, 0); + self->write(self, nd, row); + if (++row == self->height) + break; + nd = rb_next(nd); + } + + return row; +} + +bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row) +{ + return self->top_idx + row == self->index; +} + +void ui_browser__refresh_dimensions(struct ui_browser *self) +{ + int cols, rows; + newtGetScreenSize(&cols, &rows); + + self->width = cols - 1; + self->height = rows - 2; + self->y = 1; + self->x = 0; +} + +void ui_browser__reset_index(struct ui_browser *self) +{ + self->index = self->top_idx = 0; + self->seek(self, 0, SEEK_SET); +} + +void ui_browser__add_exit_key(struct ui_browser *self, int key) +{ + newtFormAddHotKey(self->form, key); +} + +void ui_browser__add_exit_keys(struct ui_browser *self, int keys[]) +{ + int i = 0; + + while (keys[i] && i < 64) { + ui_browser__add_exit_key(self, keys[i]); + ++i; + } +} + +int ui_browser__show(struct ui_browser *self, const char *title, + const char *helpline, ...) +{ + 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; + + SLsmg_gotorc(0, 0); + ui_browser__set_color(self, NEWT_COLORSET_ROOT); + slsmg_write_nstring(title, self->width); + + ui_browser__add_exit_keys(self, keys); + newtFormAddComponent(self->form, self->sb); + + va_start(ap, helpline); + ui_helpline__vpush(helpline, ap); + va_end(ap); + return 0; +} + +void ui_browser__hide(struct ui_browser *self) +{ + newtFormDestroy(self->form); + self->form = NULL; + ui_helpline__pop(); +} + +int ui_browser__refresh(struct ui_browser *self) +{ + int row; + + 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, ' '); + + return 0; +} + +int ui_browser__run(struct ui_browser *self) +{ + struct newtExitStruct es; + + if (ui_browser__refresh(self) < 0) + return -1; + + while (1) { + off_t offset; + + newtFormRun(self->form, &es); + + if (es.reason != NEWT_EXIT_HOTKEY) + break; + switch (es.u.key) { + case NEWT_KEY_DOWN: + if (self->index == self->nr_entries - 1) + break; + ++self->index; + if (self->index == self->top_idx + self->height) { + ++self->top_idx; + self->seek(self, +1, SEEK_CUR); + } + break; + case NEWT_KEY_UP: + if (self->index == 0) + break; + --self->index; + if (self->index < self->top_idx) { + --self->top_idx; + self->seek(self, -1, SEEK_CUR); + } + break; + case NEWT_KEY_PGDN: + case ' ': + if (self->top_idx + self->height > self->nr_entries - 1) + break; + + offset = self->height; + if (self->index + offset > self->nr_entries - 1) + offset = self->nr_entries - 1 - self->index; + self->index += offset; + self->top_idx += offset; + self->seek(self, +offset, SEEK_CUR); + break; + case NEWT_KEY_PGUP: + if (self->top_idx == 0) + break; + + if (self->top_idx < self->height) + offset = self->top_idx; + else + offset = self->height; + + self->index -= offset; + self->top_idx -= offset; + self->seek(self, -offset, SEEK_CUR); + break; + case NEWT_KEY_HOME: + ui_browser__reset_index(self); + break; + case NEWT_KEY_END: + offset = self->height - 1; + if (offset >= self->nr_entries) + offset = self->nr_entries - 1; + + self->index = self->nr_entries - 1; + self->top_idx = self->index - offset; + self->seek(self, -offset, SEEK_END); + break; + default: + return es.u.key; + } + if (ui_browser__refresh(self) < 0) + return -1; + } + return -1; +} + +unsigned int ui_browser__list_head_refresh(struct ui_browser *self) +{ + struct list_head *pos; + struct list_head *head = self->entries; + int row = 0; + + if (self->top == NULL || self->top == self->entries) + self->top = 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; + } + + 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", +}; + +void ui_browser__init(void) +{ + struct newtPercentTreeColors *c = &defaultPercentTreeColors; + + 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); +} diff --git a/tools/perf/util/ui/browser.h b/tools/perf/util/ui/browser.h new file mode 100644 index 000000000000..0dc7e4da36f5 --- /dev/null +++ b/tools/perf/util/ui/browser.h @@ -0,0 +1,51 @@ +#ifndef _PERF_UI_BROWSER_H_ +#define _PERF_UI_BROWSER_H_ 1 + +#include <stdbool.h> +#include <newt.h> +#include <sys/types.h> +#include "../types.h" + +#define HE_COLORSET_TOP 50 +#define HE_COLORSET_MEDIUM 51 +#define HE_COLORSET_NORMAL 52 +#define HE_COLORSET_SELECTED 53 +#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; + 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); + u32 nr_entries; +}; + + +void ui_browser__set_color(struct ui_browser *self, int color); +void ui_browser__set_percent_color(struct ui_browser *self, + double percent, bool current); +bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row); +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[]); +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); + +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); + +void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whence); +unsigned int ui_browser__list_head_refresh(struct ui_browser *self); + +void ui_browser__init(void); +#endif /* _PERF_UI_BROWSER_H_ */ diff --git a/tools/perf/util/ui/browsers/annotate.c b/tools/perf/util/ui/browsers/annotate.c new file mode 100644 index 000000000000..82b78f99251b --- /dev/null +++ b/tools/perf/util/ui/browsers/annotate.c @@ -0,0 +1,237 @@ +#include "../browser.h" +#include "../helpline.h" +#include "../libslang.h" +#include "../../hist.h" +#include "../../sort.h" +#include "../../symbol.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); +} + +struct annotate_browser { + struct ui_browser b; + struct rb_root entries; + struct rb_node *curr_hot; +}; + +struct objdump_line_rb_node { + struct rb_node rb_node; + double percent; + u32 idx; +}; + +static inline +struct objdump_line_rb_node *objdump_line__rb(struct objdump_line *self) +{ + return (struct objdump_line_rb_node *)(self + 1); +} + +static void annotate_browser__write(struct ui_browser *self, void *entry, int row) +{ + struct objdump_line *ol = rb_entry(entry, struct objdump_line, node); + bool current_entry = ui_browser__is_current_entry(self, row); + int width = self->width; + + if (ol->offset != -1) { + struct objdump_line_rb_node *olrb = objdump_line__rb(ol); + ui_browser__set_percent_color(self, olrb->percent, current_entry); + slsmg_printf(" %7.2f ", olrb->percent); + if (!current_entry) + ui_browser__set_color(self, HE_COLORSET_CODE); + } else { + ui_browser__set_percent_color(self, 0, current_entry); + slsmg_write_nstring(" ", 9); + } + + SLsmg_write_char(':'); + slsmg_write_nstring(" ", 8); + if (!*ol->line) + slsmg_write_nstring(" ", width - 18); + else + slsmg_write_nstring(ol->line, width - 18); +} + +static double objdump_line__calc_percent(struct objdump_line *self, + struct list_head *head, + struct symbol *sym) +{ + double percent = 0.0; + + if (self->offset != -1) { + int len = sym->end - sym->start; + unsigned int hits = 0; + struct sym_priv *priv = symbol__priv(sym); + struct sym_ext *sym_ext = priv->ext; + struct sym_hist *h = priv->hist; + s64 offset = self->offset; + struct objdump_line *next = objdump__get_next_ip_line(head, self); + + + while (offset < (s64)len && + (next == NULL || offset < next->offset)) { + if (sym_ext) { + percent += sym_ext[offset].percent; + } else + hits += h->ip[offset]; + + ++offset; + } + + if (sym_ext == NULL && h->sum) + percent = 100.0 * hits / h->sum; + } + + return percent; +} + +static void objdump__insert_line(struct rb_root *self, + struct objdump_line_rb_node *line) +{ + struct rb_node **p = &self->rb_node; + struct rb_node *parent = NULL; + struct objdump_line_rb_node *l; + + while (*p != NULL) { + parent = *p; + l = rb_entry(parent, struct objdump_line_rb_node, rb_node); + if (line->percent < l->percent) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + rb_link_node(&line->rb_node, parent, p); + rb_insert_color(&line->rb_node, self); +} + +static void annotate_browser__set_top(struct annotate_browser *self, + struct rb_node *nd) +{ + struct objdump_line_rb_node *rbpos; + struct objdump_line *pos; + unsigned back; + + ui_browser__refresh_dimensions(&self->b); + back = self->b.height / 2; + rbpos = rb_entry(nd, struct objdump_line_rb_node, rb_node); + pos = ((struct objdump_line *)rbpos) - 1; + self->b.top_idx = self->b.index = rbpos->idx; + + while (self->b.top_idx != 0 && back != 0) { + pos = list_entry(pos->node.prev, struct objdump_line, node); + + --self->b.top_idx; + --back; + } + + self->b.top = pos; + self->curr_hot = nd; +} + +static int annotate_browser__run(struct annotate_browser *self) +{ + struct rb_node *nd; + struct hist_entry *he = self->b.priv; + int key; + + if (ui_browser__show(&self->b, he->ms.sym->name, + "<-, -> or ESC: exit, TAB/shift+TAB: cycle thru samples") < 0) + return -1; + /* + * To allow builtin-annotate to cycle thru multiple symbols by + * examining the exit key for this function. + */ + ui_browser__add_exit_key(&self->b, NEWT_KEY_RIGHT); + + nd = self->curr_hot; + if (nd) { + int tabs[] = { NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0 }; + ui_browser__add_exit_keys(&self->b, tabs); + } + + while (1) { + key = ui_browser__run(&self->b); + + switch (key) { + case NEWT_KEY_TAB: + nd = rb_prev(nd); + if (nd == NULL) + nd = rb_last(&self->entries); + annotate_browser__set_top(self, nd); + break; + case NEWT_KEY_UNTAB: + nd = rb_next(nd); + if (nd == NULL) + nd = rb_first(&self->entries); + annotate_browser__set_top(self, nd); + break; + default: + goto out; + } + } +out: + ui_browser__hide(&self->b); + return key; +} + +int hist_entry__tui_annotate(struct hist_entry *self) +{ + struct objdump_line *pos, *n; + struct objdump_line_rb_node *rbpos; + LIST_HEAD(head); + struct annotate_browser browser = { + .b = { + .entries = &head, + .refresh = ui_browser__list_head_refresh, + .seek = ui_browser__list_head_seek, + .write = annotate_browser__write, + .priv = self, + }, + }; + int ret; + + if (self->ms.sym == NULL) + return -1; + + if (self->ms.map->dso->annotate_warned) + return -1; + + if (hist_entry__annotate(self, &head, sizeof(*rbpos)) < 0) { + ui__error_window(ui_helpline__last_msg); + return -1; + } + + ui_helpline__push("Press <- or ESC to exit"); + + list_for_each_entry(pos, &head, node) { + size_t line_len = strlen(pos->line); + if (browser.b.width < line_len) + browser.b.width = line_len; + rbpos = objdump_line__rb(pos); + rbpos->idx = browser.b.nr_entries++; + rbpos->percent = objdump_line__calc_percent(pos, &head, self->ms.sym); + if (rbpos->percent < 0.01) + continue; + objdump__insert_line(&browser.entries, rbpos); + } + + /* + * Position the browser at the hottest line. + */ + browser.curr_hot = rb_last(&browser.entries); + if (browser.curr_hot) + annotate_browser__set_top(&browser, browser.curr_hot); + + browser.b.width += 18; /* Percentage */ + ret = annotate_browser__run(&browser); + list_for_each_entry_safe(pos, n, &head, node) { + list_del(&pos->node); + objdump_line__free(pos); + } + return ret; +} diff --git a/tools/perf/util/ui/browsers/hists.c b/tools/perf/util/ui/browsers/hists.c new file mode 100644 index 000000000000..ebda8c3fde9e --- /dev/null +++ b/tools/perf/util/ui/browsers/hists.c @@ -0,0 +1,1013 @@ +#define _GNU_SOURCE +#include <stdio.h> +#undef _GNU_SOURCE +#include "../libslang.h" +#include <stdlib.h> +#include <string.h> +#include <newt.h> +#include <linux/rbtree.h> + +#include "../../hist.h" +#include "../../pstack.h" +#include "../../sort.h" +#include "../../util.h" + +#include "../browser.h" +#include "../helpline.h" +#include "../util.h" +#include "map.h" + +struct hist_browser { + struct ui_browser b; + struct hists *hists; + struct hist_entry *he_selection; + struct map_symbol *selection; +}; + +static void hist_browser__refresh_dimensions(struct hist_browser *self) +{ + /* 3 == +/- toggle symbol before actual hist_entry rendering */ + self->b.width = 3 + (hists__sort_list_width(self->hists) + + sizeof("[k]")); +} + +static void hist_browser__reset(struct hist_browser *self) +{ + self->b.nr_entries = self->hists->nr_entries; + hist_browser__refresh_dimensions(self); + ui_browser__reset_index(&self->b); +} + +static char tree__folded_sign(bool unfolded) +{ + return unfolded ? '-' : '+'; +} + +static char map_symbol__folded(const struct map_symbol *self) +{ + return self->has_children ? tree__folded_sign(self->unfolded) : ' '; +} + +static char hist_entry__folded(const struct hist_entry *self) +{ + return map_symbol__folded(&self->ms); +} + +static char callchain_list__folded(const struct callchain_list *self) +{ + return map_symbol__folded(&self->ms); +} + +static void map_symbol__set_folding(struct map_symbol *self, bool unfold) +{ + self->unfolded = unfold ? self->has_children : false; +} + +static int callchain_node__count_rows_rb_tree(struct callchain_node *self) +{ + int n = 0; + struct rb_node *nd; + + for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { + struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); + struct callchain_list *chain; + char folded_sign = ' '; /* No children */ + + list_for_each_entry(chain, &child->val, list) { + ++n; + /* We need this because we may not have children */ + folded_sign = callchain_list__folded(chain); + if (folded_sign == '+') + break; + } + + if (folded_sign == '-') /* Have children and they're unfolded */ + n += callchain_node__count_rows_rb_tree(child); + } + + return n; +} + +static int callchain_node__count_rows(struct callchain_node *node) +{ + struct callchain_list *chain; + bool unfolded = false; + int n = 0; + + list_for_each_entry(chain, &node->val, list) { + ++n; + unfolded = chain->ms.unfolded; + } + + if (unfolded) + n += callchain_node__count_rows_rb_tree(node); + + return n; +} + +static int callchain__count_rows(struct rb_root *chain) +{ + struct rb_node *nd; + int n = 0; + + for (nd = rb_first(chain); nd; nd = rb_next(nd)) { + struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + n += callchain_node__count_rows(node); + } + + return n; +} + +static bool map_symbol__toggle_fold(struct map_symbol *self) +{ + if (!self->has_children) + return false; + + self->unfolded = !self->unfolded; + return true; +} + +static void callchain_node__init_have_children_rb_tree(struct callchain_node *self) +{ + struct rb_node *nd = rb_first(&self->rb_root); + + for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { + struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); + struct callchain_list *chain; + bool first = true; + + list_for_each_entry(chain, &child->val, list) { + if (first) { + first = false; + chain->ms.has_children = chain->list.next != &child->val || + !RB_EMPTY_ROOT(&child->rb_root); + } else + chain->ms.has_children = chain->list.next == &child->val && + !RB_EMPTY_ROOT(&child->rb_root); + } + + callchain_node__init_have_children_rb_tree(child); + } +} + +static void callchain_node__init_have_children(struct callchain_node *self) +{ + struct callchain_list *chain; + + list_for_each_entry(chain, &self->val, list) + chain->ms.has_children = !RB_EMPTY_ROOT(&self->rb_root); + + callchain_node__init_have_children_rb_tree(self); +} + +static void callchain__init_have_children(struct rb_root *self) +{ + struct rb_node *nd; + + for (nd = rb_first(self); nd; nd = rb_next(nd)) { + struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + callchain_node__init_have_children(node); + } +} + +static void hist_entry__init_have_children(struct hist_entry *self) +{ + if (!self->init_have_children) { + self->ms.has_children = !RB_EMPTY_ROOT(&self->sorted_chain); + callchain__init_have_children(&self->sorted_chain); + self->init_have_children = true; + } +} + +static bool hist_browser__toggle_fold(struct hist_browser *self) +{ + if (map_symbol__toggle_fold(self->selection)) { + struct hist_entry *he = self->he_selection; + + hist_entry__init_have_children(he); + self->hists->nr_entries -= he->nr_rows; + + if (he->ms.unfolded) + he->nr_rows = callchain__count_rows(&he->sorted_chain); + else + he->nr_rows = 0; + self->hists->nr_entries += he->nr_rows; + self->b.nr_entries = self->hists->nr_entries; + + return true; + } + + /* If it doesn't have children, no toggling performed */ + return false; +} + +static int callchain_node__set_folding_rb_tree(struct callchain_node *self, bool unfold) +{ + int n = 0; + struct rb_node *nd; + + for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { + struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); + struct callchain_list *chain; + bool has_children = false; + + list_for_each_entry(chain, &child->val, list) { + ++n; + map_symbol__set_folding(&chain->ms, unfold); + has_children = chain->ms.has_children; + } + + if (has_children) + n += callchain_node__set_folding_rb_tree(child, unfold); + } + + return n; +} + +static int callchain_node__set_folding(struct callchain_node *node, bool unfold) +{ + struct callchain_list *chain; + bool has_children = false; + int n = 0; + + list_for_each_entry(chain, &node->val, list) { + ++n; + map_symbol__set_folding(&chain->ms, unfold); + has_children = chain->ms.has_children; + } + + if (has_children) + n += callchain_node__set_folding_rb_tree(node, unfold); + + return n; +} + +static int callchain__set_folding(struct rb_root *chain, bool unfold) +{ + struct rb_node *nd; + int n = 0; + + for (nd = rb_first(chain); nd; nd = rb_next(nd)) { + struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + n += callchain_node__set_folding(node, unfold); + } + + return n; +} + +static void hist_entry__set_folding(struct hist_entry *self, bool unfold) +{ + hist_entry__init_have_children(self); + map_symbol__set_folding(&self->ms, unfold); + + if (self->ms.has_children) { + int n = callchain__set_folding(&self->sorted_chain, unfold); + self->nr_rows = unfold ? n : 0; + } else + self->nr_rows = 0; +} + +static void hists__set_folding(struct hists *self, bool unfold) +{ + struct rb_node *nd; + + self->nr_entries = 0; + + for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { + struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); + hist_entry__set_folding(he, unfold); + self->nr_entries += 1 + he->nr_rows; + } +} + +static void hist_browser__set_folding(struct hist_browser *self, bool unfold) +{ + hists__set_folding(self->hists, unfold); + self->b.nr_entries = self->hists->nr_entries; + /* Go to the start, we may be way after valid entries after a collapse */ + ui_browser__reset_index(&self->b); +} + +static int hist_browser__run(struct hist_browser *self, const char *title) +{ + int key; + int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't', + NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT, 0, }; + + self->b.entries = &self->hists->entries; + self->b.nr_entries = self->hists->nr_entries; + + hist_browser__refresh_dimensions(self); + + 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); + + switch (key) { + case 'D': { /* Debug */ + static int seq; + struct hist_entry *h = rb_entry(self->b.top, + struct hist_entry, rb_node); + ui_helpline__pop(); + ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", + seq++, self->b.nr_entries, + self->hists->nr_entries, + self->b.height, + self->b.index, + self->b.top_idx, + h->row_offset, h->nr_rows); + } + break; + case 'C': + /* Collapse the whole world. */ + hist_browser__set_folding(self, false); + break; + case 'E': + /* Expand the whole world. */ + hist_browser__set_folding(self, true); + break; + case NEWT_KEY_ENTER: + if (hist_browser__toggle_fold(self)) + break; + /* fall thru */ + default: + goto out; + } + } +out: + ui_browser__hide(&self->b); + return key; +} + +static char *callchain_list__sym_name(struct callchain_list *self, + char *bf, size_t bfsize) +{ + if (self->ms.sym) + return self->ms.sym->name; + + snprintf(bf, bfsize, "%#Lx", self->ip); + return bf; +} + +#define LEVEL_OFFSET_STEP 3 + +static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, + struct callchain_node *chain_node, + u64 total, int level, + unsigned short row, + off_t *row_offset, + bool *is_current_entry) +{ + struct rb_node *node; + int first_row = row, width, offset = level * LEVEL_OFFSET_STEP; + u64 new_total, remaining; + + if (callchain_param.mode == CHAIN_GRAPH_REL) + new_total = chain_node->children_hit; + else + new_total = total; + + remaining = new_total; + node = rb_first(&chain_node->rb_root); + while (node) { + struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); + struct rb_node *next = rb_next(node); + u64 cumul = cumul_hits(child); + struct callchain_list *chain; + char folded_sign = ' '; + int first = true; + int extra_offset = 0; + + remaining -= cumul; + + list_for_each_entry(chain, &child->val, list) { + char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str; + const char *str; + int color; + bool was_first = first; + + if (first) + first = false; + else + extra_offset = LEVEL_OFFSET_STEP; + + folded_sign = callchain_list__folded(chain); + if (*row_offset != 0) { + --*row_offset; + goto do_next; + } + + alloc_str = NULL; + str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); + if (was_first) { + double percent = cumul * 100.0 / new_total; + + if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) + str = "Not enough memory!"; + else + str = alloc_str; + } + + color = HE_COLORSET_NORMAL; + width = self->b.width - (offset + extra_offset + 2); + if (ui_browser__is_current_entry(&self->b, row)) { + self->selection = &chain->ms; + color = HE_COLORSET_SELECTED; + *is_current_entry = true; + } + + ui_browser__set_color(&self->b, color); + ui_browser__gotorc(&self->b, row, 0); + slsmg_write_nstring(" ", offset + extra_offset); + slsmg_printf("%c ", folded_sign); + slsmg_write_nstring(str, width); + free(alloc_str); + + if (++row == self->b.height) + goto out; +do_next: + if (folded_sign == '+') + break; + } + + if (folded_sign == '-') { + const int new_level = level + (extra_offset ? 2 : 1); + row += hist_browser__show_callchain_node_rb_tree(self, child, new_total, + new_level, row, row_offset, + is_current_entry); + } + if (row == self->b.height) + goto out; + node = next; + } +out: + return row - first_row; +} + +static int hist_browser__show_callchain_node(struct hist_browser *self, + struct callchain_node *node, + int level, unsigned short row, + off_t *row_offset, + bool *is_current_entry) +{ + struct callchain_list *chain; + int first_row = row, + offset = level * LEVEL_OFFSET_STEP, + width = self->b.width - offset; + char folded_sign = ' '; + + list_for_each_entry(chain, &node->val, list) { + char ipstr[BITS_PER_LONG / 4 + 1], *s; + int color; + + folded_sign = callchain_list__folded(chain); + + if (*row_offset != 0) { + --*row_offset; + continue; + } + + color = HE_COLORSET_NORMAL; + if (ui_browser__is_current_entry(&self->b, row)) { + self->selection = &chain->ms; + color = HE_COLORSET_SELECTED; + *is_current_entry = true; + } + + s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); + ui_browser__gotorc(&self->b, row, 0); + ui_browser__set_color(&self->b, color); + slsmg_write_nstring(" ", offset); + slsmg_printf("%c ", folded_sign); + slsmg_write_nstring(s, width - 2); + + if (++row == self->b.height) + goto out; + } + + if (folded_sign == '-') + row += hist_browser__show_callchain_node_rb_tree(self, node, + self->hists->stats.total_period, + level + 1, row, + row_offset, + is_current_entry); +out: + return row - first_row; +} + +static int hist_browser__show_callchain(struct hist_browser *self, + struct rb_root *chain, + int level, unsigned short row, + off_t *row_offset, + bool *is_current_entry) +{ + struct rb_node *nd; + int first_row = row; + + for (nd = rb_first(chain); nd; nd = rb_next(nd)) { + struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + + row += hist_browser__show_callchain_node(self, node, level, + row, row_offset, + is_current_entry); + if (row == self->b.height) + break; + } + + return row - first_row; +} + +static int hist_browser__show_entry(struct hist_browser *self, + struct hist_entry *entry, + unsigned short row) +{ + char s[256]; + double percent; + int printed = 0; + int color, width = self->b.width; + char folded_sign = ' '; + bool current_entry = ui_browser__is_current_entry(&self->b, row); + off_t row_offset = entry->row_offset; + + if (current_entry) { + self->he_selection = entry; + self->selection = &entry->ms; + } + + if (symbol_conf.use_callchain) { + hist_entry__init_have_children(entry); + folded_sign = hist_entry__folded(entry); + } + + if (row_offset == 0) { + hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false, + 0, false, self->hists->stats.total_period); + percent = (entry->period * 100.0) / self->hists->stats.total_period; + + color = HE_COLORSET_SELECTED; + if (!current_entry) { + if (percent >= MIN_RED) + color = HE_COLORSET_TOP; + else if (percent >= MIN_GREEN) + color = HE_COLORSET_MEDIUM; + else + color = HE_COLORSET_NORMAL; + } + + ui_browser__set_color(&self->b, color); + ui_browser__gotorc(&self->b, row, 0); + if (symbol_conf.use_callchain) { + slsmg_printf("%c ", folded_sign); + width -= 2; + } + slsmg_write_nstring(s, width); + ++row; + ++printed; + } else + --row_offset; + + if (folded_sign == '-' && row != self->b.height) { + printed += hist_browser__show_callchain(self, &entry->sorted_chain, + 1, row, &row_offset, + ¤t_entry); + if (current_entry) + self->he_selection = entry; + } + + return printed; +} + +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); + + for (nd = self->top; nd; nd = rb_next(nd)) { + struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); + + if (h->filtered) + continue; + + row += hist_browser__show_entry(hb, h, row); + if (row == self->height) + break; + } + + return row; +} + +static struct rb_node *hists__filter_entries(struct rb_node *nd) +{ + while (nd != NULL) { + struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); + if (!h->filtered) + return nd; + + nd = rb_next(nd); + } + + return NULL; +} + +static struct rb_node *hists__filter_prev_entries(struct rb_node *nd) +{ + while (nd != NULL) { + struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); + if (!h->filtered) + return nd; + + nd = rb_prev(nd); + } + + return NULL; +} + +static void ui_browser__hists_seek(struct ui_browser *self, + off_t offset, int whence) +{ + struct hist_entry *h; + struct rb_node *nd; + bool first = true; + + switch (whence) { + case SEEK_SET: + nd = hists__filter_entries(rb_first(self->entries)); + break; + case SEEK_CUR: + nd = self->top; + goto do_offset; + case SEEK_END: + nd = hists__filter_prev_entries(rb_last(self->entries)); + first = false; + break; + default: + return; + } + + /* + * Moves not relative to the first visible entry invalidates its + * row_offset: + */ + h = rb_entry(self->top, struct hist_entry, rb_node); + h->row_offset = 0; + + /* + * Here we have to check if nd is expanded (+), if it is we can't go + * the next top level hist_entry, instead we must compute an offset of + * what _not_ to show and not change the first visible entry. + * + * This offset increments when we are going from top to bottom and + * decreases when we're going from bottom to top. + * + * As we don't have backpointers to the top level in the callchains + * structure, we need to always print the whole hist_entry callchain, + * skipping the first ones that are before the first visible entry + * and stop when we printed enough lines to fill the screen. + */ +do_offset: + if (offset > 0) { + do { + h = rb_entry(nd, struct hist_entry, rb_node); + if (h->ms.unfolded) { + u16 remaining = h->nr_rows - h->row_offset; + if (offset > remaining) { + offset -= remaining; + h->row_offset = 0; + } else { + h->row_offset += offset; + offset = 0; + self->top = nd; + break; + } + } + nd = hists__filter_entries(rb_next(nd)); + if (nd == NULL) + break; + --offset; + self->top = nd; + } while (offset != 0); + } else if (offset < 0) { + while (1) { + h = rb_entry(nd, struct hist_entry, rb_node); + if (h->ms.unfolded) { + if (first) { + if (-offset > h->row_offset) { + offset += h->row_offset; + h->row_offset = 0; + } else { + h->row_offset += offset; + offset = 0; + self->top = nd; + break; + } + } else { + if (-offset > h->nr_rows) { + offset += h->nr_rows; + h->row_offset = 0; + } else { + h->row_offset = h->nr_rows + offset; + offset = 0; + self->top = nd; + break; + } + } + } + + nd = hists__filter_prev_entries(rb_prev(nd)); + if (nd == NULL) + break; + ++offset; + self->top = nd; + if (offset == 0) { + /* + * Last unfiltered hist_entry, check if it is + * unfolded, if it is then we should have + * row_offset at its last entry. + */ + h = rb_entry(nd, struct hist_entry, rb_node); + if (h->ms.unfolded) + h->row_offset = h->nr_rows; + break; + } + first = false; + } + } else { + self->top = nd; + h = rb_entry(nd, struct hist_entry, rb_node); + h->row_offset = 0; + } +} + +static struct hist_browser *hist_browser__new(struct hists *hists) +{ + struct hist_browser *self = zalloc(sizeof(*self)); + + if (self) { + self->hists = hists; + self->b.refresh = hist_browser__refresh; + self->b.seek = ui_browser__hists_seek; + } + + return self; +} + +static void hist_browser__delete(struct hist_browser *self) +{ + free(self); +} + +static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) +{ + return self->he_selection; +} + +static struct thread *hist_browser__selected_thread(struct hist_browser *self) +{ + return self->he_selection->thread; +} + +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) +{ + char unit; + int printed; + unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE]; + + nr_events = convert_unit(nr_events, &unit); + printed = snprintf(bf, size, "Events: %lu%c %s", nr_events, unit, ev_name); + + if (thread) + printed += snprintf(bf + printed, size - printed, + ", Thread: %s(%d)", + (thread->comm_set ? thread->comm : ""), + thread->pid); + if (dso) + printed += snprintf(bf + printed, size - printed, + ", DSO: %s", dso->short_name); + return printed; +} + +int hists__browse(struct hists *self, const char *helpline, const char *ev_name) +{ + 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) + return -1; + + fstack = pstack__new(2); + if (fstack == NULL) + goto out; + + ui_helpline__push(helpline); + + hists__browser_title(self, msg, sizeof(msg), ev_name, + dso_filter, thread_filter); + while (1) { + const struct thread *thread; + const struct dso *dso; + char *options[16]; + int nr_options = 0, choice = 0, i, + annotate = -2, zoom_dso = -2, zoom_thread = -2, + browse_map = -2; + + key = hist_browser__run(browser, msg); + + thread = hist_browser__selected_thread(browser); + dso = browser->selection->map ? browser->selection->map->dso : NULL; + + switch (key) { + case NEWT_KEY_TAB: + case NEWT_KEY_UNTAB: + /* + * Exit the browser, let hists__browser_tree + * go to the next or previous + */ + goto out_free_stack; + case 'a': + if (browser->selection->map == NULL && + browser->selection->map->dso->annotate_warned) + continue; + goto do_annotate; + case 'd': + goto zoom_dso; + case 't': + goto zoom_thread; + case NEWT_KEY_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" + "q/CTRL+C Exit browser"); + continue; + case NEWT_KEY_ENTER: + case NEWT_KEY_RIGHT: + /* menu */ + break; + case NEWT_KEY_LEFT: { + const void *top; + + if (pstack__empty(fstack)) + continue; + top = pstack__pop(fstack); + if (top == &dso_filter) + goto zoom_out_dso; + if (top == &thread_filter) + goto zoom_out_thread; + continue; + } + case NEWT_KEY_ESCAPE: + if (!ui__dialog_yesno("Do you really want to exit?")) + continue; + /* Fall thru */ + default: + goto out_free_stack; + } + + if (browser->selection->sym != NULL && + !browser->selection->map->dso->annotate_warned && + asprintf(&options[nr_options], "Annotate %s", + browser->selection->sym->name) > 0) + annotate = nr_options++; + + if (thread != NULL && + asprintf(&options[nr_options], "Zoom %s %s(%d) thread", + (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"), + (dso->kernel ? "the Kernel" : dso->short_name)) > 0) + zoom_dso = nr_options++; + + if (browser->selection->map != NULL && + asprintf(&options[nr_options], "Browse map details") > 0) + browse_map = nr_options++; + + options[nr_options++] = (char *)"Exit"; + + choice = ui__popup_menu(nr_options, options); + + for (i = 0; i < nr_options - 1; ++i) + free(options[i]); + + if (choice == nr_options - 1) + break; + + if (choice == -1) + continue; + + if (choice == annotate) { + struct hist_entry *he; +do_annotate: + if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) { + browser->selection->map->dso->annotate_warned = 1; + ui_helpline__puts("No vmlinux file found, can't " + "annotate with just a " + "kallsyms file"); + continue; + } + + he = hist_browser__selected_entry(browser); + if (he == NULL) + continue; + + hist_entry__tui_annotate(he); + } 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); +zoom_out_dso: + ui_helpline__pop(); + dso_filter = NULL; + } 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); + } + hists__filter_by_dso(self, dso_filter); + hists__browser_title(self, msg, sizeof(msg), ev_name, + dso_filter, thread_filter); + hist_browser__reset(browser); + } else if (choice == zoom_thread) { +zoom_thread: + if (thread_filter) { + pstack__remove(fstack, &thread_filter); +zoom_out_thread: + ui_helpline__pop(); + thread_filter = NULL; + } 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); + } + hists__filter_by_thread(self, thread_filter); + hists__browser_title(self, msg, sizeof(msg), ev_name, + dso_filter, thread_filter); + hist_browser__reset(browser); + } + } +out_free_stack: + pstack__delete(fstack); +out: + hist_browser__delete(browser); + return key; +} + +int hists__tui_browse_tree(struct rb_root *self, const char *help) +{ + struct rb_node *first = rb_first(self), *nd = first, *next; + int key = 0; + + while (nd) { + struct hists *hists = rb_entry(nd, struct hists, rb_node); + const char *ev_name = __event_name(hists->type, hists->config); + + key = hists__browse(hists, help, ev_name); + switch (key) { + case NEWT_KEY_TAB: + next = rb_next(nd); + if (next) + nd = next; + break; + case NEWT_KEY_UNTAB: + if (nd == first) + continue; + nd = rb_prev(nd); + default: + return key; + } + } + + return key; +} diff --git a/tools/perf/util/ui/browsers/map.c b/tools/perf/util/ui/browsers/map.c new file mode 100644 index 000000000000..e35437dfa5b4 --- /dev/null +++ b/tools/perf/util/ui/browsers/map.c @@ -0,0 +1,155 @@ +#include "../libslang.h" +#include <elf.h> +#include <sys/ttydefaults.h> +#include <ctype.h> +#include <string.h> +#include <linux/bitops.h> +#include "../../debug.h" +#include "../../symbol.h" +#include "../browser.h" +#include "../helpline.h" +#include "map.h" + +static int ui_entry__read(const char *title, char *bf, size_t size, int width) +{ + struct newtExitStruct es; + newtComponent form, entry; + const char *result; + int err = -1; + + newtCenteredWindow(width, 1, title); + form = newtForm(NULL, NULL, 0); + if (form == NULL) + return -1; + + entry = newtEntry(0, 0, "0x", width, &result, NEWT_FLAG_SCROLL); + if (entry == NULL) + goto out_free_form; + + newtFormAddComponent(form, entry); + newtFormAddHotKey(form, NEWT_KEY_ENTER); + newtFormAddHotKey(form, NEWT_KEY_ESCAPE); + newtFormAddHotKey(form, NEWT_KEY_LEFT); + newtFormAddHotKey(form, CTRL('c')); + newtFormRun(form, &es); + + if (result != NULL) { + strncpy(bf, result, size); + err = 0; + } +out_free_form: + newtPopWindow(); + newtFormDestroy(form); + return 0; +} + +struct map_browser { + struct ui_browser b; + struct map *map; + u8 addrlen; +}; + +static void map_browser__write(struct ui_browser *self, void *nd, int row) +{ + struct symbol *sym = rb_entry(nd, struct symbol, rb_node); + struct map_browser *mb = container_of(self, struct map_browser, b); + bool current_entry = ui_browser__is_current_entry(self, row); + int width; + + ui_browser__set_percent_color(self, 0, current_entry); + slsmg_printf("%*llx %*llx %c ", + mb->addrlen, sym->start, mb->addrlen, sym->end, + sym->binding == STB_GLOBAL ? 'g' : + sym->binding == STB_LOCAL ? 'l' : 'w'); + width = self->width - ((mb->addrlen * 2) + 4); + if (width > 0) + slsmg_write_nstring(sym->name, width); +} + +/* FIXME uber-kludgy, see comment on cmd_report... */ +static u32 *symbol__browser_index(struct symbol *self) +{ + return ((void *)self) - sizeof(struct rb_node) - sizeof(u32); +} + +static int map_browser__search(struct map_browser *self) +{ + char target[512]; + struct symbol *sym; + int err = ui_entry__read("Search by name/addr", target, sizeof(target), 40); + + if (err) + return err; + + if (target[0] == '0' && tolower(target[1]) == 'x') { + u64 addr = strtoull(target, NULL, 16); + sym = map__find_symbol(self->map, addr, NULL); + } else + sym = map__find_symbol_by_name(self->map, target, NULL); + + if (sym != NULL) { + u32 *idx = symbol__browser_index(sym); + + self->b.top = &sym->rb_node; + self->b.index = self->b.top_idx = *idx; + } else + ui_helpline__fpush("%s not found!", target); + + return 0; +} + +static int map_browser__run(struct map_browser *self) +{ + int key; + + if (ui_browser__show(&self->b, self->map->dso->long_name, + "Press <- or ESC to exit, %s / to search", + 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); + + if (verbose && key == '/') + map_browser__search(self); + else + break; + } + + ui_browser__hide(&self->b); + return key; +} + +int map__browse(struct map *self) +{ + struct map_browser mb = { + .b = { + .entries = &self->dso->symbols[self->type], + .refresh = ui_browser__rb_tree_refresh, + .seek = ui_browser__rb_tree_seek, + .write = map_browser__write, + }, + .map = self, + }; + struct rb_node *nd; + char tmp[BITS_PER_LONG / 4]; + u64 maxaddr = 0; + + for (nd = rb_first(mb.b.entries); nd; nd = rb_next(nd)) { + struct symbol *pos = rb_entry(nd, struct symbol, rb_node); + + if (maxaddr < pos->end) + maxaddr = pos->end; + if (verbose) { + u32 *idx = symbol__browser_index(pos); + *idx = mb.b.nr_entries; + } + ++mb.b.nr_entries; + } + + mb.addrlen = snprintf(tmp, sizeof(tmp), "%llx", maxaddr); + return map_browser__run(&mb); +} diff --git a/tools/perf/util/ui/browsers/map.h b/tools/perf/util/ui/browsers/map.h new file mode 100644 index 000000000000..df8581a43e17 --- /dev/null +++ b/tools/perf/util/ui/browsers/map.h @@ -0,0 +1,6 @@ +#ifndef _PERF_UI_MAP_BROWSER_H_ +#define _PERF_UI_MAP_BROWSER_H_ 1 +struct map; + +int map__browse(struct map *self); +#endif /* _PERF_UI_MAP_BROWSER_H_ */ diff --git a/tools/perf/util/ui/helpline.c b/tools/perf/util/ui/helpline.c new file mode 100644 index 000000000000..8d79daa4458a --- /dev/null +++ b/tools/perf/util/ui/helpline.c @@ -0,0 +1,69 @@ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <newt.h> + +#include "../debug.h" +#include "helpline.h" + +void ui_helpline__pop(void) +{ + newtPopHelpLine(); +} + +void ui_helpline__push(const char *msg) +{ + newtPushHelpLine(msg); +} + +void ui_helpline__vpush(const char *fmt, va_list ap) +{ + char *s; + + if (vasprintf(&s, fmt, ap) < 0) + vfprintf(stderr, fmt, ap); + else { + ui_helpline__push(s); + free(s); + } +} + +void ui_helpline__fpush(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + ui_helpline__vpush(fmt, ap); + va_end(ap); +} + +void ui_helpline__puts(const char *msg) +{ + ui_helpline__pop(); + ui_helpline__push(msg); +} + +void ui_helpline__init(void) +{ + ui_helpline__puts(" "); +} + +char ui_helpline__last_msg[1024]; + +int ui_helpline__show_help(const char *format, va_list ap) +{ + int ret; + static int backlog; + + ret = vsnprintf(ui_helpline__last_msg + backlog, + sizeof(ui_helpline__last_msg) - backlog, format, ap); + backlog += ret; + + if (ui_helpline__last_msg[backlog - 1] == '\n') { + ui_helpline__puts(ui_helpline__last_msg); + newtRefresh(); + backlog = 0; + } + + return ret; +} diff --git a/tools/perf/util/ui/helpline.h b/tools/perf/util/ui/helpline.h new file mode 100644 index 000000000000..ab6028d0c401 --- /dev/null +++ b/tools/perf/util/ui/helpline.h @@ -0,0 +1,11 @@ +#ifndef _PERF_UI_HELPLINE_H_ +#define _PERF_UI_HELPLINE_H_ 1 + +void ui_helpline__init(void); +void ui_helpline__pop(void); +void ui_helpline__push(const char *msg); +void ui_helpline__vpush(const char *fmt, va_list ap); +void ui_helpline__fpush(const char *fmt, ...); +void ui_helpline__puts(const char *msg); + +#endif /* _PERF_UI_HELPLINE_H_ */ diff --git a/tools/perf/util/ui/libslang.h b/tools/perf/util/ui/libslang.h new file mode 100644 index 000000000000..5623da8e8080 --- /dev/null +++ b/tools/perf/util/ui/libslang.h @@ -0,0 +1,27 @@ +#ifndef _PERF_UI_SLANG_H_ +#define _PERF_UI_SLANG_H_ 1 +/* + * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks + * the build if it isn't defined. Use the equivalent one that glibc + * has on features.h. + */ +#include <features.h> +#ifndef HAVE_LONG_LONG +#define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG +#endif +#include <slang.h> + +#if SLANG_VERSION < 20104 +#define slsmg_printf(msg, args...) \ + SLsmg_printf((char *)msg, ##args) +#define slsmg_write_nstring(msg, len) \ + SLsmg_write_nstring((char *)msg, len) +#define sltt_set_color(obj, name, fg, bg) \ + SLtt_set_color(obj,(char *)name, (char *)fg, (char *)bg) +#else +#define slsmg_printf SLsmg_printf +#define slsmg_write_nstring SLsmg_write_nstring +#define sltt_set_color SLtt_set_color +#endif + +#endif /* _PERF_UI_SLANG_H_ */ diff --git a/tools/perf/util/ui/progress.c b/tools/perf/util/ui/progress.c new file mode 100644 index 000000000000..d7fc399d36b3 --- /dev/null +++ b/tools/perf/util/ui/progress.c @@ -0,0 +1,60 @@ +#include <stdlib.h> +#include <newt.h> +#include "../cache.h" +#include "progress.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) +{ + /* + * 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); +} diff --git a/tools/perf/util/ui/progress.h b/tools/perf/util/ui/progress.h new file mode 100644 index 000000000000..a3820a0beb5b --- /dev/null +++ b/tools/perf/util/ui/progress.h @@ -0,0 +1,11 @@ +#ifndef _PERF_UI_PROGRESS_H_ +#define _PERF_UI_PROGRESS_H_ 1 + +struct ui_progress; + +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); + +#endif diff --git a/tools/perf/util/ui/setup.c b/tools/perf/util/ui/setup.c new file mode 100644 index 000000000000..662085032eb7 --- /dev/null +++ b/tools/perf/util/ui/setup.c @@ -0,0 +1,42 @@ +#include <newt.h> +#include <signal.h> +#include <stdbool.h> + +#include "../cache.h" +#include "../debug.h" +#include "browser.h" +#include "helpline.h" + +static void newt_suspend(void *d __used) +{ + newtSuspend(); + raise(SIGTSTP); + newtResume(); +} + +void setup_browser(void) +{ + if (!isatty(1) || !use_browser || dump_trace) { + use_browser = 0; + setup_pager(); + return; + } + + use_browser = 1; + newtInit(); + newtCls(); + newtSetSuspendCallback(newt_suspend, NULL); + ui_helpline__init(); + ui_browser__init(); +} + +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(); + } +} diff --git a/tools/perf/util/ui/util.c b/tools/perf/util/ui/util.c new file mode 100644 index 000000000000..9706d9d40279 --- /dev/null +++ b/tools/perf/util/ui/util.c @@ -0,0 +1,112 @@ +#include <newt.h> +#include <signal.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <sys/ttydefaults.h> + +#include "../cache.h" +#include "../debug.h" +#include "browser.h" +#include "helpline.h" +#include "util.h" + +static void newt_form__set_exit_keys(newtComponent self) +{ + newtFormAddHotKey(self, NEWT_KEY_LEFT); + newtFormAddHotKey(self, NEWT_KEY_ESCAPE); + newtFormAddHotKey(self, 'Q'); + newtFormAddHotKey(self, 'q'); + newtFormAddHotKey(self, CTRL('c')); +} + +static newtComponent newt_form__new(void) +{ + newtComponent self = newtForm(NULL, NULL, 0); + if (self) + newt_form__set_exit_keys(self); + return self; +} + +int ui__popup_menu(int argc, char * const argv[]) +{ + struct newtExitStruct es; + int i, rc = -1, max_len = 5; + newtComponent listbox, form = newt_form__new(); + + if (form == NULL) + return -1; + + listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT); + if (listbox == NULL) + goto out_destroy_form; + + newtFormAddComponent(form, listbox); + + for (i = 0; i < argc; ++i) { + int len = strlen(argv[i]); + if (len > max_len) + max_len = len; + if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i)) + goto out_destroy_form; + } + + newtCenteredWindow(max_len, argc, NULL); + newtFormRun(form, &es); + rc = newtListboxGetCurrent(listbox) - NULL; + if (es.reason == NEWT_EXIT_HOTKEY) + rc = -1; + newtPopWindow(); +out_destroy_form: + newtFormDestroy(form); + return rc; +} + +int ui__help_window(const char *text) +{ + struct newtExitStruct es; + newtComponent tb, form = newt_form__new(); + int rc = -1; + int max_len = 0, nr_lines = 0; + const char *t; + + if (form == NULL) + return -1; + + t = text; + while (1) { + const char *sep = strchr(t, '\n'); + int len; + + if (sep == NULL) + sep = strchr(t, '\0'); + len = sep - t; + if (max_len < len) + max_len = len; + ++nr_lines; + if (*sep == '\0') + break; + t = sep + 1; + } + + tb = newtTextbox(0, 0, max_len, nr_lines, 0); + if (tb == NULL) + goto out_destroy_form; + + newtTextboxSetText(tb, text); + newtFormAddComponent(form, tb); + newtCenteredWindow(max_len, nr_lines, NULL); + newtFormRun(form, &es); + newtPopWindow(); + rc = 0; +out_destroy_form: + newtFormDestroy(form); + return rc; +} + +bool ui__dialog_yesno(const char *msg) +{ + /* newtWinChoice should really be accepting const char pointers... */ + char yes[] = "Yes", no[] = "No"; + return newtWinChoice(NULL, yes, no, (char *)msg) == 1; +} diff --git a/tools/perf/util/ui/util.h b/tools/perf/util/ui/util.h new file mode 100644 index 000000000000..afcbc1d99531 --- /dev/null +++ b/tools/perf/util/ui/util.h @@ -0,0 +1,10 @@ +#ifndef _PERF_UI_UTIL_H_ +#define _PERF_UI_UTIL_H_ 1 + +#include <stdbool.h> + +int ui__popup_menu(int argc, char * const argv[]); +int ui__help_window(const char *text); +bool ui__dialog_yesno(const char *msg); + +#endif /* _PERF_UI_UTIL_H_ */ diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index 4e8b6b0c551c..7562707ddd1c 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -89,6 +89,7 @@ extern const char *graph_line; extern const char *graph_dotted_line; +extern char buildid_dir[]; /* On most systems <limits.h> would have given us this, but * not on some systems (e.g. GNU/Hurd). @@ -152,6 +153,8 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))) extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN); extern int prefixcmp(const char *str, const char *prefix); +extern void set_buildid_dir(void); +extern void disable_buildid_cache(void); static inline const char *skip_prefix(const char *str, const char *prefix) { @@ -263,19 +266,6 @@ bool strglobmatch(const char *str, const char *pat); bool strlazymatch(const char *str, const char *pat); unsigned long convert_unit(unsigned long value, char *unit); -#ifndef ESC -#define ESC 27 -#endif - -static inline bool is_exit_key(int key) -{ - char up; - if (key == CTRL('c') || key == ESC) - return true; - up = toupper(key); - return up == 'Q'; -} - #define _STR(x) #x #define STR(x) _STR(x) |