diff options
Diffstat (limited to 'tools/perf')
124 files changed, 7331 insertions, 5196 deletions
diff --git a/tools/perf/.gitignore b/tools/perf/.gitignore index 416684be0ad30..26b823b61aa17 100644 --- a/tools/perf/.gitignore +++ b/tools/perf/.gitignore @@ -19,3 +19,5 @@ TAGS cscope* config.mak config.mak.autogen +*-bison.* +*-flex.* diff --git a/tools/perf/Documentation/Makefile b/tools/perf/Documentation/Makefile index 4626a398836a2..ca600e09c8d47 100644 --- a/tools/perf/Documentation/Makefile +++ b/tools/perf/Documentation/Makefile @@ -1,3 +1,10 @@ +OUTPUT := ./ +ifeq ("$(origin O)", "command line") + ifneq ($(O),) + OUTPUT := $(O)/ + endif +endif + MAN1_TXT= \ $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \ $(wildcard perf-*.txt)) \ @@ -6,10 +13,11 @@ MAN5_TXT= MAN7_TXT= MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT) -MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT)) -MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT)) +_MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT)) +_MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT)) -DOC_HTML=$(MAN_HTML) +MAN_XML=$(addprefix $(OUTPUT),$(_MAN_XML)) +MAN_HTML=$(addprefix $(OUTPUT),$(_MAN_HTML)) ARTICLES = # with their own formatting rules. @@ -18,11 +26,17 @@ API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technica SP_ARTICLES += $(API_DOCS) SP_ARTICLES += technical/api-index -DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES)) +_DOC_HTML = $(_MAN_HTML) +_DOC_HTML+=$(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES)) +DOC_HTML=$(addprefix $(OUTPUT),$(_DOC_HTML)) -DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT)) -DOC_MAN5=$(patsubst %.txt,%.5,$(MAN5_TXT)) -DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT)) +_DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT)) +_DOC_MAN5=$(patsubst %.txt,%.5,$(MAN5_TXT)) +_DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT)) + +DOC_MAN1=$(addprefix $(OUTPUT),$(_DOC_MAN1)) +DOC_MAN5=$(addprefix $(OUTPUT),$(_DOC_MAN5)) +DOC_MAN7=$(addprefix $(OUTPUT),$(_DOC_MAN7)) # Make the path relative to DESTDIR, not prefix ifndef DESTDIR @@ -150,9 +164,9 @@ man1: $(DOC_MAN1) man5: $(DOC_MAN5) man7: $(DOC_MAN7) -info: perf.info perfman.info +info: $(OUTPUT)perf.info $(OUTPUT)perfman.info -pdf: user-manual.pdf +pdf: $(OUTPUT)user-manual.pdf install: install-man @@ -166,7 +180,7 @@ install-man: man install-info: info $(INSTALL) -d -m 755 $(DESTDIR)$(infodir) - $(INSTALL) -m 644 perf.info perfman.info $(DESTDIR)$(infodir) + $(INSTALL) -m 644 $(OUTPUT)perf.info $(OUTPUT)perfman.info $(DESTDIR)$(infodir) if test -r $(DESTDIR)$(infodir)/dir; then \ $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) perf.info ;\ $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) perfman.info ;\ @@ -176,7 +190,7 @@ install-info: info install-pdf: pdf $(INSTALL) -d -m 755 $(DESTDIR)$(pdfdir) - $(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir) + $(INSTALL) -m 644 $(OUTPUT)user-manual.pdf $(DESTDIR)$(pdfdir) #install-html: html # '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir) @@ -189,14 +203,14 @@ install-pdf: pdf # # Determine "include::" file references in asciidoc files. # -doc.dep : $(wildcard *.txt) build-docdep.perl +$(OUTPUT)doc.dep : $(wildcard *.txt) build-docdep.perl $(QUIET_GEN)$(RM) $@+ $@ && \ $(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \ mv $@+ $@ --include doc.dep +-include $(OUPTUT)doc.dep -cmds_txt = cmds-ancillaryinterrogators.txt \ +_cmds_txt = cmds-ancillaryinterrogators.txt \ cmds-ancillarymanipulators.txt \ cmds-mainporcelain.txt \ cmds-plumbinginterrogators.txt \ @@ -205,32 +219,36 @@ cmds_txt = cmds-ancillaryinterrogators.txt \ cmds-synchelpers.txt \ cmds-purehelpers.txt \ cmds-foreignscminterface.txt +cmds_txt=$(addprefix $(OUTPUT),$(_cmds_txt)) -$(cmds_txt): cmd-list.made +$(cmds_txt): $(OUTPUT)cmd-list.made -cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT) +$(OUTPUT)cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT) $(QUIET_GEN)$(RM) $@ && \ $(PERL_PATH) ./cmd-list.perl ../command-list.txt $(QUIET_STDERR) && \ date >$@ clean: - $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 - $(RM) *.texi *.texi+ *.texi++ perf.info perfman.info - $(RM) howto-index.txt howto/*.html doc.dep - $(RM) technical/api-*.html technical/api-index.txt - $(RM) $(cmds_txt) *.made - -$(MAN_HTML): %.html : %.txt + $(RM) $(MAN_XML) $(addsuffix +,$(MAN_XML)) + $(RM) $(MAN_HTML) $(addsuffix +,$(MAN_HTML)) + $(RM) $(DOC_HTML) $(DOC_MAN1) $(DOC_MAN5) $(DOC_MAN7) + $(RM) $(OUTPUT)*.texi $(OUTPUT)*.texi+ $(OUTPUT)*.texi++ + $(RM) $(OUTPUT)perf.info $(OUTPUT)perfman.info + $(RM) $(OUTPUT)howto-index.txt $(OUTPUT)howto/*.html $(OUTPUT)doc.dep + $(RM) $(OUTPUT)technical/api-*.html $(OUTPUT)technical/api-index.txt + $(RM) $(cmds_txt) $(OUTPUT)*.made + +$(MAN_HTML): $(OUTPUT)%.html : %.txt $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -aperf_version=$(PERF_VERSION) -o $@+ $< && \ mv $@+ $@ -%.1 %.5 %.7 : %.xml +$(OUTPUT)%.1 $(OUTPUT)%.5 $(OUTPUT)%.7 : $(OUTPUT)%.xml $(QUIET_XMLTO)$(RM) $@ && \ - xmlto -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $< + xmlto -o $(OUTPUT) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $< -%.xml : %.txt +$(OUTPUT)%.xml : %.txt $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -aperf_version=$(PERF_VERSION) -o $@+ $< && \ @@ -239,25 +257,25 @@ $(MAN_HTML): %.html : %.txt XSLT = docbook.xsl XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css -user-manual.html: user-manual.xml +$(OUTPUT)user-manual.html: $(OUTPUT)user-manual.xml $(QUIET_XSLTPROC)xsltproc $(XSLTOPTS) -o $@ $(XSLT) $< -perf.info: user-manual.texi - $(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi +$(OUTPUT)perf.info: $(OUTPUT)user-manual.texi + $(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ $(OUTPUT)user-manual.texi -user-manual.texi: user-manual.xml +$(OUTPUT)user-manual.texi: $(OUTPUT)user-manual.xml $(QUIET_DB2TEXI)$(RM) $@+ $@ && \ - $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \ + $(DOCBOOK2X_TEXI) $(OUTPUT)user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \ $(PERL_PATH) fix-texi.perl <$@++ >$@+ && \ rm $@++ && \ mv $@+ $@ -user-manual.pdf: user-manual.xml +$(OUTPUT)user-manual.pdf: $(OUTPUT)user-manual.xml $(QUIET_DBLATEX)$(RM) $@+ $@ && \ $(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $< && \ mv $@+ $@ -perfman.texi: $(MAN_XML) cat-texi.perl +$(OUTPUT)perfman.texi: $(MAN_XML) cat-texi.perl $(QUIET_DB2TEXI)$(RM) $@+ $@ && \ ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \ --to-stdout $(xml) &&) true) > $@++ && \ @@ -265,7 +283,7 @@ perfman.texi: $(MAN_XML) cat-texi.perl rm $@++ && \ mv $@+ $@ -perfman.info: perfman.texi +$(OUTPUT)perfman.info: $(OUTPUT)perfman.texi $(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml diff --git a/tools/perf/Documentation/perf-lock.txt b/tools/perf/Documentation/perf-lock.txt index d6b2a4f2108bf..c7f5f55634ac3 100644 --- a/tools/perf/Documentation/perf-lock.txt +++ b/tools/perf/Documentation/perf-lock.txt @@ -8,7 +8,7 @@ perf-lock - Analyze lock events SYNOPSIS -------- [verse] -'perf lock' {record|report|trace} +'perf lock' {record|report|script|info} DESCRIPTION ----------- @@ -20,10 +20,13 @@ and statistics with this 'perf lock' command. produces the file "perf.data" which contains tracing results of lock events. - 'perf lock trace' shows raw lock events. - 'perf lock report' reports statistical data. + 'perf lock script' shows raw lock events. + + 'perf lock info' shows metadata like threads or addresses + of lock instances. + COMMON OPTIONS -------------- @@ -47,6 +50,17 @@ REPORT OPTIONS Sorting key. Possible values: acquired (default), contended, wait_total, wait_max, wait_min. +INFO OPTIONS +------------ + +-t:: +--threads:: + dump thread list in perf.data + +-m:: +--map:: + dump map of lock instances (address:name table) + SEE ALSO -------- linkperf:perf[1] diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt index 2937f7e14bb74..a1386b2fff00f 100644 --- a/tools/perf/Documentation/perf-record.txt +++ b/tools/perf/Documentation/perf-record.txt @@ -52,11 +52,15 @@ OPTIONS -p:: --pid=:: - Record events on existing process ID. + Record events on existing process ID (comma separated list). -t:: --tid=:: - Record events on existing thread ID. + Record events on existing thread ID (comma separated list). + +-u:: +--uid=:: + Record events in threads owned by uid. Name or number. -r:: --realtime=:: @@ -148,6 +152,36 @@ an empty cgroup (monitor all the time) using, e.g., -G foo,,bar. Cgroups must ha corresponding events, i.e., they always refer to events defined earlier on the command line. +-b:: +--branch-any:: +Enable taken branch stack sampling. Any type of taken branch may be sampled. +This is a shortcut for --branch-filter any. See --branch-filter for more infos. + +-j:: +--branch-filter:: +Enable taken branch stack sampling. Each sample captures a series of consecutive +taken branches. The number of branches captured with each sample depends on the +underlying hardware, the type of branches of interest, and the executed code. +It is possible to select the types of branches captured by enabling filters. The +following filters are defined: + + - any: any type of branches + - any_call: any function call or system call + - any_ret: any function return or system call return + - any_ind: any indirect branch + - u: only when the branch target is at the user level + - k: only when the branch target is in the kernel + - hv: only when the target is at the hypervisor level + ++ +The option requires at least one branch type among any, any_call, any_ret, ind_call. +The privilege levels may be ommitted, in which case, the privilege levels of the associated +event are applied to the branch filter. Both kernel (k) and hypervisor (hv) privilege +levels are subject to permissions. When sampling on multiple events, branch stack sampling +is enabled for all the sampling events. The sampled branch type is the same for all events. +The various filters must be specified as a comma separated list: --branch-filter any_ret,u,k +Note that this feature may not be available on all processors. + 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 9b430e98712e9..2d89f02719b5f 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -48,6 +48,9 @@ OPTIONS Only consider these symbols. CSV that understands file://filename entries. +--symbol-filter=:: + Only show symbols that match (partially) with this filter. + -U:: --hide-unresolved:: Only display entries resolved to a symbol. @@ -110,6 +113,8 @@ OPTIONS requires a tty, if one is not present, as when piping to other commands, the stdio interface is used. +--gtk:: Use the GTK2 interface. + -k:: --vmlinux=<file>:: vmlinux pathname @@ -153,6 +158,16 @@ OPTIONS information which may be very large and thus may clutter the display. It currently includes: cpu and numa topology of the host system. +-b:: +--branch-stack:: + Use the addresses of sampled taken branches instead of the instruction + address to build the histograms. To generate meaningful output, the + perf.data file must have been obtained using perf record -b or + perf record --branch-filter xxx where xxx is a branch filter option. + perf report is able to auto-detect whether a perf.data file contains + branch stacks and it will automatically switch to the branch view mode, + unless --no-branch-stack is used. + SEE ALSO -------- linkperf:perf-stat[1], linkperf:perf-annotate[1] diff --git a/tools/perf/Documentation/perf-script.txt b/tools/perf/Documentation/perf-script.txt index 2f6cef43da250..e9cbfcddfa3f6 100644 --- a/tools/perf/Documentation/perf-script.txt +++ b/tools/perf/Documentation/perf-script.txt @@ -115,7 +115,7 @@ OPTIONS -f:: --fields:: Comma separated list of fields to print. Options are: - comm, tid, pid, time, cpu, event, trace, ip, sym, dso, addr. + comm, tid, pid, time, cpu, event, trace, ip, sym, dso, addr, symoff. Field list can be prepended with the type, trace, sw or hw, to indicate to which event type the field list applies. e.g., -f sw:comm,tid,time,ip,sym and -f trace:time,cpu,trace @@ -200,6 +200,9 @@ OPTIONS It currently includes: cpu and numa topology of the host system. It can only be used with the perf script report mode. +--show-kernel-path:: + Try to resolve the path of [kernel.kallsyms] + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-script-perl[1], diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt index 8966b9ab20144..2fa173b519706 100644 --- a/tools/perf/Documentation/perf-stat.txt +++ b/tools/perf/Documentation/perf-stat.txt @@ -35,11 +35,11 @@ OPTIONS child tasks do not inherit counters -p:: --pid=<pid>:: - stat events on existing process id + stat events on existing process id (comma separated list) -t:: --tid=<tid>:: - stat events on existing thread id + stat events on existing thread id (comma separated list) -a:: diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt index b1a5bbbfebef9..4a5680cb242eb 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -72,11 +72,15 @@ Default is to monitor all CPUS. -p <pid>:: --pid=<pid>:: - Profile events on existing Process ID. + Profile events on existing Process ID (comma separated list). -t <tid>:: --tid=<tid>:: - Profile events on existing thread ID. + Profile events on existing thread ID (comma separated list). + +-u:: +--uid=:: + Record events in threads owned by uid. Name or number. -r <priority>:: --realtime=<priority>:: diff --git a/tools/perf/Documentation/perfconfig.example b/tools/perf/Documentation/perfconfig.example index d1448668f4d4f..42c6fd2ae85d1 100644 --- a/tools/perf/Documentation/perfconfig.example +++ b/tools/perf/Documentation/perfconfig.example @@ -6,6 +6,7 @@ normal = black, lightgray selected = lightgray, magenta code = blue, lightgray + addr = magenta, lightgray [tui] diff --git a/tools/perf/MANIFEST b/tools/perf/MANIFEST index 1078c5fadd5bf..5476bc0a1eac8 100644 --- a/tools/perf/MANIFEST +++ b/tools/perf/MANIFEST @@ -9,6 +9,7 @@ lib/rbtree.c include/linux/swab.h arch/*/include/asm/unistd*.h arch/*/lib/memcpy*.S +arch/*/lib/memset*.S include/linux/poison.h include/linux/magic.h include/linux/hw_breakpoint.h diff --git a/tools/perf/Makefile b/tools/perf/Makefile index 7c12650165aed..fa37cd53e9b9f 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -1,20 +1,22 @@ -ifeq ("$(origin O)", "command line") - OUTPUT := $(O)/ -endif +include ../scripts/Makefile.include # The default target of this Makefile is... all: include config/utilities.mak -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 to have a more verbose compile. # +# Define O to save output files in a separate directory. +# +# Define ARCH as name of target architecture if you want cross-builds. +# +# Define CROSS_COMPILE as prefix name of compiler if you want cross-builds. +# +# Define NO_LIBPERL to disable perl script extension. +# +# Define NO_LIBPYTHON to disable python script extension. +# # Define PYTHON to point to the python binary if the default # `python' is not correct; for example: PYTHON=python2 # @@ -32,6 +34,10 @@ endif # Define NO_DWARF if you do not want debug-info analysis feature at all. # # Define WERROR=0 to disable treating any warnings as errors. +# +# Define NO_NEWT if you do not want TUI support. +# +# Define NO_DEMANGLE if you do not want C++ symbol demangling. $(OUTPUT)PERF-VERSION-FILE: .FORCE-PERF-VERSION-FILE @$(SHELL_PATH) util/PERF-VERSION-GEN $(OUTPUT) @@ -61,7 +67,7 @@ ifeq ($(ARCH),x86_64) ifeq (${IS_X86_64}, 1) RAW_ARCH := x86_64 ARCH_CFLAGS := -DARCH_X86_64 - ARCH_INCLUDE = ../../arch/x86/lib/memcpy_64.S + ARCH_INCLUDE = ../../arch/x86/lib/memcpy_64.S ../../arch/x86/lib/memset_64.S endif endif @@ -70,31 +76,6 @@ ifneq ($(WERROR),0) CFLAGS_WERROR := -Werror endif -# -# Include saner warnings here, which can catch bugs: -# - -EXTRA_WARNINGS := -Wformat -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wformat-security -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wformat-y2k -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wshadow -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Winit-self -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wpacked -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wredundant-decls -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wstrict-aliasing=3 -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wswitch-default -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wswitch-enum -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wno-system-headers -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wundef -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wwrite-strings -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wbad-function-cast -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wmissing-declarations -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wmissing-prototypes -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wnested-externs -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wold-style-definition -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wstrict-prototypes -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wdeclaration-after-statement - ifeq ("$(origin DEBUG)", "command line") PERF_DEBUG = $(DEBUG) endif @@ -168,7 +149,7 @@ endif ### --- END CONFIGURATION SECTION --- -BASIC_CFLAGS = -Iutil/include -Iarch/$(ARCH)/include -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE +BASIC_CFLAGS = -Iutil/include -Iarch/$(ARCH)/include -I$(OUTPUT)/util -I$(EVENT_PARSE_DIR) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE BASIC_LDFLAGS = # Guard against environment variables @@ -183,7 +164,10 @@ SCRIPT_SH += perf-archive.sh grep-libs = $(filter -l%,$(1)) strip-libs = $(filter-out -l%,$(1)) -$(OUTPUT)python/perf.so: $(PYRF_OBJS) +PYTHON_EXT_SRCS := $(shell grep -v ^\# util/python-ext-sources) +PYTHON_EXT_DEPS := util/python-ext-sources util/setup.py + +$(OUTPUT)python/perf.so: $(PYRF_OBJS) $(PYTHON_EXT_SRCS) $(PYTHON_EXT_DEPS) $(QUIET_GEN)CFLAGS='$(BASIC_CFLAGS)' $(PYTHON_WORD) util/setup.py \ --quiet build_ext; \ mkdir -p $(OUTPUT)python && \ @@ -194,6 +178,17 @@ $(OUTPUT)python/perf.so: $(PYRF_OBJS) SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) +EVENT_PARSE_DIR = ../lib/traceevent/ + +ifeq ("$(origin O)", "command line") + EP_PATH=$(OUTPUT)/ +else + EP_PATH=$(EVENT_PARSE_DIR)/ +endif + +LIBPARSEVENT = $(EP_PATH)libtraceevent.a +EP_LIB := -L$(EP_PATH) -ltraceevent + # # Single 'perf' binary right now: # @@ -217,6 +212,24 @@ endif export PERL_PATH +FLEX = flex +BISON= bison + +$(OUTPUT)util/parse-events-flex.c: util/parse-events.l + $(QUIET_FLEX)$(FLEX) --header-file=$(OUTPUT)util/parse-events-flex.h -t util/parse-events.l > $(OUTPUT)util/parse-events-flex.c + +$(OUTPUT)util/parse-events-bison.c: util/parse-events.y + $(QUIET_BISON)$(BISON) -v util/parse-events.y -d -o $(OUTPUT)util/parse-events-bison.c + +$(OUTPUT)util/pmu-flex.c: util/pmu.l + $(QUIET_FLEX)$(FLEX) --header-file=$(OUTPUT)util/pmu-flex.h -t util/pmu.l > $(OUTPUT)util/pmu-flex.c + +$(OUTPUT)util/pmu-bison.c: util/pmu.y + $(QUIET_BISON)$(BISON) -v util/pmu.y -d -o $(OUTPUT)util/pmu-bison.c + +$(OUTPUT)util/parse-events.o: $(OUTPUT)util/parse-events-flex.c $(OUTPUT)util/parse-events-bison.c +$(OUTPUT)util/pmu.o: $(OUTPUT)util/pmu-flex.c $(OUTPUT)util/pmu-bison.c + LIB_FILE=$(OUTPUT)libperf.a LIB_H += ../../include/linux/perf_event.h @@ -232,7 +245,7 @@ LIB_H += util/include/linux/const.h LIB_H += util/include/linux/ctype.h LIB_H += util/include/linux/kernel.h LIB_H += util/include/linux/list.h -LIB_H += util/include/linux/module.h +LIB_H += util/include/linux/export.h LIB_H += util/include/linux/poison.h LIB_H += util/include/linux/prefetch.h LIB_H += util/include/linux/rbtree.h @@ -249,6 +262,8 @@ LIB_H += util/include/asm/uaccess.h LIB_H += util/include/dwarf-regs.h LIB_H += util/include/asm/dwarf2.h LIB_H += util/include/asm/cpufeature.h +LIB_H += util/include/asm/unistd_32.h +LIB_H += util/include/asm/unistd_64.h LIB_H += perf.h LIB_H += util/annotate.h LIB_H += util/cache.h @@ -256,6 +271,8 @@ LIB_H += util/callchain.h LIB_H += util/build-id.h LIB_H += util/debug.h LIB_H += util/debugfs.h +LIB_H += util/sysfs.h +LIB_H += util/pmu.h LIB_H += util/event.h LIB_H += util/evsel.h LIB_H += util/evlist.h @@ -294,6 +311,8 @@ LIB_H += util/cpumap.h LIB_H += util/top.h LIB_H += $(ARCH_INCLUDE) LIB_H += util/cgroup.h +LIB_H += $(EVENT_PARSE_DIR)event-parse.h +LIB_H += util/target.h LIB_OBJS += $(OUTPUT)util/abspath.o LIB_OBJS += $(OUTPUT)util/alias.o @@ -302,6 +321,8 @@ LIB_OBJS += $(OUTPUT)util/build-id.o LIB_OBJS += $(OUTPUT)util/config.o LIB_OBJS += $(OUTPUT)util/ctype.o LIB_OBJS += $(OUTPUT)util/debugfs.o +LIB_OBJS += $(OUTPUT)util/sysfs.o +LIB_OBJS += $(OUTPUT)util/pmu.o LIB_OBJS += $(OUTPUT)util/environment.o LIB_OBJS += $(OUTPUT)util/event.o LIB_OBJS += $(OUTPUT)util/evlist.o @@ -338,6 +359,10 @@ LIB_OBJS += $(OUTPUT)util/session.o LIB_OBJS += $(OUTPUT)util/thread.o LIB_OBJS += $(OUTPUT)util/thread_map.o LIB_OBJS += $(OUTPUT)util/trace-event-parse.o +LIB_OBJS += $(OUTPUT)util/parse-events-flex.o +LIB_OBJS += $(OUTPUT)util/parse-events-bison.o +LIB_OBJS += $(OUTPUT)util/pmu-flex.o +LIB_OBJS += $(OUTPUT)util/pmu-bison.o LIB_OBJS += $(OUTPUT)util/trace-event-read.o LIB_OBJS += $(OUTPUT)util/trace-event-info.o LIB_OBJS += $(OUTPUT)util/trace-event-scripting.o @@ -349,6 +374,7 @@ LIB_OBJS += $(OUTPUT)util/util.o LIB_OBJS += $(OUTPUT)util/xyarray.o LIB_OBJS += $(OUTPUT)util/cpumap.o LIB_OBJS += $(OUTPUT)util/cgroup.o +LIB_OBJS += $(OUTPUT)util/target.o BUILTIN_OBJS += $(OUTPUT)builtin-annotate.o @@ -359,8 +385,10 @@ BUILTIN_OBJS += $(OUTPUT)bench/sched-messaging.o BUILTIN_OBJS += $(OUTPUT)bench/sched-pipe.o ifeq ($(RAW_ARCH),x86_64) BUILTIN_OBJS += $(OUTPUT)bench/mem-memcpy-x86-64-asm.o +BUILTIN_OBJS += $(OUTPUT)bench/mem-memset-x86-64-asm.o endif BUILTIN_OBJS += $(OUTPUT)bench/mem-memcpy.o +BUILTIN_OBJS += $(OUTPUT)bench/mem-memset.o BUILTIN_OBJS += $(OUTPUT)builtin-diff.o BUILTIN_OBJS += $(OUTPUT)builtin-evlist.o @@ -382,7 +410,7 @@ BUILTIN_OBJS += $(OUTPUT)builtin-kvm.o BUILTIN_OBJS += $(OUTPUT)builtin-test.o BUILTIN_OBJS += $(OUTPUT)builtin-inject.o -PERFLIBS = $(LIB_FILE) +PERFLIBS = $(LIB_FILE) $(LIBPARSEVENT) # Files needed for the python binding, perf.so # pyrf is just an internal name needed for all those wrappers. @@ -459,22 +487,42 @@ 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/keysyms.h - LIB_H += util/ui/libslang.h - LIB_H += util/ui/progress.h - LIB_H += util/ui/util.h - LIB_H += util/ui/ui.h + LIB_OBJS += $(OUTPUT)ui/setup.o + LIB_OBJS += $(OUTPUT)ui/browser.o + LIB_OBJS += $(OUTPUT)ui/browsers/annotate.o + LIB_OBJS += $(OUTPUT)ui/browsers/hists.o + LIB_OBJS += $(OUTPUT)ui/browsers/map.o + LIB_OBJS += $(OUTPUT)ui/helpline.o + LIB_OBJS += $(OUTPUT)ui/progress.o + LIB_OBJS += $(OUTPUT)ui/util.o + LIB_OBJS += $(OUTPUT)ui/tui/setup.o + LIB_H += ui/browser.h + LIB_H += ui/browsers/map.h + LIB_H += ui/helpline.h + LIB_H += ui/keysyms.h + LIB_H += ui/libslang.h + LIB_H += ui/progress.h + LIB_H += ui/util.h + LIB_H += ui/ui.h + endif +endif + +ifdef NO_GTK2 + BASIC_CFLAGS += -DNO_GTK2_SUPPORT +else + FLAGS_GTK2=$(ALL_CFLAGS) $(ALL_LDFLAGS) $(EXTLIBS) $(shell pkg-config --libs --cflags gtk+-2.0) + ifneq ($(call try-cc,$(SOURCE_GTK2),$(FLAGS_GTK2)),y) + msg := $(warning GTK2 not found, disables GTK2 support. Please install gtk2-devel or libgtk2.0-dev); + BASIC_CFLAGS += -DNO_GTK2_SUPPORT + else + BASIC_CFLAGS += $(shell pkg-config --cflags gtk+-2.0) + EXTLIBS += $(shell pkg-config --libs gtk+-2.0) + LIB_OBJS += $(OUTPUT)ui/gtk/browser.o + LIB_OBJS += $(OUTPUT)ui/gtk/setup.o + # Make sure that it'd be included only once. + ifneq ($(findstring -DNO_NEWT_SUPPORT,$(BASIC_CFLAGS)),) + LIB_OBJS += $(OUTPUT)ui/setup.o + endif endif endif @@ -617,16 +665,6 @@ else endif endif -ifneq ($(findstring $(MAKEFLAGS),s),s) -ifndef V - QUIET_CC = @echo ' ' CC $@; - QUIET_AR = @echo ' ' AR $@; - QUIET_LINK = @echo ' ' LINK $@; - QUIET_MKDIR = @echo ' ' MKDIR $@; - QUIET_GEN = @echo ' ' GEN $@; -endif -endif - ifdef ASCIIDOC8 export ASCIIDOC8 endif @@ -704,12 +742,28 @@ $(OUTPUT)perf.o perf.spec \ $(SCRIPTS) \ : $(OUTPUT)PERF-VERSION-FILE +.SUFFIXES: +.SUFFIXES: .o .c .S .s + +# These two need to be here so that when O= is not used they take precedence +# over the general rule for .o + +$(OUTPUT)util/%-flex.o: $(OUTPUT)util/%-flex.c $(OUTPUT)PERF-CFLAGS + $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -Iutil/ -w $< + +$(OUTPUT)util/%-bison.o: $(OUTPUT)util/%-bison.c $(OUTPUT)PERF-CFLAGS + $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DYYENABLE_NLS=0 -DYYLTYPE_IS_TRIVIAL=0 -Iutil/ -w $< + $(OUTPUT)%.o: %.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) $< +$(OUTPUT)%.i: %.c $(OUTPUT)PERF-CFLAGS + $(QUIET_CC)$(CC) -o $@ -E $(ALL_CFLAGS) $< $(OUTPUT)%.s: %.c $(OUTPUT)PERF-CFLAGS - $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $< + $(QUIET_CC)$(CC) -o $@ -S $(ALL_CFLAGS) $< $(OUTPUT)%.o: %.S $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) $< +$(OUTPUT)%.s: %.S + $(QUIET_CC)$(CC) -o $@ -E $(ALL_CFLAGS) $< $(OUTPUT)util/exec_cmd.o: util/exec_cmd.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) \ @@ -721,16 +775,16 @@ $(OUTPUT)util/exec_cmd.o: util/exec_cmd.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/ui/browser.o: util/ui/browser.c $(OUTPUT)PERF-CFLAGS +$(OUTPUT)ui/browser.o: 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 +$(OUTPUT)ui/browsers/annotate.o: 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 +$(OUTPUT)ui/browsers/hists.o: 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 +$(OUTPUT)ui/browsers/map.o: 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 @@ -765,6 +819,10 @@ $(sort $(dir $(DIRECTORY_DEPS))): $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) +# libparsevent.a +$(LIBPARSEVENT): + make -C $(EVENT_PARSE_DIR) $(COMMAND_O) libtraceevent.a + help: @echo 'Perf make targets:' @echo ' doc - make *all* documentation (see below)' @@ -792,7 +850,6 @@ help: @echo ' quick-install-html - install the html documentation quickly' @echo '' @echo 'Perf maintainer targets:' - @echo ' distclean - alias to clean' @echo ' clean - clean all binary objects and build output' doc: @@ -909,6 +966,7 @@ clean: $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo $(OUTPUT)common-cmds.h TAGS tags cscope* $(MAKE) -C Documentation/ clean $(RM) $(OUTPUT)PERF-VERSION-FILE $(OUTPUT)PERF-CFLAGS + $(RM) $(OUTPUT)util/*-{bison,flex}* $(python-clean) .PHONY: all install clean strip diff --git a/tools/perf/arch/powerpc/util/header.c b/tools/perf/arch/powerpc/util/header.c index eba80c292945b..2f7073d107fd1 100644 --- a/tools/perf/arch/powerpc/util/header.c +++ b/tools/perf/arch/powerpc/util/header.c @@ -25,7 +25,7 @@ get_cpuid(char *buffer, size_t sz) pvr = mfspr(SPRN_PVR); - nb = snprintf(buffer, sz, "%lu,%lu$", PVR_VER(pvr), PVR_REV(pvr)); + nb = scnprintf(buffer, sz, "%lu,%lu$", PVR_VER(pvr), PVR_REV(pvr)); /* look for end marker to ensure the entire data fit */ if (strchr(buffer, '$')) { diff --git a/tools/perf/arch/x86/util/header.c b/tools/perf/arch/x86/util/header.c index f94006068d2b5..146d12a1cec0a 100644 --- a/tools/perf/arch/x86/util/header.c +++ b/tools/perf/arch/x86/util/header.c @@ -48,7 +48,7 @@ get_cpuid(char *buffer, size_t sz) if (family >= 0x6) model += ((a >> 16) & 0xf) << 4; } - nb = snprintf(buffer, sz, "%s,%u,%u,%u$", vendor, family, model, step); + nb = scnprintf(buffer, sz, "%s,%u,%u,%u$", vendor, family, model, step); /* look for end marker to ensure the entire data fit */ if (strchr(buffer, '$')) { diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h index f7781c6267c06..a09bece6dad2f 100644 --- a/tools/perf/bench/bench.h +++ b/tools/perf/bench/bench.h @@ -4,6 +4,7 @@ extern int bench_sched_messaging(int argc, const char **argv, const char *prefix); extern int bench_sched_pipe(int argc, const char **argv, const char *prefix); extern int bench_mem_memcpy(int argc, const char **argv, const char *prefix __used); +extern int bench_mem_memset(int argc, const char **argv, const char *prefix); #define BENCH_FORMAT_DEFAULT_STR "default" #define BENCH_FORMAT_DEFAULT 0 diff --git a/tools/perf/bench/mem-memcpy-x86-64-asm-def.h b/tools/perf/bench/mem-memcpy-x86-64-asm-def.h index d588b87696fcb..d66ab799b35fd 100644 --- a/tools/perf/bench/mem-memcpy-x86-64-asm-def.h +++ b/tools/perf/bench/mem-memcpy-x86-64-asm-def.h @@ -2,3 +2,11 @@ MEMCPY_FN(__memcpy, "x86-64-unrolled", "unrolled memcpy() in arch/x86/lib/memcpy_64.S") + +MEMCPY_FN(memcpy_c, + "x86-64-movsq", + "movsq-based memcpy() in arch/x86/lib/memcpy_64.S") + +MEMCPY_FN(memcpy_c_e, + "x86-64-movsb", + "movsb-based memcpy() in arch/x86/lib/memcpy_64.S") diff --git a/tools/perf/bench/mem-memcpy-x86-64-asm.S b/tools/perf/bench/mem-memcpy-x86-64-asm.S index 185a96d66dd18..fcd9cf00600a9 100644 --- a/tools/perf/bench/mem-memcpy-x86-64-asm.S +++ b/tools/perf/bench/mem-memcpy-x86-64-asm.S @@ -1,4 +1,8 @@ - +#define memcpy MEMCPY /* don't hide glibc's memcpy() */ +#define altinstr_replacement text +#define globl p2align 4; .globl +#define Lmemcpy_c globl memcpy_c; memcpy_c +#define Lmemcpy_c_e globl memcpy_c_e; memcpy_c_e #include "../../../arch/x86/lib/memcpy_64.S" /* * We need to provide note.GNU-stack section, saying that we want diff --git a/tools/perf/bench/mem-memcpy.c b/tools/perf/bench/mem-memcpy.c index db82021f4b91c..71557225bf92a 100644 --- a/tools/perf/bench/mem-memcpy.c +++ b/tools/perf/bench/mem-memcpy.c @@ -5,7 +5,6 @@ * * Written by Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp> */ -#include <ctype.h> #include "../perf.h" #include "../util/util.h" @@ -24,6 +23,7 @@ static const char *length_str = "1MB"; static const char *routine = "default"; +static int iterations = 1; static bool use_clock; static int clock_fd; static bool only_prefault; @@ -35,6 +35,8 @@ static const struct option options[] = { "available unit: B, MB, GB (upper and lower)"), OPT_STRING('r', "routine", &routine, "default", "Specify routine to copy"), + OPT_INTEGER('i', "iterations", &iterations, + "repeat memcpy() invocation this number of times"), OPT_BOOLEAN('c', "clock", &use_clock, "Use CPU clock for measuring"), OPT_BOOLEAN('o', "only-prefault", &only_prefault, @@ -121,6 +123,7 @@ static u64 do_memcpy_clock(memcpy_t fn, size_t len, bool prefault) { u64 clock_start = 0ULL, clock_end = 0ULL; void *src = NULL, *dst = NULL; + int i; alloc_mem(&src, &dst, len); @@ -128,7 +131,8 @@ static u64 do_memcpy_clock(memcpy_t fn, size_t len, bool prefault) fn(dst, src, len); clock_start = get_clock(); - fn(dst, src, len); + for (i = 0; i < iterations; ++i) + fn(dst, src, len); clock_end = get_clock(); free(src); @@ -140,6 +144,7 @@ static double do_memcpy_gettimeofday(memcpy_t fn, size_t len, bool prefault) { struct timeval tv_start, tv_end, tv_diff; void *src = NULL, *dst = NULL; + int i; alloc_mem(&src, &dst, len); @@ -147,7 +152,8 @@ static double do_memcpy_gettimeofday(memcpy_t fn, size_t len, bool prefault) fn(dst, src, len); BUG_ON(gettimeofday(&tv_start, NULL)); - fn(dst, src, len); + for (i = 0; i < iterations; ++i) + fn(dst, src, len); BUG_ON(gettimeofday(&tv_end, NULL)); timersub(&tv_end, &tv_start, &tv_diff); diff --git a/tools/perf/bench/mem-memset-arch.h b/tools/perf/bench/mem-memset-arch.h new file mode 100644 index 0000000000000..a040fa77665b0 --- /dev/null +++ b/tools/perf/bench/mem-memset-arch.h @@ -0,0 +1,12 @@ + +#ifdef ARCH_X86_64 + +#define MEMSET_FN(fn, name, desc) \ + extern void *fn(void *, int, size_t); + +#include "mem-memset-x86-64-asm-def.h" + +#undef MEMSET_FN + +#endif + diff --git a/tools/perf/bench/mem-memset-x86-64-asm-def.h b/tools/perf/bench/mem-memset-x86-64-asm-def.h new file mode 100644 index 0000000000000..a71dff97c1f54 --- /dev/null +++ b/tools/perf/bench/mem-memset-x86-64-asm-def.h @@ -0,0 +1,12 @@ + +MEMSET_FN(__memset, + "x86-64-unrolled", + "unrolled memset() in arch/x86/lib/memset_64.S") + +MEMSET_FN(memset_c, + "x86-64-stosq", + "movsq-based memset() in arch/x86/lib/memset_64.S") + +MEMSET_FN(memset_c_e, + "x86-64-stosb", + "movsb-based memset() in arch/x86/lib/memset_64.S") diff --git a/tools/perf/bench/mem-memset-x86-64-asm.S b/tools/perf/bench/mem-memset-x86-64-asm.S new file mode 100644 index 0000000000000..9e5af89ed13af --- /dev/null +++ b/tools/perf/bench/mem-memset-x86-64-asm.S @@ -0,0 +1,13 @@ +#define memset MEMSET /* don't hide glibc's memset() */ +#define altinstr_replacement text +#define globl p2align 4; .globl +#define Lmemset_c globl memset_c; memset_c +#define Lmemset_c_e globl memset_c_e; memset_c_e +#include "../../../arch/x86/lib/memset_64.S" + +/* + * We need to provide note.GNU-stack section, saying that we want + * NOT executable stack. Otherwise the final linking will assume that + * the ELF stack should not be restricted at all and set it RWX. + */ +.section .note.GNU-stack,"",@progbits diff --git a/tools/perf/bench/mem-memset.c b/tools/perf/bench/mem-memset.c new file mode 100644 index 0000000000000..e9079185bd72d --- /dev/null +++ b/tools/perf/bench/mem-memset.c @@ -0,0 +1,297 @@ +/* + * mem-memset.c + * + * memset: Simple memory set in various ways + * + * Trivial clone of mem-memcpy.c. + */ + +#include "../perf.h" +#include "../util/util.h" +#include "../util/parse-options.h" +#include "../util/header.h" +#include "bench.h" +#include "mem-memset-arch.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> + +#define K 1024 + +static const char *length_str = "1MB"; +static const char *routine = "default"; +static int iterations = 1; +static bool use_clock; +static int clock_fd; +static bool only_prefault; +static bool no_prefault; + +static const struct option options[] = { + OPT_STRING('l', "length", &length_str, "1MB", + "Specify length of memory to copy. " + "available unit: B, MB, GB (upper and lower)"), + OPT_STRING('r', "routine", &routine, "default", + "Specify routine to copy"), + OPT_INTEGER('i', "iterations", &iterations, + "repeat memset() invocation this number of times"), + OPT_BOOLEAN('c', "clock", &use_clock, + "Use CPU clock for measuring"), + OPT_BOOLEAN('o', "only-prefault", &only_prefault, + "Show only the result with page faults before memset()"), + OPT_BOOLEAN('n', "no-prefault", &no_prefault, + "Show only the result without page faults before memset()"), + OPT_END() +}; + +typedef void *(*memset_t)(void *, int, size_t); + +struct routine { + const char *name; + const char *desc; + memset_t fn; +}; + +static const struct routine routines[] = { + { "default", + "Default memset() provided by glibc", + memset }, +#ifdef ARCH_X86_64 + +#define MEMSET_FN(fn, name, desc) { name, desc, fn }, +#include "mem-memset-x86-64-asm-def.h" +#undef MEMSET_FN + +#endif + + { NULL, + NULL, + NULL } +}; + +static const char * const bench_mem_memset_usage[] = { + "perf bench mem memset <options>", + NULL +}; + +static struct perf_event_attr clock_attr = { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_CPU_CYCLES +}; + +static void init_clock(void) +{ + clock_fd = sys_perf_event_open(&clock_attr, getpid(), -1, -1, 0); + + if (clock_fd < 0 && errno == ENOSYS) + die("No CONFIG_PERF_EVENTS=y kernel support configured?\n"); + else + BUG_ON(clock_fd < 0); +} + +static u64 get_clock(void) +{ + int ret; + u64 clk; + + ret = read(clock_fd, &clk, sizeof(u64)); + BUG_ON(ret != sizeof(u64)); + + return clk; +} + +static double timeval2double(struct timeval *ts) +{ + return (double)ts->tv_sec + + (double)ts->tv_usec / (double)1000000; +} + +static void alloc_mem(void **dst, size_t length) +{ + *dst = zalloc(length); + if (!dst) + die("memory allocation failed - maybe length is too large?\n"); +} + +static u64 do_memset_clock(memset_t fn, size_t len, bool prefault) +{ + u64 clock_start = 0ULL, clock_end = 0ULL; + void *dst = NULL; + int i; + + alloc_mem(&dst, len); + + if (prefault) + fn(dst, -1, len); + + clock_start = get_clock(); + for (i = 0; i < iterations; ++i) + fn(dst, i, len); + clock_end = get_clock(); + + free(dst); + return clock_end - clock_start; +} + +static double do_memset_gettimeofday(memset_t fn, size_t len, bool prefault) +{ + struct timeval tv_start, tv_end, tv_diff; + void *dst = NULL; + int i; + + alloc_mem(&dst, len); + + if (prefault) + fn(dst, -1, len); + + BUG_ON(gettimeofday(&tv_start, NULL)); + for (i = 0; i < iterations; ++i) + fn(dst, i, len); + BUG_ON(gettimeofday(&tv_end, NULL)); + + timersub(&tv_end, &tv_start, &tv_diff); + + free(dst); + return (double)((double)len / timeval2double(&tv_diff)); +} + +#define pf (no_prefault ? 0 : 1) + +#define print_bps(x) do { \ + if (x < K) \ + printf(" %14lf B/Sec", x); \ + else if (x < K * K) \ + printf(" %14lfd KB/Sec", x / K); \ + else if (x < K * K * K) \ + printf(" %14lf MB/Sec", x / K / K); \ + else \ + printf(" %14lf GB/Sec", x / K / K / K); \ + } while (0) + +int bench_mem_memset(int argc, const char **argv, + const char *prefix __used) +{ + int i; + size_t len; + double result_bps[2]; + u64 result_clock[2]; + + argc = parse_options(argc, argv, options, + bench_mem_memset_usage, 0); + + if (use_clock) + init_clock(); + + len = (size_t)perf_atoll((char *)length_str); + + result_clock[0] = result_clock[1] = 0ULL; + result_bps[0] = result_bps[1] = 0.0; + + if ((s64)len <= 0) { + fprintf(stderr, "Invalid length:%s\n", length_str); + return 1; + } + + /* same to without specifying either of prefault and no-prefault */ + if (only_prefault && no_prefault) + only_prefault = no_prefault = false; + + for (i = 0; routines[i].name; i++) { + if (!strcmp(routines[i].name, routine)) + break; + } + if (!routines[i].name) { + printf("Unknown routine:%s\n", routine); + printf("Available routines...\n"); + for (i = 0; routines[i].name; i++) { + printf("\t%s ... %s\n", + routines[i].name, routines[i].desc); + } + return 1; + } + + if (bench_format == BENCH_FORMAT_DEFAULT) + printf("# Copying %s Bytes ...\n\n", length_str); + + if (!only_prefault && !no_prefault) { + /* show both of results */ + if (use_clock) { + result_clock[0] = + do_memset_clock(routines[i].fn, len, false); + result_clock[1] = + do_memset_clock(routines[i].fn, len, true); + } else { + result_bps[0] = + do_memset_gettimeofday(routines[i].fn, + len, false); + result_bps[1] = + do_memset_gettimeofday(routines[i].fn, + len, true); + } + } else { + if (use_clock) { + result_clock[pf] = + do_memset_clock(routines[i].fn, + len, only_prefault); + } else { + result_bps[pf] = + do_memset_gettimeofday(routines[i].fn, + len, only_prefault); + } + } + + switch (bench_format) { + case BENCH_FORMAT_DEFAULT: + if (!only_prefault && !no_prefault) { + if (use_clock) { + printf(" %14lf Clock/Byte\n", + (double)result_clock[0] + / (double)len); + printf(" %14lf Clock/Byte (with prefault)\n ", + (double)result_clock[1] + / (double)len); + } else { + print_bps(result_bps[0]); + printf("\n"); + print_bps(result_bps[1]); + printf(" (with prefault)\n"); + } + } else { + if (use_clock) { + printf(" %14lf Clock/Byte", + (double)result_clock[pf] + / (double)len); + } else + print_bps(result_bps[pf]); + + printf("%s\n", only_prefault ? " (with prefault)" : ""); + } + break; + case BENCH_FORMAT_SIMPLE: + if (!only_prefault && !no_prefault) { + if (use_clock) { + printf("%lf %lf\n", + (double)result_clock[0] / (double)len, + (double)result_clock[1] / (double)len); + } else { + printf("%lf %lf\n", + result_bps[0], result_bps[1]); + } + } else { + if (use_clock) { + printf("%lf\n", (double)result_clock[pf] + / (double)len); + } else + printf("%lf\n", result_bps[pf]); + } + break; + default: + /* reaching this means there's some disaster: */ + die("unknown format: %d\n", bench_format); + break; + } + + return 0; +} diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c index fcb96269852a3..b0e74ab2d7a29 100644 --- a/tools/perf/builtin-bench.c +++ b/tools/perf/builtin-bench.c @@ -52,6 +52,9 @@ static struct bench_suite mem_suites[] = { { "memcpy", "Simple memory copy in various ways", bench_mem_memcpy }, + { "memset", + "Simple memory set in various ways", + bench_mem_memset }, suite_all, { NULL, NULL, diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index 4f19513d7dda0..d29d350fb2b73 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -24,6 +24,11 @@ static char diff__default_sort_order[] = "dso,symbol"; static bool force; static bool show_displacement; +struct perf_diff { + struct perf_tool tool; + struct perf_session *session; +}; + static int hists__add_entry(struct hists *self, struct addr_location *al, u64 period) { @@ -32,12 +37,14 @@ static int hists__add_entry(struct hists *self, return -ENOMEM; } -static int diff__process_sample_event(struct perf_tool *tool __used, +static int diff__process_sample_event(struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct perf_evsel *evsel __used, struct machine *machine) { + struct perf_diff *_diff = container_of(tool, struct perf_diff, tool); + struct perf_session *session = _diff->session; struct addr_location al; if (perf_event__preprocess_sample(event, machine, &al, sample, NULL) < 0) { @@ -49,24 +56,26 @@ static int diff__process_sample_event(struct perf_tool *tool __used, if (al.filtered || al.sym == NULL) return 0; - if (hists__add_entry(&evsel->hists, &al, sample->period)) { + if (hists__add_entry(&session->hists, &al, sample->period)) { pr_warning("problem incrementing symbol period, skipping event\n"); return -1; } - evsel->hists.stats.total_period += sample->period; + session->hists.stats.total_period += sample->period; return 0; } -static struct perf_tool perf_diff = { - .sample = diff__process_sample_event, - .mmap = perf_event__process_mmap, - .comm = perf_event__process_comm, - .exit = perf_event__process_task, - .fork = perf_event__process_task, - .lost = perf_event__process_lost, - .ordered_samples = true, - .ordering_requires_timestamps = true, +static struct perf_diff diff = { + .tool = { + .sample = diff__process_sample_event, + .mmap = perf_event__process_mmap, + .comm = perf_event__process_comm, + .exit = perf_event__process_task, + .fork = perf_event__process_task, + .lost = perf_event__process_lost, + .ordered_samples = true, + .ordering_requires_timestamps = true, + }, }; static void perf_session__insert_hist_entry_by_name(struct rb_root *root, @@ -107,12 +116,6 @@ static void hists__resort_entries(struct hists *self) self->entries = tmp; } -static void hists__set_positions(struct hists *self) -{ - hists__output_resort(self); - hists__resort_entries(self); -} - static struct hist_entry *hists__find_entry(struct hists *self, struct hist_entry *he) { @@ -146,30 +149,37 @@ static void hists__match(struct hists *older, struct hists *newer) static int __cmd_diff(void) { int ret, i; +#define older (session[0]) +#define newer (session[1]) struct perf_session *session[2]; - session[0] = perf_session__new(input_old, O_RDONLY, force, false, &perf_diff); - session[1] = perf_session__new(input_new, O_RDONLY, force, false, &perf_diff); + older = perf_session__new(input_old, O_RDONLY, force, false, + &diff.tool); + newer = perf_session__new(input_new, O_RDONLY, force, false, + &diff.tool); if (session[0] == NULL || session[1] == NULL) return -ENOMEM; for (i = 0; i < 2; ++i) { - ret = perf_session__process_events(session[i], &perf_diff); + diff.session = session[i]; + ret = perf_session__process_events(session[i], &diff.tool); if (ret) goto out_delete; + hists__output_resort(&session[i]->hists); } - hists__output_resort(&session[1]->hists); if (show_displacement) - hists__set_positions(&session[0]->hists); + hists__resort_entries(&older->hists); - hists__match(&session[0]->hists, &session[1]->hists); - hists__fprintf(&session[1]->hists, &session[0]->hists, + hists__match(&older->hists, &newer->hists); + hists__fprintf(&newer->hists, &older->hists, show_displacement, true, 0, 0, stdout); out_delete: for (i = 0; i < 2; ++i) perf_session__delete(session[i]); return ret; +#undef older +#undef newer } static const char * const diff_usage[] = { diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index 39104c0beea37..547af48deb4f9 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c @@ -192,7 +192,7 @@ static void insert_caller_stat(unsigned long call_site, } static void process_alloc_event(void *data, - struct event *event, + struct event_format *event, int cpu, u64 timestamp __used, struct thread *thread __used, @@ -253,7 +253,7 @@ static struct alloc_stat *search_alloc_stat(unsigned long ptr, } static void process_free_event(void *data, - struct event *event, + struct event_format *event, int cpu, u64 timestamp __used, struct thread *thread __used) @@ -281,7 +281,7 @@ static void process_free_event(void *data, static void process_raw_event(union perf_event *raw_event __used, void *data, int cpu, u64 timestamp, struct thread *thread) { - struct event *event; + struct event_format *event; int type; type = trace_parse_common_type(data); diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index 2296c391d0f58..fd53319de20d6 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -356,25 +356,25 @@ struct trace_release_event { struct trace_lock_handler { void (*acquire_event)(struct trace_acquire_event *, - struct event *, + struct event_format *, int cpu, u64 timestamp, struct thread *thread); void (*acquired_event)(struct trace_acquired_event *, - struct event *, + struct event_format *, int cpu, u64 timestamp, struct thread *thread); void (*contended_event)(struct trace_contended_event *, - struct event *, + struct event_format *, int cpu, u64 timestamp, struct thread *thread); void (*release_event)(struct trace_release_event *, - struct event *, + struct event_format *, int cpu, u64 timestamp, struct thread *thread); @@ -416,7 +416,7 @@ enum acquire_flags { static void report_lock_acquire_event(struct trace_acquire_event *acquire_event, - struct event *__event __used, + struct event_format *__event __used, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -480,7 +480,7 @@ end: static void report_lock_acquired_event(struct trace_acquired_event *acquired_event, - struct event *__event __used, + struct event_format *__event __used, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -536,7 +536,7 @@ end: static void report_lock_contended_event(struct trace_contended_event *contended_event, - struct event *__event __used, + struct event_format *__event __used, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -583,7 +583,7 @@ end: static void report_lock_release_event(struct trace_release_event *release_event, - struct event *__event __used, + struct event_format *__event __used, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -647,7 +647,7 @@ static struct trace_lock_handler *trace_handler; static void process_lock_acquire_event(void *data, - struct event *event __used, + struct event_format *event __used, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -666,7 +666,7 @@ process_lock_acquire_event(void *data, static void process_lock_acquired_event(void *data, - struct event *event __used, + struct event_format *event __used, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -684,7 +684,7 @@ process_lock_acquired_event(void *data, static void process_lock_contended_event(void *data, - struct event *event __used, + struct event_format *event __used, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -702,7 +702,7 @@ process_lock_contended_event(void *data, static void process_lock_release_event(void *data, - struct event *event __used, + struct event_format *event __used, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -721,7 +721,7 @@ process_lock_release_event(void *data, static void process_raw_event(void *data, int cpu, u64 timestamp, struct thread *thread) { - struct event *event; + struct event_format *event; int type; type = trace_parse_common_type(data); @@ -922,12 +922,12 @@ static const struct option info_options[] = { OPT_BOOLEAN('t', "threads", &info_threads, "dump thread list in perf.data"), OPT_BOOLEAN('m', "map", &info_map, - "map of lock instances (name:address table)"), + "map of lock instances (address:name table)"), OPT_END() }; static const char * const lock_usage[] = { - "perf lock [<options>] {record|trace|report}", + "perf lock [<options>] {record|report|script|info}", NULL }; diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index fb8566181f27f..4935c09dd5b58 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -58,7 +58,7 @@ static struct { struct perf_probe_event events[MAX_PROBES]; struct strlist *dellist; struct line_range line_range; - const char *target_module; + const char *target; int max_probe_points; struct strfilter *filter; } params; @@ -246,7 +246,7 @@ static const struct option options[] = { "file", "vmlinux pathname"), OPT_STRING('s', "source", &symbol_conf.source_prefix, "directory", "path to kernel source"), - OPT_STRING('m', "module", ¶ms.target_module, + OPT_STRING('m', "module", ¶ms.target, "modname|path", "target module name (for online) or path (for offline)"), #endif @@ -333,7 +333,7 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) if (!params.filter) params.filter = strfilter__new(DEFAULT_FUNC_FILTER, NULL); - ret = show_available_funcs(params.target_module, + ret = show_available_funcs(params.target, params.filter); strfilter__delete(params.filter); if (ret < 0) @@ -354,7 +354,7 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) usage_with_options(probe_usage, options); } - ret = show_line_range(¶ms.line_range, params.target_module); + ret = show_line_range(¶ms.line_range, params.target); if (ret < 0) pr_err(" Error: Failed to show lines. (%d)\n", ret); return ret; @@ -371,7 +371,7 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) ret = show_available_vars(params.events, params.nevents, params.max_probe_points, - params.target_module, + params.target, params.filter, params.show_ext_vars); strfilter__delete(params.filter); @@ -393,7 +393,7 @@ 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.max_probe_points, - params.target_module, + params.target, params.force_add); if (ret < 0) { pr_err(" Error: Failed to add events. (%d)\n", ret); diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 0abfb18b911fb..8a3dfac161e29 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -204,8 +204,11 @@ static void perf_record__open(struct perf_record *rec) if (opts->group && pos != first) group_fd = first->fd; +fallback_missing_features: + if (opts->exclude_guest_missing) + attr->exclude_guest = attr->exclude_host = 0; retry_sample_id: - attr->sample_id_all = opts->sample_id_all_avail ? 1 : 0; + attr->sample_id_all = opts->sample_id_all_missing ? 0 : 1; try_again: if (perf_evsel__open(pos, evlist->cpus, evlist->threads, opts->group, group_fd) < 0) { @@ -214,26 +217,38 @@ try_again: if (err == EPERM || err == EACCES) { ui__error_paranoid(); exit(EXIT_FAILURE); - } else if (err == ENODEV && opts->cpu_list) { + } else if (err == ENODEV && opts->target.cpu_list) { die("No such device - did you specify" " an out-of-range profile CPU?\n"); - } else if (err == EINVAL && opts->sample_id_all_avail) { - /* - * Old kernel, no attr->sample_id_type_all field - */ - opts->sample_id_all_avail = false; - if (!opts->sample_time && !opts->raw_samples && !time_needed) - attr->sample_type &= ~PERF_SAMPLE_TIME; - - goto retry_sample_id; + } else if (err == EINVAL) { + if (!opts->exclude_guest_missing && + (attr->exclude_guest || attr->exclude_host)) { + pr_debug("Old kernel, cannot exclude " + "guest or host samples.\n"); + opts->exclude_guest_missing = true; + goto fallback_missing_features; + } else if (!opts->sample_id_all_missing) { + /* + * Old kernel, no attr->sample_id_type_all field + */ + opts->sample_id_all_missing = true; + if (!opts->sample_time && !opts->raw_samples && !time_needed) + attr->sample_type &= ~PERF_SAMPLE_TIME; + + goto retry_sample_id; + } } /* * If it's cycles then fall back to hrtimer * based cpu-clock-tick sw counter, which - * is always available even if no PMU support: + * is always available even if no PMU support. + * + * PPC returns ENXIO until 2.6.37 (behavior changed + * with commit b0a873e). */ - if (attr->type == PERF_TYPE_HARDWARE + if ((err == ENOENT || err == ENXIO) + && attr->type == PERF_TYPE_HARDWARE && attr->config == PERF_COUNT_HW_CPU_CYCLES) { if (verbose) @@ -241,6 +256,10 @@ try_again: "trying to fall back to cpu-clock-ticks\n"); attr->type = PERF_TYPE_SOFTWARE; attr->config = PERF_COUNT_SW_CPU_CLOCK; + if (pos->name) { + free(pos->name); + pos->name = NULL; + } goto try_again; } @@ -385,7 +404,7 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) { struct stat st; int flags; - int err, output; + int err, output, feat; unsigned long waking = 0; const bool forks = argc > 0; struct machine *machine; @@ -452,8 +471,17 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) rec->session = session; - if (!rec->no_buildid) - perf_header__set_feat(&session->header, HEADER_BUILD_ID); + for (feat = HEADER_FIRST_FEATURE; feat < HEADER_LAST_FEATURE; feat++) + perf_header__set_feat(&session->header, feat); + + if (rec->no_buildid) + perf_header__clear_feat(&session->header, HEADER_BUILD_ID); + + if (!have_tracepoints(&evsel_list->entries)) + perf_header__clear_feat(&session->header, HEADER_TRACE_INFO); + + if (!rec->opts.branch_stack) + perf_header__clear_feat(&session->header, HEADER_BRANCH_STACK); if (!rec->file_new) { err = perf_session__read_header(session, output); @@ -461,22 +489,6 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) goto out_delete_session; } - if (have_tracepoints(&evsel_list->entries)) - perf_header__set_feat(&session->header, HEADER_TRACE_INFO); - - perf_header__set_feat(&session->header, HEADER_HOSTNAME); - perf_header__set_feat(&session->header, HEADER_OSRELEASE); - perf_header__set_feat(&session->header, HEADER_ARCH); - perf_header__set_feat(&session->header, HEADER_CPUDESC); - perf_header__set_feat(&session->header, HEADER_NRCPUS); - perf_header__set_feat(&session->header, HEADER_EVENT_DESC); - perf_header__set_feat(&session->header, HEADER_CMDLINE); - perf_header__set_feat(&session->header, HEADER_VERSION); - perf_header__set_feat(&session->header, HEADER_CPU_TOPOLOGY); - perf_header__set_feat(&session->header, HEADER_TOTAL_MEM); - perf_header__set_feat(&session->header, HEADER_NUMA_TOPOLOGY); - perf_header__set_feat(&session->header, HEADER_CPUID); - if (forks) { err = perf_evlist__prepare_workload(evsel_list, opts, argv); if (err < 0) { @@ -503,9 +515,9 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) return err; } - if (!!rec->no_buildid + if (!rec->no_buildid && !perf_header__has_feat(&session->header, HEADER_BUILD_ID)) { - pr_err("Couldn't generating buildids. " + pr_err("Couldn't generate buildids. " "Use --no-buildid to profile anyway.\n"); return -1; } @@ -573,7 +585,7 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) perf_session__process_machines(session, tool, perf_event__synthesize_guest_os); - if (!opts->system_wide) + if (!opts->target.system_wide) perf_event__synthesize_thread_map(tool, evsel_list->threads, process_synthesized_event, machine); @@ -636,6 +648,90 @@ out_delete_session: return err; } +#define BRANCH_OPT(n, m) \ + { .name = n, .mode = (m) } + +#define BRANCH_END { .name = NULL } + +struct branch_mode { + const char *name; + int mode; +}; + +static const struct branch_mode branch_modes[] = { + BRANCH_OPT("u", PERF_SAMPLE_BRANCH_USER), + BRANCH_OPT("k", PERF_SAMPLE_BRANCH_KERNEL), + BRANCH_OPT("hv", PERF_SAMPLE_BRANCH_HV), + BRANCH_OPT("any", PERF_SAMPLE_BRANCH_ANY), + BRANCH_OPT("any_call", PERF_SAMPLE_BRANCH_ANY_CALL), + BRANCH_OPT("any_ret", PERF_SAMPLE_BRANCH_ANY_RETURN), + BRANCH_OPT("ind_call", PERF_SAMPLE_BRANCH_IND_CALL), + BRANCH_END +}; + +static int +parse_branch_stack(const struct option *opt, const char *str, int unset) +{ +#define ONLY_PLM \ + (PERF_SAMPLE_BRANCH_USER |\ + PERF_SAMPLE_BRANCH_KERNEL |\ + PERF_SAMPLE_BRANCH_HV) + + uint64_t *mode = (uint64_t *)opt->value; + const struct branch_mode *br; + char *s, *os = NULL, *p; + int ret = -1; + + if (unset) + return 0; + + /* + * cannot set it twice, -b + --branch-filter for instance + */ + if (*mode) + return -1; + + /* str may be NULL in case no arg is passed to -b */ + if (str) { + /* because str is read-only */ + s = os = strdup(str); + if (!s) + return -1; + + for (;;) { + p = strchr(s, ','); + if (p) + *p = '\0'; + + for (br = branch_modes; br->name; br++) { + if (!strcasecmp(s, br->name)) + break; + } + if (!br->name) { + ui__warning("unknown branch filter %s," + " check man page\n", s); + goto error; + } + + *mode |= br->mode; + + if (!p) + break; + + s = p + 1; + } + } + ret = 0; + + /* default to any branch */ + if ((*mode & ~ONLY_PLM) == 0) { + *mode = PERF_SAMPLE_BRANCH_ANY; + } +error: + free(os); + return ret; +} + static const char * const record_usage[] = { "perf record [<options>] [<command>]", "perf record [<options>] -- <command> [<options>]", @@ -654,13 +750,13 @@ static const char * const record_usage[] = { */ static struct perf_record record = { .opts = { - .target_pid = -1, - .target_tid = -1, .mmap_pages = UINT_MAX, .user_freq = UINT_MAX, .user_interval = ULLONG_MAX, .freq = 1000, - .sample_id_all_avail = true, + .target = { + .uses_mmap = true, + }, }, .write_mode = WRITE_FORCE, .file_new = true, @@ -679,9 +775,9 @@ const struct option record_options[] = { parse_events_option), OPT_CALLBACK(0, "filter", &record.evlist, "filter", "event filter", parse_filter), - OPT_INTEGER('p', "pid", &record.opts.target_pid, + OPT_STRING('p', "pid", &record.opts.target.pid, "pid", "record events on existing process id"), - OPT_INTEGER('t', "tid", &record.opts.target_tid, + OPT_STRING('t', "tid", &record.opts.target.tid, "tid", "record events on existing thread id"), OPT_INTEGER('r', "realtime", &record.realtime_prio, "collect data with this RT SCHED_FIFO priority"), @@ -689,11 +785,11 @@ const struct option record_options[] = { "collect data without buffering"), OPT_BOOLEAN('R', "raw-samples", &record.opts.raw_samples, "collect raw sample records from all opened counters"), - OPT_BOOLEAN('a', "all-cpus", &record.opts.system_wide, + OPT_BOOLEAN('a', "all-cpus", &record.opts.target.system_wide, "system-wide collection from all CPUs"), OPT_BOOLEAN('A', "append", &record.append_file, "append to the output file to do incremental profiling"), - OPT_STRING('C', "cpu", &record.opts.cpu_list, "cpu", + OPT_STRING('C', "cpu", &record.opts.target.cpu_list, "cpu", "list of cpus to monitor"), OPT_BOOLEAN('f', "force", &record.force, "overwrite existing data file (deprecated)"), @@ -727,6 +823,16 @@ const struct option record_options[] = { OPT_CALLBACK('G', "cgroup", &record.evlist, "name", "monitor event in cgroup name only", parse_cgroups), + OPT_STRING('u', "uid", &record.opts.target.uid_str, "user", + "user to profile"), + + OPT_CALLBACK_NOOPT('b', "branch-any", &record.opts.branch_stack, + "branch any", "sample any taken branches", + parse_branch_stack), + + OPT_CALLBACK('j', "branch-filter", &record.opts.branch_stack, + "branch filter mask", "branch stack filter modes", + parse_branch_stack), OPT_END() }; @@ -736,6 +842,7 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) struct perf_evsel *pos; struct perf_evlist *evsel_list; struct perf_record *rec = &record; + char errbuf[BUFSIZ]; perf_header__set_cmdline(argc, argv); @@ -747,8 +854,7 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) argc = parse_options(argc, argv, record_options, record_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (!argc && rec->opts.target_pid == -1 && rec->opts.target_tid == -1 && - !rec->opts.system_wide && !rec->opts.cpu_list) + if (!argc && perf_target__none(&rec->opts.target)) usage_with_options(record_usage, record_options); if (rec->force && rec->append_file) { @@ -761,7 +867,7 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) rec->write_mode = WRITE_FORCE; } - if (nr_cgroups && !rec->opts.system_wide) { + if (nr_cgroups && !rec->opts.target.system_wide) { fprintf(stderr, "cgroup monitoring only available in" " system-wide mode\n"); usage_with_options(record_usage, record_options); @@ -788,11 +894,25 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) goto out_symbol_exit; } - if (rec->opts.target_pid != -1) - rec->opts.target_tid = rec->opts.target_pid; + err = perf_target__validate(&rec->opts.target); + if (err) { + perf_target__strerror(&rec->opts.target, err, errbuf, BUFSIZ); + ui__warning("%s", errbuf); + } + + err = perf_target__parse_uid(&rec->opts.target); + if (err) { + int saved_errno = errno; + + perf_target__strerror(&rec->opts.target, err, errbuf, BUFSIZ); + ui__warning("%s", errbuf); + + err = -saved_errno; + goto out_free_fd; + } - if (perf_evlist__create_maps(evsel_list, rec->opts.target_pid, - rec->opts.target_tid, rec->opts.cpu_list) < 0) + err = -ENOMEM; + if (perf_evlist__create_maps(evsel_list, &rec->opts.target) < 0) usage_with_options(record_usage, record_options); list_for_each_entry(pos, &evsel_list->entries, node) { diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 25d34d483e494..d58e41445d0d6 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -40,7 +40,7 @@ struct perf_report { struct perf_tool tool; struct perf_session *session; char const *input_name; - bool force, use_tui, use_stdio; + bool force, use_tui, use_gtk, use_stdio; bool hide_unresolved; bool dont_use_callchains; bool show_full_info; @@ -50,9 +50,86 @@ struct perf_report { const char *pretty_printing_style; symbol_filter_t annotate_init; const char *cpu_list; + const char *symbol_filter_str; DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); }; +static int perf_report__add_branch_hist_entry(struct perf_tool *tool, + struct addr_location *al, + struct perf_sample *sample, + struct perf_evsel *evsel, + struct machine *machine) +{ + struct perf_report *rep = container_of(tool, struct perf_report, tool); + struct symbol *parent = NULL; + int err = 0; + unsigned i; + struct hist_entry *he; + struct branch_info *bi, *bx; + + if ((sort__has_parent || symbol_conf.use_callchain) + && sample->callchain) { + err = machine__resolve_callchain(machine, evsel, al->thread, + sample->callchain, &parent); + if (err) + return err; + } + + bi = machine__resolve_bstack(machine, al->thread, + sample->branch_stack); + if (!bi) + return -ENOMEM; + + for (i = 0; i < sample->branch_stack->nr; i++) { + if (rep->hide_unresolved && !(bi[i].from.sym && bi[i].to.sym)) + continue; + /* + * The report shows the percentage of total branches captured + * and not events sampled. Thus we use a pseudo period of 1. + */ + he = __hists__add_branch_entry(&evsel->hists, al, parent, + &bi[i], 1); + if (he) { + struct annotation *notes; + err = -ENOMEM; + bx = he->branch_info; + if (bx->from.sym && use_browser > 0) { + notes = symbol__annotation(bx->from.sym); + if (!notes->src + && symbol__alloc_hist(bx->from.sym) < 0) + goto out; + + err = symbol__inc_addr_samples(bx->from.sym, + bx->from.map, + evsel->idx, + bx->from.al_addr); + if (err) + goto out; + } + + if (bx->to.sym && use_browser > 0) { + notes = symbol__annotation(bx->to.sym); + if (!notes->src + && symbol__alloc_hist(bx->to.sym) < 0) + goto out; + + err = symbol__inc_addr_samples(bx->to.sym, + bx->to.map, + evsel->idx, + bx->to.al_addr); + if (err) + goto out; + } + evsel->hists.stats.total_period += 1; + hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); + err = 0; + } else + return -ENOMEM; + } +out: + return err; +} + static int perf_evsel__add_hist_entry(struct perf_evsel *evsel, struct addr_location *al, struct perf_sample *sample, @@ -126,14 +203,21 @@ static int process_sample_event(struct perf_tool *tool, if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap)) return 0; - if (al.map != NULL) - al.map->dso->hit = 1; + if (sort__branch_mode == 1) { + if (perf_report__add_branch_hist_entry(tool, &al, sample, + evsel, machine)) { + pr_debug("problem adding lbr entry, skipping event\n"); + return -1; + } + } else { + if (al.map != NULL) + al.map->dso->hit = 1; - if (perf_evsel__add_hist_entry(evsel, &al, sample, machine)) { - pr_debug("problem incrementing symbol period, skipping event\n"); - return -1; + if (perf_evsel__add_hist_entry(evsel, &al, sample, machine)) { + pr_debug("problem incrementing symbol period, skipping event\n"); + return -1; + } } - return 0; } @@ -188,6 +272,15 @@ static int perf_report__setup_sample_type(struct perf_report *rep) } } + if (sort__branch_mode == 1) { + if (!(self->sample_type & PERF_SAMPLE_BRANCH_STACK)) { + fprintf(stderr, "selected -b but no branch data." + " Did you call perf record without" + " -b?\n"); + return -1; + } + } + return 0; } @@ -203,12 +296,15 @@ static size_t hists__fprintf_nr_sample_events(struct hists *self, { size_t ret; char unit; - unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE]; + unsigned long nr_samples = self->stats.nr_events[PERF_RECORD_SAMPLE]; + u64 nr_events = self->stats.total_period; - nr_events = convert_unit(nr_events, &unit); - ret = fprintf(fp, "# Events: %lu%c", nr_events, unit); + nr_samples = convert_unit(nr_samples, &unit); + ret = fprintf(fp, "# Samples: %lu%c", nr_samples, unit); if (evname != NULL) - ret += fprintf(fp, " %s", evname); + ret += fprintf(fp, " of event '%s'", evname); + + ret += fprintf(fp, "\n# Event count (approx.): %" PRIu64, nr_events); return ret + fprintf(fp, "\n#\n"); } @@ -246,7 +342,7 @@ static int __cmd_report(struct perf_report *rep) { int ret = -EINVAL; u64 nr_samples; - struct perf_session *session; + struct perf_session *session = rep->session; struct perf_evsel *pos; struct map *kernel_map; struct kmap *kernel_kmap; @@ -254,13 +350,6 @@ static int __cmd_report(struct perf_report *rep) signal(SIGINT, sig_handler); - session = perf_session__new(rep->input_name, O_RDONLY, - rep->force, false, &rep->tool); - if (session == NULL) - return -ENOMEM; - - rep->session = session; - if (rep->cpu_list) { ret = perf_session__cpu_bitmap(session, rep->cpu_list, rep->cpu_bitmap); @@ -288,16 +377,23 @@ static int __cmd_report(struct perf_report *rep) (kernel_map->dso->hit && (kernel_kmap->ref_reloc_sym == NULL || kernel_kmap->ref_reloc_sym->addr == 0))) { - const struct dso *kdso = kernel_map->dso; + const char *desc = + "As no suitable kallsyms nor vmlinux was found, kernel samples\n" + "can't be resolved."; + + if (kernel_map) { + const struct dso *kdso = kernel_map->dso; + if (!RB_EMPTY_ROOT(&kdso->symbols[MAP__FUNCTION])) { + desc = "If some relocation was applied (e.g. " + "kexec) symbols may be misresolved."; + } + } ui__warning( "Kernel address maps (/proc/{kallsyms,modules}) were restricted.\n\n" "Check /proc/sys/kernel/kptr_restrict before running 'perf record'.\n\n%s\n\n" "Samples in kernel modules can't be resolved as well.\n\n", - RB_EMPTY_ROOT(&kdso->symbols[MAP__FUNCTION]) ? -"As no suitable kallsyms nor vmlinux was found, kernel samples\n" -"can't be resolved." : -"If some relocation was applied (e.g. kexec) symbols may be misresolved."); + desc); } if (dump_trace) { @@ -315,6 +411,9 @@ static int __cmd_report(struct perf_report *rep) list_for_each_entry(pos, &session->evlist->entries, node) { struct hists *hists = &pos->hists; + if (pos->idx == 0) + hists->symbol_filter_str = rep->symbol_filter_str; + hists__collapse_resort(hists); hists__output_resort(hists); nr_samples += hists->stats.nr_events[PERF_RECORD_SAMPLE]; @@ -326,8 +425,13 @@ static int __cmd_report(struct perf_report *rep) } if (use_browser > 0) { - perf_evlist__tui_browse_hists(session->evlist, help, - NULL, NULL, 0); + if (use_browser == 1) { + perf_evlist__tui_browse_hists(session->evlist, help, + NULL, NULL, 0); + } else if (use_browser == 2) { + perf_evlist__gtk_browse_hists(session->evlist, help, + NULL, NULL, 0); + } } else perf_evlist__tty_browse_hists(session->evlist, rep, help); @@ -427,9 +531,19 @@ setup: return 0; } +static int +parse_branch_mode(const struct option *opt __used, const char *str __used, int unset) +{ + sort__branch_mode = !unset; + return 0; +} + int cmd_report(int argc, const char **argv, const char *prefix __used) { + struct perf_session *session; struct stat st; + bool has_br_stack = false; + int ret = -1; char callchain_default_opt[] = "fractal,0.5,callee"; const char * const report_usage[] = { "perf report [<options>]", @@ -474,10 +588,12 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) OPT_STRING(0, "pretty", &report.pretty_printing_style, "key", "pretty printing style key: normal raw"), OPT_BOOLEAN(0, "tui", &report.use_tui, "Use the TUI interface"), + OPT_BOOLEAN(0, "gtk", &report.use_gtk, "Use the GTK2 interface"), OPT_BOOLEAN(0, "stdio", &report.use_stdio, "Use the stdio interface"), OPT_STRING('s', "sort", &sort_order, "key[,key2...]", - "sort by key(s): pid, comm, dso, symbol, parent"), + "sort by key(s): pid, comm, dso, symbol, parent, dso_to," + " dso_from, symbol_to, symbol_from, mispredict"), OPT_BOOLEAN(0, "showcpuutilization", &symbol_conf.show_cpu_utilization, "Show sample percentage for different cpu modes"), OPT_STRING('p', "parent", &parent_pattern, "regex", @@ -495,6 +611,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) "only consider symbols in these comms"), OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]", "only consider these symbols"), + OPT_STRING(0, "symbol-filter", &report.symbol_filter_str, "filter", + "only show symbols that (partially) match with this filter"), OPT_STRING('w', "column-widths", &symbol_conf.col_width_list_str, "width[,width...]", "don't try to adjust column width, use these fixed values"), @@ -517,6 +635,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) "Specify disassembler style (e.g. -M intel for intel syntax)"), OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period, "Show a column with the sum of periods"), + OPT_CALLBACK_NOOPT('b', "branch-stack", &sort__branch_mode, "", + "use branch records for histogram filling", parse_branch_mode), OPT_END() }; @@ -526,6 +646,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) use_browser = 0; else if (report.use_tui) use_browser = 1; + else if (report.use_gtk) + use_browser = 2; if (report.inverted_callchain) callchain_param.order = ORDER_CALLER; @@ -536,6 +658,30 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) else report.input_name = "perf.data"; } + session = perf_session__new(report.input_name, O_RDONLY, + report.force, false, &report.tool); + if (session == NULL) + return -ENOMEM; + + report.session = session; + + has_br_stack = perf_header__has_feat(&session->header, + HEADER_BRANCH_STACK); + + if (sort__branch_mode == -1 && has_br_stack) + sort__branch_mode = 1; + + /* sort__branch_mode could be 0 if --no-branch-stack */ + if (sort__branch_mode == 1) { + /* + * if no sort_order is provided, then specify + * branch-mode specific order + */ + if (sort_order == default_sort_order) + sort_order = "comm,dso_from,symbol_from," + "dso_to,symbol_to"; + + } if (strcmp(report.input_name, "-") != 0) setup_browser(true); @@ -568,13 +714,13 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) } if (symbol__init() < 0) - return -1; + goto error; setup_sorting(report_usage, options); if (parent_pattern != default_parent_pattern) { if (sort_dimension__add("parent") < 0) - return -1; + goto error; /* * Only show the parent fields if we explicitly @@ -586,15 +732,31 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) } else symbol_conf.exclude_other = false; - /* - * Any (unrecognized) arguments left? - */ - if (argc) - usage_with_options(report_usage, options); + if (argc) { + /* + * Special case: if there's an argument left then assume that + * it's a symbol filter: + */ + if (argc > 1) + usage_with_options(report_usage, options); + + report.symbol_filter_str = argv[0]; + } - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", stdout); sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, "comm", stdout); - sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", stdout); - return __cmd_report(&report); + if (sort__branch_mode == 1) { + sort_entry__setup_elide(&sort_dso_from, symbol_conf.dso_from_list, "dso_from", stdout); + sort_entry__setup_elide(&sort_dso_to, symbol_conf.dso_to_list, "dso_to", stdout); + sort_entry__setup_elide(&sort_sym_from, symbol_conf.sym_from_list, "sym_from", stdout); + sort_entry__setup_elide(&sort_sym_to, symbol_conf.sym_to_list, "sym_to", stdout); + } else { + sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", stdout); + sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", stdout); + } + + ret = __cmd_report(&report); +error: + perf_session__delete(session); + return ret; } diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index fb8b5f83b4a0a..b125e07eb3993 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c @@ -17,6 +17,7 @@ #include "util/debug.h" #include <sys/prctl.h> +#include <sys/resource.h> #include <semaphore.h> #include <pthread.h> @@ -727,34 +728,34 @@ struct trace_migrate_task_event { struct trace_sched_handler { void (*switch_event)(struct trace_switch_event *, struct machine *, - struct event *, + struct event_format *, int cpu, u64 timestamp, struct thread *thread); void (*runtime_event)(struct trace_runtime_event *, struct machine *, - struct event *, + struct event_format *, int cpu, u64 timestamp, struct thread *thread); void (*wakeup_event)(struct trace_wakeup_event *, struct machine *, - struct event *, + struct event_format *, int cpu, u64 timestamp, struct thread *thread); void (*fork_event)(struct trace_fork_event *, - struct event *, + struct event_format *, int cpu, u64 timestamp, struct thread *thread); void (*migrate_task_event)(struct trace_migrate_task_event *, struct machine *machine, - struct event *, + struct event_format *, int cpu, u64 timestamp, struct thread *thread); @@ -764,7 +765,7 @@ struct trace_sched_handler { static void replay_wakeup_event(struct trace_wakeup_event *wakeup_event, struct machine *machine __used, - struct event *event, + struct event_format *event, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -791,7 +792,7 @@ static u64 cpu_last_switched[MAX_CPUS]; static void replay_switch_event(struct trace_switch_event *switch_event, struct machine *machine __used, - struct event *event, + struct event_format *event, int cpu, u64 timestamp, struct thread *thread __used) @@ -834,7 +835,7 @@ replay_switch_event(struct trace_switch_event *switch_event, static void replay_fork_event(struct trace_fork_event *fork_event, - struct event *event, + struct event_format *event, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -943,7 +944,7 @@ static void thread_atoms_insert(struct thread *thread) static void latency_fork_event(struct trace_fork_event *fork_event __used, - struct event *event __used, + struct event_format *event __used, int cpu __used, u64 timestamp __used, struct thread *thread __used) @@ -1025,7 +1026,7 @@ add_sched_in_event(struct work_atoms *atoms, u64 timestamp) static void latency_switch_event(struct trace_switch_event *switch_event, struct machine *machine, - struct event *event __used, + struct event_format *event __used, int cpu, u64 timestamp, struct thread *thread __used) @@ -1078,7 +1079,7 @@ latency_switch_event(struct trace_switch_event *switch_event, static void latency_runtime_event(struct trace_runtime_event *runtime_event, struct machine *machine, - struct event *event __used, + struct event_format *event __used, int cpu, u64 timestamp, struct thread *this_thread __used) @@ -1101,7 +1102,7 @@ latency_runtime_event(struct trace_runtime_event *runtime_event, static void latency_wakeup_event(struct trace_wakeup_event *wakeup_event, struct machine *machine, - struct event *__event __used, + struct event_format *__event __used, int cpu __used, u64 timestamp, struct thread *thread __used) @@ -1149,7 +1150,7 @@ latency_wakeup_event(struct trace_wakeup_event *wakeup_event, static void latency_migrate_task_event(struct trace_migrate_task_event *migrate_task_event, struct machine *machine, - struct event *__event __used, + struct event_format *__event __used, int cpu __used, u64 timestamp, struct thread *thread __used) @@ -1360,7 +1361,7 @@ static struct trace_sched_handler *trace_handler; static void process_sched_wakeup_event(struct perf_tool *tool __used, - struct event *event, + struct event_format *event, struct perf_sample *sample, struct machine *machine, struct thread *thread) @@ -1397,7 +1398,7 @@ static char next_shortname2 = '0'; static void map_switch_event(struct trace_switch_event *switch_event, struct machine *machine, - struct event *event __used, + struct event_format *event __used, int this_cpu, u64 timestamp, struct thread *thread __used) @@ -1475,7 +1476,7 @@ map_switch_event(struct trace_switch_event *switch_event, static void process_sched_switch_event(struct perf_tool *tool __used, - struct event *event, + struct event_format *event, struct perf_sample *sample, struct machine *machine, struct thread *thread) @@ -1511,7 +1512,7 @@ process_sched_switch_event(struct perf_tool *tool __used, static void process_sched_runtime_event(struct perf_tool *tool __used, - struct event *event, + struct event_format *event, struct perf_sample *sample, struct machine *machine, struct thread *thread) @@ -1531,7 +1532,7 @@ process_sched_runtime_event(struct perf_tool *tool __used, static void process_sched_fork_event(struct perf_tool *tool __used, - struct event *event, + struct event_format *event, struct perf_sample *sample, struct machine *machine __used, struct thread *thread) @@ -1553,7 +1554,7 @@ process_sched_fork_event(struct perf_tool *tool __used, static void process_sched_exit_event(struct perf_tool *tool __used, - struct event *event, + struct event_format *event, struct perf_sample *sample __used, struct machine *machine __used, struct thread *thread __used) @@ -1564,7 +1565,7 @@ process_sched_exit_event(struct perf_tool *tool __used, static void process_sched_migrate_task_event(struct perf_tool *tool __used, - struct event *event, + struct event_format *event, struct perf_sample *sample, struct machine *machine, struct thread *thread) @@ -1585,7 +1586,7 @@ process_sched_migrate_task_event(struct perf_tool *tool __used, sample->time, thread); } -typedef void (*tracepoint_handler)(struct perf_tool *tool, struct event *event, +typedef void (*tracepoint_handler)(struct perf_tool *tool, struct event_format *event, struct perf_sample *sample, struct machine *machine, struct thread *thread); diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index bb68ddf257b7d..8e395a538eb92 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -40,6 +40,7 @@ enum perf_output_field { PERF_OUTPUT_SYM = 1U << 8, PERF_OUTPUT_DSO = 1U << 9, PERF_OUTPUT_ADDR = 1U << 10, + PERF_OUTPUT_SYMOFFSET = 1U << 11, }; struct output_option { @@ -57,6 +58,7 @@ struct output_option { {.str = "sym", .field = PERF_OUTPUT_SYM}, {.str = "dso", .field = PERF_OUTPUT_DSO}, {.str = "addr", .field = PERF_OUTPUT_ADDR}, + {.str = "symoff", .field = PERF_OUTPUT_SYMOFFSET}, }; /* default set to maintain compatibility with current format */ @@ -193,6 +195,11 @@ static int perf_evsel__check_attr(struct perf_evsel *evsel, "to symbols.\n"); return -EINVAL; } + if (PRINT_FIELD(SYMOFFSET) && !PRINT_FIELD(SYM)) { + pr_err("Display of offsets requested but symbol is not" + "selected.\n"); + return -EINVAL; + } if (PRINT_FIELD(DSO) && !PRINT_FIELD(IP) && !PRINT_FIELD(ADDR)) { pr_err("Display of DSO requested but neither sample IP nor " "sample address\nis selected. Hence, no addresses to convert " @@ -254,7 +261,7 @@ static void print_sample_start(struct perf_sample *sample, struct perf_event_attr *attr) { int type; - struct event *event; + struct event_format *event; const char *evname = NULL; unsigned long secs; unsigned long usecs; @@ -300,10 +307,17 @@ static void print_sample_start(struct perf_sample *sample, } else evname = __event_name(attr->type, attr->config); - printf("%s: ", evname ? evname : "(unknown)"); + printf("%s: ", evname ? evname : "[unknown]"); } } +static bool is_bts_event(struct perf_event_attr *attr) +{ + return ((attr->type == PERF_TYPE_HARDWARE) && + (attr->config & PERF_COUNT_HW_BRANCH_INSTRUCTIONS) && + (attr->sample_period == 1)); +} + static bool sample_addr_correlates_sym(struct perf_event_attr *attr) { if ((attr->type == PERF_TYPE_SOFTWARE) && @@ -312,6 +326,9 @@ static bool sample_addr_correlates_sym(struct perf_event_attr *attr) (attr->config == PERF_COUNT_SW_PAGE_FAULTS_MAJ))) return true; + if (is_bts_event(attr)) + return true; + return false; } @@ -323,7 +340,6 @@ static void print_sample_addr(union perf_event *event, { struct addr_location al; u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - const char *symname, *dsoname; printf("%16" PRIx64, sample->addr); @@ -343,22 +359,46 @@ static void print_sample_addr(union perf_event *event, al.sym = map__find_symbol(al.map, al.addr, NULL); if (PRINT_FIELD(SYM)) { - if (al.sym && al.sym->name) - symname = al.sym->name; + printf(" "); + if (PRINT_FIELD(SYMOFFSET)) + symbol__fprintf_symname_offs(al.sym, &al, stdout); else - symname = ""; - - printf(" %16s", symname); + symbol__fprintf_symname(al.sym, stdout); } if (PRINT_FIELD(DSO)) { - if (al.map && al.map->dso && al.map->dso->name) - dsoname = al.map->dso->name; - else - dsoname = ""; + printf(" ("); + map__fprintf_dsoname(al.map, stdout); + printf(")"); + } +} - printf(" (%s)", dsoname); +static void print_sample_bts(union perf_event *event, + struct perf_sample *sample, + struct perf_evsel *evsel, + struct machine *machine, + struct thread *thread) +{ + struct perf_event_attr *attr = &evsel->attr; + + /* print branch_from information */ + if (PRINT_FIELD(IP)) { + if (!symbol_conf.use_callchain) + printf(" "); + else + printf("\n"); + perf_event__print_ip(event, sample, machine, evsel, + PRINT_FIELD(SYM), PRINT_FIELD(DSO), + PRINT_FIELD(SYMOFFSET)); } + + printf(" => "); + + /* print branch_to information */ + if (PRINT_FIELD(ADDR)) + print_sample_addr(event, sample, machine, thread, attr); + + printf("\n"); } static void process_event(union perf_event *event __unused, @@ -374,6 +414,11 @@ static void process_event(union perf_event *event __unused, print_sample_start(sample, thread, attr); + if (is_bts_event(attr)) { + print_sample_bts(event, sample, evsel, machine, thread); + return; + } + if (PRINT_FIELD(TRACE)) print_trace_event(sample->cpu, sample->raw_data, sample->raw_size); @@ -387,7 +432,8 @@ static void process_event(union perf_event *event __unused, else printf("\n"); perf_event__print_ip(event, sample, machine, evsel, - PRINT_FIELD(SYM), PRINT_FIELD(DSO)); + PRINT_FIELD(SYM), PRINT_FIELD(DSO), + PRINT_FIELD(SYMOFFSET)); } printf("\n"); @@ -1097,7 +1143,10 @@ static const struct option options[] = { OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", "Look for files with symbols relative to this directory"), OPT_CALLBACK('f', "fields", NULL, "str", - "comma separated output fields prepend with 'type:'. Valid types: hw,sw,trace,raw. Fields: comm,tid,pid,time,cpu,event,trace,ip,sym,dso,addr", + "comma separated output fields prepend with 'type:'. " + "Valid types: hw,sw,trace,raw. " + "Fields: comm,tid,pid,time,cpu,event,trace,ip,sym,dso," + "addr,symoff", parse_output_fields), OPT_BOOLEAN('a', "all-cpus", &system_wide, "system-wide collection from all CPUs"), @@ -1106,6 +1155,9 @@ static const struct option options[] = { "only display events for these comms"), OPT_BOOLEAN('I', "show-info", &show_full_info, "display extended information from perf.data file"), + OPT_BOOLEAN('\0', "show-kernel-path", &symbol_conf.show_kernel_path, + "Show the path of [kernel.kallsyms]"), + OPT_END() }; diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index f5d2a63eba665..62ae30d34fa6c 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -173,24 +173,23 @@ static struct perf_event_attr very_very_detailed_attrs[] = { -struct perf_evlist *evsel_list; +static struct perf_evlist *evsel_list; -static bool system_wide = false; -static int run_idx = 0; +static struct perf_target target = { + .uid = UINT_MAX, +}; +static int run_idx = 0; static int run_count = 1; static bool no_inherit = false; static bool scale = true; static bool no_aggr = false; -static pid_t target_pid = -1; -static pid_t target_tid = -1; static pid_t child_pid = -1; static bool null_run = false; static int detailed_run = 0; static bool sync_run = false; static bool big_num = true; static int big_num_opt = -1; -static const char *cpu_list; static const char *csv_sep = NULL; static bool csv_output = false; static bool group = false; @@ -265,24 +264,26 @@ static double stddev_stats(struct stats *stats) return sqrt(variance_mean); } -struct stats runtime_nsecs_stats[MAX_NR_CPUS]; -struct stats runtime_cycles_stats[MAX_NR_CPUS]; -struct stats runtime_stalled_cycles_front_stats[MAX_NR_CPUS]; -struct stats runtime_stalled_cycles_back_stats[MAX_NR_CPUS]; -struct stats runtime_branches_stats[MAX_NR_CPUS]; -struct stats runtime_cacherefs_stats[MAX_NR_CPUS]; -struct stats runtime_l1_dcache_stats[MAX_NR_CPUS]; -struct stats runtime_l1_icache_stats[MAX_NR_CPUS]; -struct stats runtime_ll_cache_stats[MAX_NR_CPUS]; -struct stats runtime_itlb_cache_stats[MAX_NR_CPUS]; -struct stats runtime_dtlb_cache_stats[MAX_NR_CPUS]; -struct stats walltime_nsecs_stats; +static struct stats runtime_nsecs_stats[MAX_NR_CPUS]; +static struct stats runtime_cycles_stats[MAX_NR_CPUS]; +static struct stats runtime_stalled_cycles_front_stats[MAX_NR_CPUS]; +static struct stats runtime_stalled_cycles_back_stats[MAX_NR_CPUS]; +static struct stats runtime_branches_stats[MAX_NR_CPUS]; +static struct stats runtime_cacherefs_stats[MAX_NR_CPUS]; +static struct stats runtime_l1_dcache_stats[MAX_NR_CPUS]; +static struct stats runtime_l1_icache_stats[MAX_NR_CPUS]; +static struct stats runtime_ll_cache_stats[MAX_NR_CPUS]; +static struct stats runtime_itlb_cache_stats[MAX_NR_CPUS]; +static struct stats runtime_dtlb_cache_stats[MAX_NR_CPUS]; +static struct stats walltime_nsecs_stats; static int create_perf_stat_counter(struct perf_evsel *evsel, struct perf_evsel *first) { struct perf_event_attr *attr = &evsel->attr; struct xyarray *group_fd = NULL; + bool exclude_guest_missing = false; + int ret; if (group && evsel != first) group_fd = first->fd; @@ -293,16 +294,39 @@ static int create_perf_stat_counter(struct perf_evsel *evsel, attr->inherit = !no_inherit; - if (system_wide) - return perf_evsel__open_per_cpu(evsel, evsel_list->cpus, - group, group_fd); - if (target_pid == -1 && target_tid == -1) { +retry: + if (exclude_guest_missing) + evsel->attr.exclude_guest = evsel->attr.exclude_host = 0; + + if (perf_target__has_cpu(&target)) { + ret = perf_evsel__open_per_cpu(evsel, evsel_list->cpus, + group, group_fd); + if (ret) + goto check_ret; + return 0; + } + + if (!perf_target__has_task(&target) && (!group || evsel == first)) { attr->disabled = 1; attr->enable_on_exec = 1; } - return perf_evsel__open_per_thread(evsel, evsel_list->threads, - group, group_fd); + ret = perf_evsel__open_per_thread(evsel, evsel_list->threads, + group, group_fd); + if (!ret) + return 0; + /* fall through */ +check_ret: + if (ret && errno == EINVAL) { + if (!exclude_guest_missing && + (evsel->attr.exclude_guest || evsel->attr.exclude_host)) { + pr_debug("Old kernel, cannot exclude " + "guest or host samples.\n"); + exclude_guest_missing = true; + goto retry; + } + } + return ret; } /* @@ -446,7 +470,7 @@ static int run_perf_stat(int argc __used, const char **argv) exit(-1); } - if (target_tid == -1 && target_pid == -1 && !system_wide) + if (perf_target__none(&target)) evsel_list->threads->map[0] = child_pid; /* @@ -463,8 +487,13 @@ static int run_perf_stat(int argc __used, const char **argv) list_for_each_entry(counter, &evsel_list->entries, node) { if (create_perf_stat_counter(counter, first) < 0) { + /* + * PPC returns ENXIO for HW counters until 2.6.37 + * (behavior changed with commit b0a873e). + */ if (errno == EINVAL || errno == ENOSYS || - errno == ENOENT || errno == EOPNOTSUPP) { + errno == ENOENT || errno == EOPNOTSUPP || + errno == ENXIO) { if (verbose) ui__warning("%s event is not supported by the kernel.\n", event_name(counter)); @@ -476,7 +505,7 @@ static int run_perf_stat(int argc __used, const char **argv) error("You may not have permission to collect %sstats.\n" "\t Consider tweaking" " /proc/sys/kernel/perf_event_paranoid or running as root.", - system_wide ? "system-wide " : ""); + target.system_wide ? "system-wide " : ""); } else { error("open_counter returned with %d (%s). " "/bin/dmesg may provide additional information.\n", @@ -576,6 +605,8 @@ static void nsec_printout(int cpu, struct perf_evsel *evsel, double avg) if (perf_evsel__match(evsel, SOFTWARE, SW_TASK_CLOCK)) fprintf(output, " # %8.3f CPUs utilized ", avg / avg_stats(&walltime_nsecs_stats)); + else + fprintf(output, " "); } /* used for get_ratio_color() */ @@ -844,12 +875,18 @@ static void abs_printout(int cpu, struct perf_evsel *evsel, double avg) fprintf(output, " # %8.3f GHz ", ratio); } else if (runtime_nsecs_stats[cpu].n != 0) { + char unit = 'M'; + total = avg_stats(&runtime_nsecs_stats[cpu]); if (total) ratio = 1000.0 * avg / total; + if (ratio < 0.001) { + ratio *= 1000; + unit = 'K'; + } - fprintf(output, " # %8.3f M/sec ", ratio); + fprintf(output, " # %8.3f %c/sec ", ratio, unit); } else { fprintf(output, " "); } @@ -960,14 +997,14 @@ static void print_stat(int argc, const char **argv) if (!csv_output) { fprintf(output, "\n"); fprintf(output, " Performance counter stats for "); - if(target_pid == -1 && target_tid == -1) { + if (!perf_target__has_task(&target)) { fprintf(output, "\'%s", argv[0]); for (i = 1; i < argc; i++) fprintf(output, " %s", argv[i]); - } else if (target_pid != -1) - fprintf(output, "process id \'%d", target_pid); + } else if (target.pid) + fprintf(output, "process id \'%s", target.pid); else - fprintf(output, "thread id \'%d", target_tid); + fprintf(output, "thread id \'%s", target.tid); fprintf(output, "\'"); if (run_count > 1) @@ -1041,11 +1078,11 @@ static const struct option options[] = { "event filter", parse_filter), OPT_BOOLEAN('i', "no-inherit", &no_inherit, "child tasks do not inherit counters"), - OPT_INTEGER('p', "pid", &target_pid, - "stat events on existing process id"), - OPT_INTEGER('t', "tid", &target_tid, - "stat events on existing thread id"), - OPT_BOOLEAN('a', "all-cpus", &system_wide, + OPT_STRING('p', "pid", &target.pid, "pid", + "stat events on existing process id"), + OPT_STRING('t', "tid", &target.tid, "tid", + "stat events on existing thread id"), + OPT_BOOLEAN('a', "all-cpus", &target.system_wide, "system-wide collection from all CPUs"), OPT_BOOLEAN('g', "group", &group, "put the counters into a counter group"), @@ -1064,7 +1101,7 @@ static const struct option options[] = { OPT_CALLBACK_NOOPT('B', "big-num", NULL, NULL, "print large numbers with thousands\' separators", stat__set_big_num), - OPT_STRING('C', "cpu", &cpu_list, "cpu", + OPT_STRING('C', "cpu", &target.cpu_list, "cpu", "list of cpus to monitor in system-wide"), OPT_BOOLEAN('A', "no-aggr", &no_aggr, "disable CPU count aggregation"), @@ -1182,13 +1219,13 @@ int cmd_stat(int argc, const char **argv, const char *prefix __used) } else if (big_num_opt == 0) /* User passed --no-big-num */ big_num = false; - if (!argc && target_pid == -1 && target_tid == -1) + if (!argc && !perf_target__has_task(&target)) usage_with_options(stat_usage, options); if (run_count <= 0) usage_with_options(stat_usage, options); /* no_aggr, cgroup are for system-wide only */ - if ((no_aggr || nr_cgroups) && !system_wide) { + if ((no_aggr || nr_cgroups) && !perf_target__has_cpu(&target)) { fprintf(stderr, "both cgroup and no-aggregation " "modes only available in system-wide mode\n"); @@ -1198,22 +1235,14 @@ int cmd_stat(int argc, const char **argv, const char *prefix __used) if (add_default_attributes()) goto out; - if (target_pid != -1) - target_tid = target_pid; + perf_target__validate(&target); - evsel_list->threads = thread_map__new(target_pid, target_tid); - if (evsel_list->threads == NULL) { - pr_err("Problems finding threads of monitor\n"); - usage_with_options(stat_usage, options); - } - - if (system_wide) - evsel_list->cpus = cpu_map__new(cpu_list); - else - evsel_list->cpus = cpu_map__dummy_new(); + if (perf_evlist__create_maps(evsel_list, &target) < 0) { + if (perf_target__has_task(&target)) + pr_err("Problems finding threads of monitor\n"); + if (perf_target__has_cpu(&target)) + perror("failed to parse CPUs map"); - if (evsel_list->cpus == NULL) { - perror("failed to parse CPUs map"); usage_with_options(stat_usage, options); return -1; } diff --git a/tools/perf/builtin-test.c b/tools/perf/builtin-test.c index 3854e869dce10..6c47376e29d81 100644 --- a/tools/perf/builtin-test.c +++ b/tools/perf/builtin-test.c @@ -13,8 +13,11 @@ #include "util/parse-events.h" #include "util/symbol.h" #include "util/thread_map.h" +#include "util/pmu.h" #include "../../include/linux/hw_breakpoint.h" +#include <sys/mman.h> + static int vmlinux_matches_kallsyms_filter(struct map *map __used, struct symbol *sym) { bool *visited = symbol__priv(sym); @@ -276,7 +279,7 @@ static int test__open_syscall_event(void) return -1; } - threads = thread_map__new(-1, getpid()); + threads = thread_map__new(-1, getpid(), UINT_MAX); if (threads == NULL) { pr_debug("thread_map__new\n"); return -1; @@ -342,7 +345,7 @@ static int test__open_syscall_event_on_all_cpus(void) return -1; } - threads = thread_map__new(-1, getpid()); + threads = thread_map__new(-1, getpid(), UINT_MAX); if (threads == NULL) { pr_debug("thread_map__new\n"); return -1; @@ -490,7 +493,7 @@ static int test__basic_mmap(void) expected_nr_events[i] = random() % 257; } - threads = thread_map__new(-1, getpid()); + threads = thread_map__new(-1, getpid(), UINT_MAX); if (threads == NULL) { pr_debug("thread_map__new\n"); return -1; @@ -648,7 +651,7 @@ static int test__checkevent_raw(struct perf_evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); - TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); + TEST_ASSERT_VAL("wrong config", 0x1a == evsel->attr.config); return 0; } @@ -675,6 +678,24 @@ static int test__checkevent_symbolic_name(struct perf_evlist *evlist) return 0; } +static int test__checkevent_symbolic_name_config(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + TEST_ASSERT_VAL("wrong period", + 100000 == evsel->attr.sample_period); + TEST_ASSERT_VAL("wrong config1", + 0 == evsel->attr.config1); + TEST_ASSERT_VAL("wrong config2", + 1 == evsel->attr.config2); + return 0; +} + static int test__checkevent_symbolic_alias(struct perf_evlist *evlist) { struct perf_evsel *evsel = list_entry(evlist->entries.next, @@ -830,6 +851,28 @@ static int test__checkevent_symbolic_name_modifier(struct perf_evlist *evlist) return test__checkevent_symbolic_name(evlist); } +static int test__checkevent_exclude_host_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + + return test__checkevent_symbolic_name(evlist); +} + +static int test__checkevent_exclude_guest_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + + return test__checkevent_symbolic_name(evlist); +} + static int test__checkevent_symbolic_alias_modifier(struct perf_evlist *evlist) { struct perf_evsel *evsel = list_entry(evlist->entries.next, @@ -856,6 +899,115 @@ static int test__checkevent_genhw_modifier(struct perf_evlist *evlist) return test__checkevent_genhw(evlist); } +static int test__checkevent_breakpoint_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + + return test__checkevent_breakpoint(evlist); +} + +static int test__checkevent_breakpoint_x_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + + return test__checkevent_breakpoint_x(evlist); +} + +static int test__checkevent_breakpoint_r_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + + return test__checkevent_breakpoint_r(evlist); +} + +static int test__checkevent_breakpoint_w_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + + return test__checkevent_breakpoint_w(evlist); +} + +static int test__checkevent_pmu(struct perf_evlist *evlist) +{ + + struct perf_evsel *evsel = list_entry(evlist->entries.next, + struct perf_evsel, node); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 10 == evsel->attr.config); + TEST_ASSERT_VAL("wrong config1", 1 == evsel->attr.config1); + TEST_ASSERT_VAL("wrong config2", 3 == evsel->attr.config2); + TEST_ASSERT_VAL("wrong period", 1000 == evsel->attr.sample_period); + + return 0; +} + +static int test__checkevent_list(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel; + + TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries); + + /* r1 */ + evsel = list_entry(evlist->entries.next, struct perf_evsel, node); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); + TEST_ASSERT_VAL("wrong config1", 0 == evsel->attr.config1); + TEST_ASSERT_VAL("wrong config2", 0 == evsel->attr.config2); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + + /* syscalls:sys_enter_open:k */ + evsel = list_entry(evsel->node.next, struct perf_evsel, node); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong sample_type", + (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | PERF_SAMPLE_CPU) == + evsel->attr.sample_type); + TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->attr.sample_period); + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + + /* 1:1:hp */ + evsel = list_entry(evsel->node.next, struct perf_evsel, node); + TEST_ASSERT_VAL("wrong type", 1 == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + + return 0; +} + static struct test__event_st { const char *name; __u32 type; @@ -870,7 +1022,7 @@ static struct test__event_st { .check = test__checkevent_tracepoint_multi, }, { - .name = "r1", + .name = "r1a", .check = test__checkevent_raw, }, { @@ -882,6 +1034,10 @@ static struct test__event_st { .check = test__checkevent_symbolic_name, }, { + .name = "cycles/period=100000,config2/", + .check = test__checkevent_symbolic_name_config, + }, + { .name = "faults", .check = test__checkevent_symbolic_alias, }, @@ -914,7 +1070,7 @@ static struct test__event_st { .check = test__checkevent_tracepoint_multi_modifier, }, { - .name = "r1:kp", + .name = "r1a:kp", .check = test__checkevent_raw_modifier, }, { @@ -933,6 +1089,38 @@ static struct test__event_st { .name = "L1-dcache-load-miss:kp", .check = test__checkevent_genhw_modifier, }, + { + .name = "mem:0:u", + .check = test__checkevent_breakpoint_modifier, + }, + { + .name = "mem:0:x:k", + .check = test__checkevent_breakpoint_x_modifier, + }, + { + .name = "mem:0:r:hp", + .check = test__checkevent_breakpoint_r_modifier, + }, + { + .name = "mem:0:w:up", + .check = test__checkevent_breakpoint_w_modifier, + }, + { + .name = "cpu/config=10,config1,config2=3,period=1000/u", + .check = test__checkevent_pmu, + }, + { + .name = "r1,syscalls:sys_enter_open:k,1:1:hp", + .check = test__checkevent_list, + }, + { + .name = "instructions:G", + .check = test__checkevent_exclude_host_modifier, + }, + { + .name = "instructions:H", + .check = test__checkevent_exclude_guest_modifier, + }, }; #define TEST__EVENTS_CNT (sizeof(test__events) / sizeof(struct test__event_st)) @@ -958,10 +1146,9 @@ static int test__parse_events(void) } ret = e->check(evlist); + perf_evlist__delete(evlist); if (ret) break; - - perf_evlist__delete(evlist); } return ret; @@ -1008,12 +1195,13 @@ realloc: static int test__PERF_RECORD(void) { struct perf_record_opts opts = { - .target_pid = -1, - .target_tid = -1, + .target = { + .uid = UINT_MAX, + .uses_mmap = true, + }, .no_delay = true, .freq = 10, .mmap_pages = 256, - .sample_id_all_avail = true, }; cpu_set_t *cpu_mask = NULL; size_t cpu_mask_size = 0; @@ -1053,8 +1241,7 @@ static int test__PERF_RECORD(void) * perf_evlist__prepare_workload we'll fill in the only thread * we're monitoring, the one forked there. */ - err = perf_evlist__create_maps(evlist, opts.target_pid, - opts.target_tid, opts.cpu_list); + err = perf_evlist__create_maps(evlist, &opts.target); if (err < 0) { pr_debug("Not enough memory to create thread/cpu maps\n"); goto out_delete_evlist; @@ -1296,6 +1483,176 @@ out: return (err < 0 || errs > 0) ? -1 : 0; } + +#if defined(__x86_64__) || defined(__i386__) + +#define barrier() asm volatile("" ::: "memory") + +static u64 rdpmc(unsigned int counter) +{ + unsigned int low, high; + + asm volatile("rdpmc" : "=a" (low), "=d" (high) : "c" (counter)); + + return low | ((u64)high) << 32; +} + +static u64 rdtsc(void) +{ + unsigned int low, high; + + asm volatile("rdtsc" : "=a" (low), "=d" (high)); + + return low | ((u64)high) << 32; +} + +static u64 mmap_read_self(void *addr) +{ + struct perf_event_mmap_page *pc = addr; + u32 seq, idx, time_mult = 0, time_shift = 0; + u64 count, cyc = 0, time_offset = 0, enabled, running, delta; + + do { + seq = pc->lock; + barrier(); + + enabled = pc->time_enabled; + running = pc->time_running; + + if (enabled != running) { + cyc = rdtsc(); + time_mult = pc->time_mult; + time_shift = pc->time_shift; + time_offset = pc->time_offset; + } + + idx = pc->index; + count = pc->offset; + if (idx) + count += rdpmc(idx - 1); + + barrier(); + } while (pc->lock != seq); + + if (enabled != running) { + u64 quot, rem; + + quot = (cyc >> time_shift); + rem = cyc & ((1 << time_shift) - 1); + delta = time_offset + quot * time_mult + + ((rem * time_mult) >> time_shift); + + enabled += delta; + if (idx) + running += delta; + + quot = count / running; + rem = count % running; + count = quot * enabled + (rem * enabled) / running; + } + + return count; +} + +/* + * If the RDPMC instruction faults then signal this back to the test parent task: + */ +static void segfault_handler(int sig __used, siginfo_t *info __used, void *uc __used) +{ + exit(-1); +} + +static int __test__rdpmc(void) +{ + long page_size = sysconf(_SC_PAGE_SIZE); + volatile int tmp = 0; + u64 i, loops = 1000; + int n; + int fd; + void *addr; + struct perf_event_attr attr = { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_INSTRUCTIONS, + .exclude_kernel = 1, + }; + u64 delta_sum = 0; + struct sigaction sa; + + sigfillset(&sa.sa_mask); + sa.sa_sigaction = segfault_handler; + sigaction(SIGSEGV, &sa, NULL); + + fd = sys_perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + die("Error: sys_perf_event_open() syscall returned " + "with %d (%s)\n", fd, strerror(errno)); + } + + addr = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0); + if (addr == (void *)(-1)) { + die("Error: mmap() syscall returned " + "with (%s)\n", strerror(errno)); + } + + for (n = 0; n < 6; n++) { + u64 stamp, now, delta; + + stamp = mmap_read_self(addr); + + for (i = 0; i < loops; i++) + tmp++; + + now = mmap_read_self(addr); + loops *= 10; + + delta = now - stamp; + pr_debug("%14d: %14Lu\n", n, (long long)delta); + + delta_sum += delta; + } + + munmap(addr, page_size); + close(fd); + + pr_debug(" "); + + if (!delta_sum) + return -1; + + return 0; +} + +static int test__rdpmc(void) +{ + int status = 0; + int wret = 0; + int ret; + int pid; + + pid = fork(); + if (pid < 0) + return -1; + + if (!pid) { + ret = __test__rdpmc(); + + exit(ret); + } + + wret = waitpid(pid, &status, 0); + if (wret < 0 || status) + return -1; + + return 0; +} + +#endif + +static int test__perf_pmu(void) +{ + return perf_pmu__test(); +} + static struct test { const char *desc; int (*func)(void); @@ -1320,11 +1677,21 @@ static struct test { .desc = "parse events tests", .func = test__parse_events, }, +#if defined(__x86_64__) || defined(__i386__) + { + .desc = "x86 rdpmc test", + .func = test__rdpmc, + }, +#endif { .desc = "Validate PERF_RECORD_* events & perf_sample fields", .func = test__PERF_RECORD, }, { + .desc = "Test perf pmu format parsing", + .func = test__perf_pmu, + }, + { .func = NULL, }, }; @@ -1412,7 +1779,5 @@ int cmd_test(int argc, const char **argv, const char *prefix __used) if (symbol__init() < 0) return -1; - setup_pager(); - return __cmd_test(argc, argv); } diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index dd162aa24baad..3e981a710c4d2 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -42,6 +42,7 @@ #include "util/debug.h" #include <assert.h> +#include <elf.h> #include <fcntl.h> #include <stdio.h> @@ -59,12 +60,12 @@ #include <sys/prctl.h> #include <sys/wait.h> #include <sys/uio.h> +#include <sys/utsname.h> #include <sys/mman.h> #include <linux/unistd.h> #include <linux/types.h> - void get_term_dimensions(struct winsize *ws) { char *s = getenv("LINES"); @@ -163,12 +164,40 @@ static void __zero_source_counters(struct hist_entry *he) symbol__annotate_zero_histograms(sym); } +static void ui__warn_map_erange(struct map *map, struct symbol *sym, u64 ip) +{ + struct utsname uts; + int err = uname(&uts); + + ui__warning("Out of bounds address found:\n\n" + "Addr: %" PRIx64 "\n" + "DSO: %s %c\n" + "Map: %" PRIx64 "-%" PRIx64 "\n" + "Symbol: %" PRIx64 "-%" PRIx64 " %c %s\n" + "Arch: %s\n" + "Kernel: %s\n" + "Tools: %s\n\n" + "Not all samples will be on the annotation output.\n\n" + "Please report to linux-kernel@vger.kernel.org\n", + ip, map->dso->long_name, dso__symtab_origin(map->dso), + map->start, map->end, sym->start, sym->end, + sym->binding == STB_GLOBAL ? 'g' : + sym->binding == STB_LOCAL ? 'l' : 'w', sym->name, + err ? "[unknown]" : uts.machine, + err ? "[unknown]" : uts.release, perf_version_string); + if (use_browser <= 0) + sleep(5); + + map->erange_warned = true; +} + static void perf_top__record_precise_ip(struct perf_top *top, struct hist_entry *he, int counter, u64 ip) { struct annotation *notes; struct symbol *sym; + int err; if (he == NULL || he->ms.sym == NULL || ((top->sym_filter_entry == NULL || @@ -190,9 +219,12 @@ static void perf_top__record_precise_ip(struct perf_top *top, } ip = he->ms.map->map_ip(he->ms.map, ip); - symbol__inc_addr_samples(sym, he->ms.map, counter, ip); + err = symbol__inc_addr_samples(sym, he->ms.map, counter, ip); pthread_mutex_unlock(¬es->lock); + + if (err == -ERANGE && !he->ms.map->erange_warned) + ui__warn_map_erange(he->ms.map, sym, ip); } static void perf_top__show_details(struct perf_top *top) @@ -544,10 +576,20 @@ static void perf_top__sort_new_samples(void *arg) static void *display_thread_tui(void *arg) { + struct perf_evsel *pos; struct perf_top *top = arg; const char *help = "For a higher level overview, try: perf top --sort comm,dso"; perf_top__sort_new_samples(top); + + /* + * Initialize the uid_filter_str, in the future the TUI will allow + * Zooming in/out UIDs. For now juse use whatever the user passed + * via --uid. + */ + list_for_each_entry(pos, &top->evlist->entries, node) + pos->hists.uid_filter_str = top->target.uid_str; + perf_evlist__tui_browse_hists(top->evlist, help, perf_top__sort_new_samples, top, top->delay_secs); @@ -606,6 +648,7 @@ process_hotkey: /* Tag samples to be skipped. */ static const char *skip_symbols[] = { + "intel_idle", "default_idle", "native_safe_halt", "cpu_idle", @@ -668,6 +711,12 @@ static void perf_event__process_sample(struct perf_tool *tool, return; } + if (!machine) { + pr_err("%u unprocessable samples recorded.", + top->session->hists.stats.nr_unprocessable_samples++); + return; + } + if (event->header.misc & PERF_RECORD_MISC_EXACT_IP) top->exact_samples++; @@ -857,8 +906,11 @@ static void perf_top__start_counters(struct perf_top *top) attr->mmap = 1; attr->comm = 1; attr->inherit = top->inherit; +fallback_missing_features: + if (top->exclude_guest_missing) + attr->exclude_guest = attr->exclude_host = 0; retry_sample_id: - attr->sample_id_all = top->sample_id_all_avail ? 1 : 0; + attr->sample_id_all = top->sample_id_all_missing ? 0 : 1; try_again: if (perf_evsel__open(counter, top->evlist->cpus, top->evlist->threads, top->group, @@ -868,12 +920,20 @@ try_again: if (err == EPERM || err == EACCES) { ui__error_paranoid(); goto out_err; - } else if (err == EINVAL && top->sample_id_all_avail) { - /* - * Old kernel, no attr->sample_id_type_all field - */ - top->sample_id_all_avail = false; - goto retry_sample_id; + } else if (err == EINVAL) { + if (!top->exclude_guest_missing && + (attr->exclude_guest || attr->exclude_host)) { + pr_debug("Old kernel, cannot exclude " + "guest or host samples.\n"); + top->exclude_guest_missing = true; + goto fallback_missing_features; + } else if (!top->sample_id_all_missing) { + /* + * Old kernel, no attr->sample_id_type_all field + */ + top->sample_id_all_missing = true; + goto retry_sample_id; + } } /* * If it's cycles then fall back to hrtimer @@ -888,6 +948,10 @@ try_again: attr->type = PERF_TYPE_SOFTWARE; attr->config = PERF_COUNT_SW_CPU_CLOCK; + if (counter->name) { + free(counter->name); + counter->name = strdup(event_name(counter)); + } goto try_again; } @@ -956,7 +1020,7 @@ static int __cmd_top(struct perf_top *top) if (ret) goto out_delete; - if (top->target_tid != -1) + if (perf_target__has_task(&top->target)) perf_event__synthesize_thread_map(&top->tool, top->evlist->threads, perf_event__process, &top->session->host_machine); @@ -1090,16 +1154,17 @@ static const char * const top_usage[] = { int cmd_top(int argc, const char **argv, const char *prefix __used) { struct perf_evsel *pos; - int status = -ENOMEM; + int status; + char errbuf[BUFSIZ]; struct perf_top top = { .count_filter = 5, .delay_secs = 2, - .target_pid = -1, - .target_tid = -1, .freq = 1000, /* 1 KHz */ - .sample_id_all_avail = true, .mmap_pages = 128, .sym_pcnt_filter = 5, + .target = { + .uses_mmap = true, + }, }; char callchain_default_opt[] = "fractal,0.5,callee"; const struct option options[] = { @@ -1108,13 +1173,13 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) parse_events_option), OPT_INTEGER('c', "count", &top.default_interval, "event period to sample"), - OPT_INTEGER('p', "pid", &top.target_pid, + OPT_STRING('p', "pid", &top.target.pid, "pid", "profile events on existing process id"), - OPT_INTEGER('t', "tid", &top.target_tid, + OPT_STRING('t', "tid", &top.target.tid, "tid", "profile events on existing thread id"), - OPT_BOOLEAN('a', "all-cpus", &top.system_wide, + OPT_BOOLEAN('a', "all-cpus", &top.target.system_wide, "system-wide collection from all CPUs"), - OPT_STRING('C', "cpu", &top.cpu_list, "cpu", + OPT_STRING('C', "cpu", &top.target.cpu_list, "cpu", "list of cpus to monitor"), OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, "file", "vmlinux pathname"), @@ -1169,6 +1234,7 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) "Display raw encoding of assembly instructions (default)"), OPT_STRING('M', "disassembler-style", &disassembler_style, "disassembler style", "Specify disassembler style (e.g. -M intel for intel syntax)"), + OPT_STRING('u', "uid", &top.target.uid_str, "user", "user to profile"), OPT_END() }; @@ -1194,18 +1260,27 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) setup_browser(false); - /* CPU and PID are mutually exclusive */ - if (top.target_tid > 0 && top.cpu_list) { - printf("WARNING: PID switch overriding CPU\n"); - sleep(1); - top.cpu_list = NULL; + status = perf_target__validate(&top.target); + if (status) { + perf_target__strerror(&top.target, status, errbuf, BUFSIZ); + ui__warning("%s", errbuf); + } + + status = perf_target__parse_uid(&top.target); + if (status) { + int saved_errno = errno; + + perf_target__strerror(&top.target, status, errbuf, BUFSIZ); + ui__warning("%s", errbuf); + + status = -saved_errno; + goto out_delete_evlist; } - if (top.target_pid != -1) - top.target_tid = top.target_pid; + if (perf_target__none(&top.target)) + top.target.system_wide = true; - if (perf_evlist__create_maps(top.evlist, top.target_pid, - top.target_tid, top.cpu_list) < 0) + if (perf_evlist__create_maps(top.evlist, &top.target) < 0) usage_with_options(top_usage, options); if (!top.evlist->nr_entries && @@ -1269,6 +1344,7 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) status = __cmd_top(&top); +out_delete_evlist: perf_evlist__delete(top.evlist); return status; diff --git a/tools/perf/config/feature-tests.mak b/tools/perf/config/feature-tests.mak index 6170fd2531b5e..d9084e03ce566 100644 --- a/tools/perf/config/feature-tests.mak +++ b/tools/perf/config/feature-tests.mak @@ -65,6 +65,21 @@ int main(void) endef endif +ifndef NO_GTK2 +define SOURCE_GTK2 +#pragma GCC diagnostic ignored \"-Wstrict-prototypes\" +#include <gtk/gtk.h> +#pragma GCC diagnostic error \"-Wstrict-prototypes\" + +int main(int argc, char *argv[]) +{ + gtk_init(&argc, &argv); + + return 0; +} +endef +endif + ifndef NO_LIBPERL define SOURCE_PERL_EMBED #include <EXTERN.h> diff --git a/tools/perf/perf-archive.sh b/tools/perf/perf-archive.sh index 677e59d62a8dc..95b6f8b6177a9 100644 --- a/tools/perf/perf-archive.sh +++ b/tools/perf/perf-archive.sh @@ -29,13 +29,14 @@ if [ ! -s $BUILDIDS ] ; then fi MANIFEST=$(mktemp /tmp/perf-archive-manifest.XXXXXX) +PERF_BUILDID_LINKDIR=$(readlink -f $PERF_BUILDID_DIR)/ cut -d ' ' -f 1 $BUILDIDS | \ while read build_id ; do linkname=$PERF_BUILDID_DIR.build-id/${build_id:0:2}/${build_id:2} filename=$(readlink -f $linkname) echo ${linkname#$PERF_BUILDID_DIR} >> $MANIFEST - echo ${filename#$PERF_BUILDID_DIR} >> $MANIFEST + echo ${filename#$PERF_BUILDID_LINKDIR} >> $MANIFEST done tar cfj $PERF_DATA.tar.bz2 -C $PERF_BUILDID_DIR -T $MANIFEST diff --git a/tools/perf/perf.h b/tools/perf/perf.h index 64f8bee31ced8..14f1034f14f93 100644 --- a/tools/perf/perf.h +++ b/tools/perf/perf.h @@ -10,6 +10,9 @@ void get_term_dimensions(struct winsize *ws); #define rmb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory") #define cpu_relax() asm volatile("rep; nop" ::: "memory"); #define CPUINFO_PROC "model name" +#ifndef __NR_perf_event_open +# define __NR_perf_event_open 336 +#endif #endif #if defined(__x86_64__) @@ -17,6 +20,9 @@ void get_term_dimensions(struct winsize *ws); #define rmb() asm volatile("lfence" ::: "memory") #define cpu_relax() asm volatile("rep; nop" ::: "memory"); #define CPUINFO_PROC "model name" +#ifndef __NR_perf_event_open +# define __NR_perf_event_open 298 +#endif #endif #ifdef __powerpc__ @@ -167,7 +173,6 @@ sys_perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu, int group_fd, unsigned long flags) { - attr->size = sizeof(*attr); return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags); } @@ -180,14 +185,32 @@ struct ip_callchain { u64 ips[0]; }; +struct branch_flags { + u64 mispred:1; + u64 predicted:1; + u64 reserved:62; +}; + +struct branch_entry { + u64 from; + u64 to; + struct branch_flags flags; +}; + +struct branch_stack { + u64 nr; + struct branch_entry entries[0]; +}; + extern bool perf_host, perf_guest; extern const char perf_version_string[]; void pthread__unblock_sigwinch(void); +#include "util/target.h" + struct perf_record_opts { - pid_t target_pid; - pid_t target_tid; + struct perf_target target; bool call_graph; bool group; bool inherit_stat; @@ -198,15 +221,15 @@ struct perf_record_opts { bool raw_samples; bool sample_address; bool sample_time; - bool sample_id_all_avail; - bool system_wide; + bool sample_id_all_missing; + bool exclude_guest_missing; bool period; unsigned int freq; unsigned int mmap_pages; unsigned int user_freq; + int branch_stack; u64 default_interval; u64 user_interval; - const char *cpu_list; }; #endif diff --git a/tools/perf/python/twatch.py b/tools/perf/python/twatch.py index df638c438a9f9..b11cca584238e 100755 --- a/tools/perf/python/twatch.py +++ b/tools/perf/python/twatch.py @@ -19,7 +19,7 @@ def main(): cpus = perf.cpu_map() threads = perf.thread_map() evsel = perf.evsel(task = 1, comm = 1, mmap = 0, - wakeup_events = 1, sample_period = 1, + wakeup_events = 1, watermark = 1, sample_id_all = 1, sample_type = perf.SAMPLE_PERIOD | perf.SAMPLE_TID | perf.SAMPLE_CPU | perf.SAMPLE_TID) evsel.open(cpus = cpus, threads = threads); diff --git a/tools/perf/util/ui/browser.c b/tools/perf/ui/browser.c index 556829124b024..cde4d0f0ddb99 100644 --- a/tools/perf/util/ui/browser.c +++ b/tools/perf/ui/browser.c @@ -27,9 +27,12 @@ static int ui_browser__percent_color(struct ui_browser *browser, return HE_COLORSET_NORMAL; } -void ui_browser__set_color(struct ui_browser *self __used, int color) +int ui_browser__set_color(struct ui_browser *browser, int color) { + int ret = browser->current_color; + browser->current_color = color; SLsmg_set_color(color); + return ret; } void ui_browser__set_percent_color(struct ui_browser *self, @@ -503,6 +506,12 @@ static struct ui_browser__colorset { .bg = "default", }, { + .colorset = HE_COLORSET_ADDR, + .name = "addr", + .fg = "magenta", + .bg = "default", + }, + { .name = NULL, } }; @@ -584,6 +593,111 @@ unsigned int ui_browser__argv_refresh(struct ui_browser *browser) return row; } +void __ui_browser__vline(struct ui_browser *browser, unsigned int column, + u16 start, u16 end) +{ + SLsmg_set_char_set(1); + ui_browser__gotorc(browser, start, column); + SLsmg_draw_vline(end - start + 1); + SLsmg_set_char_set(0); +} + +void ui_browser__write_graph(struct ui_browser *browser __used, int graph) +{ + SLsmg_set_char_set(1); + SLsmg_write_char(graph); + SLsmg_set_char_set(0); +} + +static void __ui_browser__line_arrow_up(struct ui_browser *browser, + unsigned int column, + u64 start, u64 end) +{ + unsigned int row, end_row; + + SLsmg_set_char_set(1); + + if (start < browser->top_idx + browser->height) { + row = start - browser->top_idx; + ui_browser__gotorc(browser, row, column); + SLsmg_write_char(SLSMG_LLCORN_CHAR); + ui_browser__gotorc(browser, row, column + 1); + SLsmg_draw_hline(2); + + if (row-- == 0) + goto out; + } else + row = browser->height - 1; + + if (end > browser->top_idx) + end_row = end - browser->top_idx; + else + end_row = 0; + + ui_browser__gotorc(browser, end_row, column); + SLsmg_draw_vline(row - end_row + 1); + + ui_browser__gotorc(browser, end_row, column); + if (end >= browser->top_idx) { + SLsmg_write_char(SLSMG_ULCORN_CHAR); + ui_browser__gotorc(browser, end_row, column + 1); + SLsmg_write_char(SLSMG_HLINE_CHAR); + ui_browser__gotorc(browser, end_row, column + 2); + SLsmg_write_char(SLSMG_RARROW_CHAR); + } +out: + SLsmg_set_char_set(0); +} + +static void __ui_browser__line_arrow_down(struct ui_browser *browser, + unsigned int column, + u64 start, u64 end) +{ + unsigned int row, end_row; + + SLsmg_set_char_set(1); + + if (start >= browser->top_idx) { + row = start - browser->top_idx; + ui_browser__gotorc(browser, row, column); + SLsmg_write_char(SLSMG_ULCORN_CHAR); + ui_browser__gotorc(browser, row, column + 1); + SLsmg_draw_hline(2); + + if (row++ == 0) + goto out; + } else + row = 0; + + if (end >= browser->top_idx + browser->height) + end_row = browser->height - 1; + else + end_row = end - browser->top_idx;; + + ui_browser__gotorc(browser, row, column); + SLsmg_draw_vline(end_row - row + 1); + + ui_browser__gotorc(browser, end_row, column); + if (end < browser->top_idx + browser->height) { + SLsmg_write_char(SLSMG_LLCORN_CHAR); + ui_browser__gotorc(browser, end_row, column + 1); + SLsmg_write_char(SLSMG_HLINE_CHAR); + ui_browser__gotorc(browser, end_row, column + 2); + SLsmg_write_char(SLSMG_RARROW_CHAR); + } +out: + SLsmg_set_char_set(0); +} + +void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column, + u64 start, u64 end) +{ + if (start > end) + __ui_browser__line_arrow_up(browser, column, start, end); + else + __ui_browser__line_arrow_down(browser, column, start, end); +} + void ui_browser__init(void) { int i = 0; diff --git a/tools/perf/util/ui/browser.h b/tools/perf/ui/browser.h index 84d761b730c10..dd96d82299022 100644 --- a/tools/perf/util/ui/browser.h +++ b/tools/perf/ui/browser.h @@ -10,11 +10,13 @@ #define HE_COLORSET_NORMAL 52 #define HE_COLORSET_SELECTED 53 #define HE_COLORSET_CODE 54 +#define HE_COLORSET_ADDR 55 struct ui_browser { u64 index, top_idx; void *top, *entries; u16 y, x, width, height; + int current_color; void *priv; const char *title; char *helpline; @@ -27,7 +29,7 @@ struct ui_browser { bool use_navkeypressed; }; -void ui_browser__set_color(struct ui_browser *self, int color); +int ui_browser__set_color(struct ui_browser *browser, 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); @@ -35,6 +37,9 @@ 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__write_graph(struct ui_browser *browser, int graph); +void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column, + u64 start, u64 end); void __ui_browser__show_title(struct ui_browser *browser, const char *title); void ui_browser__show_title(struct ui_browser *browser, const char *title); int ui_browser__show(struct ui_browser *self, const char *title, @@ -44,11 +49,15 @@ int ui_browser__refresh(struct ui_browser *self); int ui_browser__run(struct ui_browser *browser, int delay_secs); void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries); void ui_browser__handle_resize(struct ui_browser *browser); +void __ui_browser__vline(struct ui_browser *browser, unsigned int column, + u16 start, u16 end); int ui_browser__warning(struct ui_browser *browser, int timeout, const char *format, ...); int ui_browser__help_window(struct ui_browser *browser, const char *text); bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text); +int ui_browser__input_window(const char *title, const char *text, char *input, + const char *exit_msg, int delay_sec); void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence); unsigned int ui_browser__argv_refresh(struct ui_browser *browser); diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c new file mode 100644 index 0000000000000..6e0ef79be1690 --- /dev/null +++ b/tools/perf/ui/browsers/annotate.c @@ -0,0 +1,867 @@ +#include "../../util/util.h" +#include "../browser.h" +#include "../helpline.h" +#include "../libslang.h" +#include "../ui.h" +#include "../util.h" +#include "../../util/annotate.h" +#include "../../util/hist.h" +#include "../../util/sort.h" +#include "../../util/symbol.h" +#include <pthread.h> +#include <newt.h> + +struct browser_disasm_line { + struct rb_node rb_node; + double percent; + u32 idx; + int idx_asm; + int jump_sources; +}; + +struct annotate_browser { + struct ui_browser b; + struct rb_root entries; + struct rb_node *curr_hot; + struct disasm_line *selection; + struct disasm_line **offsets; + u64 start; + int nr_asm_entries; + int nr_entries; + int max_jump_sources; + int nr_jumps; + bool hide_src_code; + bool use_offset; + bool jump_arrows; + bool show_nr_jumps; + bool searching_backwards; + u8 addr_width; + u8 jumps_width; + u8 target_width; + u8 min_addr_width; + u8 max_addr_width; + char search_bf[128]; +}; + +static inline struct browser_disasm_line *disasm_line__browser(struct disasm_line *dl) +{ + return (struct browser_disasm_line *)(dl + 1); +} + +static bool disasm_line__filter(struct ui_browser *browser, void *entry) +{ + struct annotate_browser *ab = container_of(browser, struct annotate_browser, b); + + if (ab->hide_src_code) { + struct disasm_line *dl = list_entry(entry, struct disasm_line, node); + return dl->offset == -1; + } + + return false; +} + +static int annotate_browser__jumps_percent_color(struct annotate_browser *browser, + int nr, bool current) +{ + if (current && (!browser->b.use_navkeypressed || browser->b.navkeypressed)) + return HE_COLORSET_SELECTED; + if (nr == browser->max_jump_sources) + return HE_COLORSET_TOP; + if (nr > 1) + return HE_COLORSET_MEDIUM; + return HE_COLORSET_NORMAL; +} + +static int annotate_browser__set_jumps_percent_color(struct annotate_browser *browser, + int nr, bool current) +{ + int color = annotate_browser__jumps_percent_color(browser, nr, current); + return ui_browser__set_color(&browser->b, color); +} + +static void annotate_browser__write(struct ui_browser *self, void *entry, int row) +{ + struct annotate_browser *ab = container_of(self, struct annotate_browser, b); + struct disasm_line *dl = list_entry(entry, struct disasm_line, node); + struct browser_disasm_line *bdl = disasm_line__browser(dl); + bool current_entry = ui_browser__is_current_entry(self, row); + bool change_color = (!ab->hide_src_code && + (!current_entry || (self->use_navkeypressed && + !self->navkeypressed))); + int width = self->width, printed; + char bf[256]; + + if (dl->offset != -1 && bdl->percent != 0.0) { + ui_browser__set_percent_color(self, bdl->percent, current_entry); + slsmg_printf("%6.2f ", bdl->percent); + } else { + ui_browser__set_percent_color(self, 0, current_entry); + slsmg_write_nstring(" ", 7); + } + + SLsmg_write_char(' '); + + /* The scroll bar isn't being used */ + if (!self->navkeypressed) + width += 1; + + if (!*dl->line) + slsmg_write_nstring(" ", width - 7); + else if (dl->offset == -1) { + printed = scnprintf(bf, sizeof(bf), "%*s ", + ab->addr_width, " "); + slsmg_write_nstring(bf, printed); + slsmg_write_nstring(dl->line, width - printed - 6); + } else { + u64 addr = dl->offset; + int color = -1; + + if (!ab->use_offset) + addr += ab->start; + + if (!ab->use_offset) { + printed = scnprintf(bf, sizeof(bf), "%" PRIx64 ": ", addr); + } else { + if (bdl->jump_sources) { + if (ab->show_nr_jumps) { + int prev; + printed = scnprintf(bf, sizeof(bf), "%*d ", + ab->jumps_width, + bdl->jump_sources); + prev = annotate_browser__set_jumps_percent_color(ab, bdl->jump_sources, + current_entry); + slsmg_write_nstring(bf, printed); + ui_browser__set_color(self, prev); + } + + printed = scnprintf(bf, sizeof(bf), "%*" PRIx64 ": ", + ab->target_width, addr); + } else { + printed = scnprintf(bf, sizeof(bf), "%*s ", + ab->addr_width, " "); + } + } + + if (change_color) + color = ui_browser__set_color(self, HE_COLORSET_ADDR); + slsmg_write_nstring(bf, printed); + if (change_color) + ui_browser__set_color(self, color); + if (dl->ins && dl->ins->ops->scnprintf) { + if (ins__is_jump(dl->ins)) { + bool fwd = dl->ops.target.offset > (u64)dl->offset; + + ui_browser__write_graph(self, fwd ? SLSMG_DARROW_CHAR : + SLSMG_UARROW_CHAR); + SLsmg_write_char(' '); + } else if (ins__is_call(dl->ins)) { + ui_browser__write_graph(self, SLSMG_RARROW_CHAR); + SLsmg_write_char(' '); + } else { + slsmg_write_nstring(" ", 2); + } + } else { + if (strcmp(dl->name, "retq")) { + slsmg_write_nstring(" ", 2); + } else { + ui_browser__write_graph(self, SLSMG_LARROW_CHAR); + SLsmg_write_char(' '); + } + } + + disasm_line__scnprintf(dl, bf, sizeof(bf), !ab->use_offset); + slsmg_write_nstring(bf, width - 10 - printed); + } + + if (current_entry) + ab->selection = dl; +} + +static void annotate_browser__draw_current_jump(struct ui_browser *browser) +{ + struct annotate_browser *ab = container_of(browser, struct annotate_browser, b); + struct disasm_line *cursor = ab->selection, *target; + struct browser_disasm_line *btarget, *bcursor; + unsigned int from, to; + + if (!cursor->ins || !ins__is_jump(cursor->ins) || + !disasm_line__has_offset(cursor)) + return; + + target = ab->offsets[cursor->ops.target.offset]; + if (!target) + return; + + bcursor = disasm_line__browser(cursor); + btarget = disasm_line__browser(target); + + if (ab->hide_src_code) { + from = bcursor->idx_asm; + to = btarget->idx_asm; + } else { + from = (u64)bcursor->idx; + to = (u64)btarget->idx; + } + + ui_browser__set_color(browser, HE_COLORSET_CODE); + __ui_browser__line_arrow(browser, 9 + ab->addr_width, from, to); +} + +static unsigned int annotate_browser__refresh(struct ui_browser *browser) +{ + struct annotate_browser *ab = container_of(browser, struct annotate_browser, b); + int ret = ui_browser__list_head_refresh(browser); + + if (ab->jump_arrows) + annotate_browser__draw_current_jump(browser); + + ui_browser__set_color(browser, HE_COLORSET_NORMAL); + __ui_browser__vline(browser, 7, 0, browser->height - 1); + return ret; +} + +static double disasm_line__calc_percent(struct disasm_line *dl, struct symbol *sym, int evidx) +{ + double percent = 0.0; + + if (dl->offset != -1) { + int len = sym->end - sym->start; + unsigned int hits = 0; + struct annotation *notes = symbol__annotation(sym); + struct source_line *src_line = notes->src->lines; + struct sym_hist *h = annotation__histogram(notes, evidx); + s64 offset = dl->offset; + struct disasm_line *next; + + next = disasm__get_next_ip_line(¬es->src->source, dl); + while (offset < (s64)len && + (next == NULL || offset < next->offset)) { + if (src_line) { + percent += src_line[offset].percent; + } else + hits += h->addr[offset]; + + ++offset; + } + /* + * If the percentage wasn't already calculated in + * symbol__get_source_line, do it now: + */ + if (src_line == NULL && h->sum) + percent = 100.0 * hits / h->sum; + } + + return percent; +} + +static void disasm_rb_tree__insert(struct rb_root *root, struct browser_disasm_line *bdl) +{ + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + struct browser_disasm_line *l; + + while (*p != NULL) { + parent = *p; + l = rb_entry(parent, struct browser_disasm_line, rb_node); + if (bdl->percent < l->percent) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + rb_link_node(&bdl->rb_node, parent, p); + rb_insert_color(&bdl->rb_node, root); +} + +static void annotate_browser__set_top(struct annotate_browser *self, + struct disasm_line *pos, u32 idx) +{ + unsigned back; + + ui_browser__refresh_dimensions(&self->b); + back = self->b.height / 2; + self->b.top_idx = self->b.index = idx; + + while (self->b.top_idx != 0 && back != 0) { + pos = list_entry(pos->node.prev, struct disasm_line, node); + + if (disasm_line__filter(&self->b, &pos->node)) + continue; + + --self->b.top_idx; + --back; + } + + self->b.top = pos; + self->b.navkeypressed = true; +} + +static void annotate_browser__set_rb_top(struct annotate_browser *browser, + struct rb_node *nd) +{ + struct browser_disasm_line *bpos; + struct disasm_line *pos; + + bpos = rb_entry(nd, struct browser_disasm_line, rb_node); + pos = ((struct disasm_line *)bpos) - 1; + annotate_browser__set_top(browser, pos, bpos->idx); + browser->curr_hot = nd; +} + +static void annotate_browser__calc_percent(struct annotate_browser *browser, + int evidx) +{ + struct map_symbol *ms = browser->b.priv; + struct symbol *sym = ms->sym; + struct annotation *notes = symbol__annotation(sym); + struct disasm_line *pos; + + browser->entries = RB_ROOT; + + pthread_mutex_lock(¬es->lock); + + list_for_each_entry(pos, ¬es->src->source, node) { + struct browser_disasm_line *bpos = disasm_line__browser(pos); + bpos->percent = disasm_line__calc_percent(pos, sym, evidx); + if (bpos->percent < 0.01) { + RB_CLEAR_NODE(&bpos->rb_node); + continue; + } + disasm_rb_tree__insert(&browser->entries, bpos); + } + pthread_mutex_unlock(¬es->lock); + + browser->curr_hot = rb_last(&browser->entries); +} + +static bool annotate_browser__toggle_source(struct annotate_browser *browser) +{ + struct disasm_line *dl; + struct browser_disasm_line *bdl; + off_t offset = browser->b.index - browser->b.top_idx; + + browser->b.seek(&browser->b, offset, SEEK_CUR); + dl = list_entry(browser->b.top, struct disasm_line, node); + bdl = disasm_line__browser(dl); + + if (browser->hide_src_code) { + if (bdl->idx_asm < offset) + offset = bdl->idx; + + browser->b.nr_entries = browser->nr_entries; + browser->hide_src_code = false; + browser->b.seek(&browser->b, -offset, SEEK_CUR); + browser->b.top_idx = bdl->idx - offset; + browser->b.index = bdl->idx; + } else { + if (bdl->idx_asm < 0) { + ui_helpline__puts("Only available for assembly lines."); + browser->b.seek(&browser->b, -offset, SEEK_CUR); + return false; + } + + if (bdl->idx_asm < offset) + offset = bdl->idx_asm; + + browser->b.nr_entries = browser->nr_asm_entries; + browser->hide_src_code = true; + browser->b.seek(&browser->b, -offset, SEEK_CUR); + browser->b.top_idx = bdl->idx_asm - offset; + browser->b.index = bdl->idx_asm; + } + + return true; +} + +static bool annotate_browser__callq(struct annotate_browser *browser, + int evidx, void (*timer)(void *arg), + void *arg, int delay_secs) +{ + struct map_symbol *ms = browser->b.priv; + struct disasm_line *dl = browser->selection; + struct symbol *sym = ms->sym; + struct annotation *notes; + struct symbol *target; + u64 ip; + + if (!ins__is_call(dl->ins)) + return false; + + ip = ms->map->map_ip(ms->map, dl->ops.target.addr); + target = map__find_symbol(ms->map, ip, NULL); + if (target == NULL) { + ui_helpline__puts("The called function was not found."); + return true; + } + + notes = symbol__annotation(target); + pthread_mutex_lock(¬es->lock); + + if (notes->src == NULL && symbol__alloc_hist(target) < 0) { + pthread_mutex_unlock(¬es->lock); + ui__warning("Not enough memory for annotating '%s' symbol!\n", + target->name); + return true; + } + + pthread_mutex_unlock(¬es->lock); + symbol__tui_annotate(target, ms->map, evidx, timer, arg, delay_secs); + ui_browser__show_title(&browser->b, sym->name); + return true; +} + +static +struct disasm_line *annotate_browser__find_offset(struct annotate_browser *browser, + s64 offset, s64 *idx) +{ + struct map_symbol *ms = browser->b.priv; + struct symbol *sym = ms->sym; + struct annotation *notes = symbol__annotation(sym); + struct disasm_line *pos; + + *idx = 0; + list_for_each_entry(pos, ¬es->src->source, node) { + if (pos->offset == offset) + return pos; + if (!disasm_line__filter(&browser->b, &pos->node)) + ++*idx; + } + + return NULL; +} + +static bool annotate_browser__jump(struct annotate_browser *browser) +{ + struct disasm_line *dl = browser->selection; + s64 idx; + + if (!ins__is_jump(dl->ins)) + return false; + + dl = annotate_browser__find_offset(browser, dl->ops.target.offset, &idx); + if (dl == NULL) { + ui_helpline__puts("Invallid jump offset"); + return true; + } + + annotate_browser__set_top(browser, dl, idx); + + return true; +} + +static +struct disasm_line *annotate_browser__find_string(struct annotate_browser *browser, + char *s, s64 *idx) +{ + struct map_symbol *ms = browser->b.priv; + struct symbol *sym = ms->sym; + struct annotation *notes = symbol__annotation(sym); + struct disasm_line *pos = browser->selection; + + *idx = browser->b.index; + list_for_each_entry_continue(pos, ¬es->src->source, node) { + if (disasm_line__filter(&browser->b, &pos->node)) + continue; + + ++*idx; + + if (pos->line && strstr(pos->line, s) != NULL) + return pos; + } + + return NULL; +} + +static bool __annotate_browser__search(struct annotate_browser *browser) +{ + struct disasm_line *dl; + s64 idx; + + dl = annotate_browser__find_string(browser, browser->search_bf, &idx); + if (dl == NULL) { + ui_helpline__puts("String not found!"); + return false; + } + + annotate_browser__set_top(browser, dl, idx); + browser->searching_backwards = false; + return true; +} + +static +struct disasm_line *annotate_browser__find_string_reverse(struct annotate_browser *browser, + char *s, s64 *idx) +{ + struct map_symbol *ms = browser->b.priv; + struct symbol *sym = ms->sym; + struct annotation *notes = symbol__annotation(sym); + struct disasm_line *pos = browser->selection; + + *idx = browser->b.index; + list_for_each_entry_continue_reverse(pos, ¬es->src->source, node) { + if (disasm_line__filter(&browser->b, &pos->node)) + continue; + + --*idx; + + if (pos->line && strstr(pos->line, s) != NULL) + return pos; + } + + return NULL; +} + +static bool __annotate_browser__search_reverse(struct annotate_browser *browser) +{ + struct disasm_line *dl; + s64 idx; + + dl = annotate_browser__find_string_reverse(browser, browser->search_bf, &idx); + if (dl == NULL) { + ui_helpline__puts("String not found!"); + return false; + } + + annotate_browser__set_top(browser, dl, idx); + browser->searching_backwards = true; + return true; +} + +static bool annotate_browser__search_window(struct annotate_browser *browser, + int delay_secs) +{ + if (ui_browser__input_window("Search", "String: ", browser->search_bf, + "ENTER: OK, ESC: Cancel", + delay_secs * 2) != K_ENTER || + !*browser->search_bf) + return false; + + return true; +} + +static bool annotate_browser__search(struct annotate_browser *browser, int delay_secs) +{ + if (annotate_browser__search_window(browser, delay_secs)) + return __annotate_browser__search(browser); + + return false; +} + +static bool annotate_browser__continue_search(struct annotate_browser *browser, + int delay_secs) +{ + if (!*browser->search_bf) + return annotate_browser__search(browser, delay_secs); + + return __annotate_browser__search(browser); +} + +static bool annotate_browser__search_reverse(struct annotate_browser *browser, + int delay_secs) +{ + if (annotate_browser__search_window(browser, delay_secs)) + return __annotate_browser__search_reverse(browser); + + return false; +} + +static +bool annotate_browser__continue_search_reverse(struct annotate_browser *browser, + int delay_secs) +{ + if (!*browser->search_bf) + return annotate_browser__search_reverse(browser, delay_secs); + + return __annotate_browser__search_reverse(browser); +} + +static int annotate_browser__run(struct annotate_browser *self, int evidx, + void(*timer)(void *arg), + void *arg, int delay_secs) +{ + struct rb_node *nd = NULL; + struct map_symbol *ms = self->b.priv; + struct symbol *sym = ms->sym; + const char *help = "Press 'h' for help on key bindings"; + int key; + + if (ui_browser__show(&self->b, sym->name, help) < 0) + return -1; + + annotate_browser__calc_percent(self, evidx); + + if (self->curr_hot) { + annotate_browser__set_rb_top(self, self->curr_hot); + self->b.navkeypressed = false; + } + + nd = self->curr_hot; + + while (1) { + key = ui_browser__run(&self->b, delay_secs); + + if (delay_secs != 0) { + annotate_browser__calc_percent(self, evidx); + /* + * Current line focus got out of the list of most active + * lines, NULL it so that if TAB|UNTAB is pressed, we + * move to curr_hot (current hottest line). + */ + if (nd != NULL && RB_EMPTY_NODE(nd)) + nd = NULL; + } + + switch (key) { + case K_TIMER: + if (timer != NULL) + timer(arg); + + if (delay_secs != 0) + symbol__annotate_decay_histogram(sym, evidx); + continue; + case K_TAB: + if (nd != NULL) { + nd = rb_prev(nd); + if (nd == NULL) + nd = rb_last(&self->entries); + } else + nd = self->curr_hot; + break; + case K_UNTAB: + if (nd != NULL) + nd = rb_next(nd); + if (nd == NULL) + nd = rb_first(&self->entries); + else + nd = self->curr_hot; + break; + case K_F1: + case 'h': + ui_browser__help_window(&self->b, + "UP/DOWN/PGUP\n" + "PGDN/SPACE Navigate\n" + "q/ESC/CTRL+C Exit\n\n" + "-> Go to target\n" + "<- Exit\n" + "h Cycle thru hottest instructions\n" + "j Toggle showing jump to target arrows\n" + "J Toggle showing number of jump sources on targets\n" + "n Search next string\n" + "o Toggle disassembler output/simplified view\n" + "s Toggle source code view\n" + "/ Search string\n" + "? Search previous string\n"); + continue; + case 'H': + nd = self->curr_hot; + break; + case 's': + if (annotate_browser__toggle_source(self)) + ui_helpline__puts(help); + continue; + case 'o': + self->use_offset = !self->use_offset; + if (self->use_offset) + self->target_width = self->min_addr_width; + else + self->target_width = self->max_addr_width; +update_addr_width: + self->addr_width = self->target_width; + if (self->show_nr_jumps) + self->addr_width += self->jumps_width + 1; + continue; + case 'j': + self->jump_arrows = !self->jump_arrows; + continue; + case 'J': + self->show_nr_jumps = !self->show_nr_jumps; + goto update_addr_width; + case '/': + if (annotate_browser__search(self, delay_secs)) { +show_help: + ui_helpline__puts(help); + } + continue; + case 'n': + if (self->searching_backwards ? + annotate_browser__continue_search_reverse(self, delay_secs) : + annotate_browser__continue_search(self, delay_secs)) + goto show_help; + continue; + case '?': + if (annotate_browser__search_reverse(self, delay_secs)) + goto show_help; + continue; + case K_ENTER: + case K_RIGHT: + if (self->selection == NULL) + ui_helpline__puts("Huh? No selection. Report to linux-kernel@vger.kernel.org"); + else if (self->selection->offset == -1) + ui_helpline__puts("Actions are only available for assembly lines."); + else if (!self->selection->ins) { + if (strcmp(self->selection->name, "retq")) + goto show_sup_ins; + goto out; + } else if (!(annotate_browser__jump(self) || + annotate_browser__callq(self, evidx, timer, arg, delay_secs))) { +show_sup_ins: + ui_helpline__puts("Actions are only available for 'callq', 'retq' & jump instructions."); + } + continue; + case K_LEFT: + case K_ESC: + case 'q': + case CTRL('c'): + goto out; + default: + continue; + } + + if (nd != NULL) + annotate_browser__set_rb_top(self, nd); + } +out: + ui_browser__hide(&self->b); + return key; +} + +int hist_entry__tui_annotate(struct hist_entry *he, int evidx, + void(*timer)(void *arg), void *arg, int delay_secs) +{ + return symbol__tui_annotate(he->ms.sym, he->ms.map, evidx, + timer, arg, delay_secs); +} + +static void annotate_browser__mark_jump_targets(struct annotate_browser *browser, + size_t size) +{ + u64 offset; + + for (offset = 0; offset < size; ++offset) { + struct disasm_line *dl = browser->offsets[offset], *dlt; + struct browser_disasm_line *bdlt; + + if (!dl || !dl->ins || !ins__is_jump(dl->ins) || + !disasm_line__has_offset(dl)) + continue; + + if (dl->ops.target.offset >= size) { + ui__error("jump to after symbol!\n" + "size: %zx, jump target: %" PRIx64, + size, dl->ops.target.offset); + continue; + } + + dlt = browser->offsets[dl->ops.target.offset]; + /* + * FIXME: Oops, no jump target? Buggy disassembler? Or do we + * have to adjust to the previous offset? + */ + if (dlt == NULL) + continue; + + bdlt = disasm_line__browser(dlt); + if (++bdlt->jump_sources > browser->max_jump_sources) + browser->max_jump_sources = bdlt->jump_sources; + + ++browser->nr_jumps; + } + +} + +static inline int width_jumps(int n) +{ + if (n >= 100) + return 5; + if (n / 10) + return 2; + return 1; +} + +int symbol__tui_annotate(struct symbol *sym, struct map *map, int evidx, + void(*timer)(void *arg), void *arg, + int delay_secs) +{ + struct disasm_line *pos, *n; + struct annotation *notes; + const size_t size = symbol__size(sym); + struct map_symbol ms = { + .map = map, + .sym = sym, + }; + struct annotate_browser browser = { + .b = { + .refresh = annotate_browser__refresh, + .seek = ui_browser__list_head_seek, + .write = annotate_browser__write, + .filter = disasm_line__filter, + .priv = &ms, + .use_navkeypressed = true, + }, + .use_offset = true, + .jump_arrows = true, + }; + int ret = -1; + + if (sym == NULL) + return -1; + + if (map->dso->annotate_warned) + return -1; + + browser.offsets = zalloc(size * sizeof(struct disasm_line *)); + if (browser.offsets == NULL) { + ui__error("Not enough memory!"); + return -1; + } + + if (symbol__annotate(sym, map, sizeof(struct browser_disasm_line)) < 0) { + ui__error("%s", ui_helpline__last_msg); + goto out_free_offsets; + } + + ui_helpline__push("Press <- or ESC to exit"); + + notes = symbol__annotation(sym); + browser.start = map__rip_2objdump(map, sym->start); + + list_for_each_entry(pos, ¬es->src->source, node) { + struct browser_disasm_line *bpos; + size_t line_len = strlen(pos->line); + + if (browser.b.width < line_len) + browser.b.width = line_len; + bpos = disasm_line__browser(pos); + bpos->idx = browser.nr_entries++; + if (pos->offset != -1) { + bpos->idx_asm = browser.nr_asm_entries++; + /* + * FIXME: short term bandaid to cope with assembly + * routines that comes with labels in the same column + * as the address in objdump, sigh. + * + * E.g. copy_user_generic_unrolled + */ + if (pos->offset < (s64)size) + browser.offsets[pos->offset] = pos; + } else + bpos->idx_asm = -1; + } + + annotate_browser__mark_jump_targets(&browser, size); + + browser.addr_width = browser.target_width = browser.min_addr_width = hex_width(size); + browser.max_addr_width = hex_width(sym->end); + browser.jumps_width = width_jumps(browser.max_jump_sources); + browser.b.nr_entries = browser.nr_entries; + browser.b.entries = ¬es->src->source, + browser.b.width += 18; /* Percentage */ + ret = annotate_browser__run(&browser, evidx, timer, arg, delay_secs); + list_for_each_entry_safe(pos, n, ¬es->src->source, node) { + list_del(&pos->node); + disasm_line__free(pos); + } + +out_free_offsets: + free(browser.offsets); + return ret; +} diff --git a/tools/perf/util/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index e81aef1f25698..a372a4b026354 100644 --- a/tools/perf/util/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -5,12 +5,12 @@ #include <newt.h> #include <linux/rbtree.h> -#include "../../evsel.h" -#include "../../evlist.h" -#include "../../hist.h" -#include "../../pstack.h" -#include "../../sort.h" -#include "../../util.h" +#include "../../util/evsel.h" +#include "../../util/evlist.h" +#include "../../util/hist.h" +#include "../../util/pstack.h" +#include "../../util/sort.h" +#include "../../util/util.h" #include "../browser.h" #include "../helpline.h" @@ -125,6 +125,9 @@ static int callchain__count_rows(struct rb_root *chain) static bool map_symbol__toggle_fold(struct map_symbol *self) { + if (!self) + return false; + if (!self->has_children) return false; @@ -805,8 +808,11 @@ static struct hist_browser *hist_browser__new(struct hists *hists) self->hists = hists; self->b.refresh = hist_browser__refresh; self->b.seek = ui_browser__hists_seek; - self->b.use_navkeypressed = true, - self->has_symbols = sort_sym.list.next != NULL; + self->b.use_navkeypressed = true; + if (sort__branch_mode == 1) + self->has_symbols = sort_sym_from.list.next != NULL; + else + self->has_symbols = sort_sym.list.next != NULL; } return self; @@ -834,22 +840,39 @@ static int hists__browser_title(struct hists *self, char *bf, size_t size, int printed; const struct dso *dso = self->dso_filter; const struct thread *thread = self->thread_filter; - unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE]; + unsigned long nr_samples = self->stats.nr_events[PERF_RECORD_SAMPLE]; + u64 nr_events = self->stats.total_period; - nr_events = convert_unit(nr_events, &unit); - printed = snprintf(bf, size, "Events: %lu%c %s", nr_events, unit, ev_name); + nr_samples = convert_unit(nr_samples, &unit); + printed = scnprintf(bf, size, + "Samples: %lu%c of event '%s', Event count (approx.): %lu", + nr_samples, unit, ev_name, nr_events); - if (thread) + + if (self->uid_filter_str) printed += snprintf(bf + printed, size - printed, + ", UID: %s", self->uid_filter_str); + if (thread) + printed += scnprintf(bf + printed, size - printed, ", Thread: %s(%d)", (thread->comm_set ? thread->comm : ""), thread->pid); if (dso) - printed += snprintf(bf + printed, size - printed, + printed += scnprintf(bf + printed, size - printed, ", DSO: %s", dso->short_name); return printed; } +static inline void free_popup_options(char **options, int n) +{ + int i; + + for (i = 0; i < n; ++i) { + free(options[i]); + options[i] = NULL; + } +} + static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, const char *helpline, const char *ev_name, bool left_exits, @@ -858,8 +881,12 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, { struct hists *self = &evsel->hists; struct hist_browser *browser = hist_browser__new(self); + struct branch_info *bi; struct pstack *fstack; + char *options[16]; + int nr_options = 0; int key = -1; + char buf[64]; if (browser == NULL) return -1; @@ -870,13 +897,16 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, ui_helpline__push(helpline); + memset(options, 0, sizeof(options)); + while (1) { const struct thread *thread = NULL; const struct dso *dso = NULL; - char *options[16]; - int nr_options = 0, choice = 0, i, + int choice = 0, annotate = -2, zoom_dso = -2, zoom_thread = -2, - browse_map = -2; + annotate_f = -2, annotate_t = -2, browse_map = -2; + + nr_options = 0; key = hist_browser__run(browser, ev_name, timer, arg, delay_secs); @@ -884,7 +914,6 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, thread = hist_browser__selected_thread(browser); dso = browser->selection->map ? browser->selection->map->dso : NULL; } - switch (key) { case K_TAB: case K_UNTAB: @@ -899,7 +928,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, if (!browser->has_symbols) { ui_browser__warning(&browser->b, delay_secs * 2, "Annotation is only available for symbolic views, " - "include \"sym\" in --sort to use it."); + "include \"sym*\" in --sort to use it."); continue; } @@ -912,6 +941,16 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, goto zoom_dso; case 't': goto zoom_thread; + case '/': + if (ui_browser__input_window("Symbol to show", + "Please enter the name of symbol you want to see", + buf, "ENTER: OK, ESC: Cancel", + delay_secs * 2) == K_ENTER) { + self->symbol_filter_str = *buf ? buf : NULL; + hists__filter_by_symbol(self); + hist_browser__reset(browser); + } + continue; case K_F1: case 'h': case '?': @@ -929,7 +968,8 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, "C Collapse all callchains\n" "E Expand all callchains\n" "d Zoom into current DSO\n" - "t Zoom into current Thread"); + "t Zoom into current Thread\n" + "/ Filter symbol by name"); continue; case K_ENTER: case K_RIGHT: @@ -969,12 +1009,34 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, if (!browser->has_symbols) goto add_exit_option; - if (browser->selection != NULL && - 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 (sort__branch_mode == 1) { + bi = browser->he_selection->branch_info; + if (browser->selection != NULL && + bi && + bi->from.sym != NULL && + !bi->from.map->dso->annotate_warned && + asprintf(&options[nr_options], "Annotate %s", + bi->from.sym->name) > 0) + annotate_f = nr_options++; + + if (browser->selection != NULL && + bi && + bi->to.sym != NULL && + !bi->to.map->dso->annotate_warned && + (bi->to.sym != bi->from.sym || + bi->to.map->dso != bi->from.map->dso) && + asprintf(&options[nr_options], "Annotate %s", + bi->to.sym->name) > 0) + annotate_t = nr_options++; + } else { + + if (browser->selection != NULL && + 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", @@ -995,25 +1057,39 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, browse_map = nr_options++; add_exit_option: options[nr_options++] = (char *)"Exit"; - +retry_popup_menu: 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) + if (choice == -1) { + free_popup_options(options, nr_options - 1); continue; + } - if (choice == annotate) { + if (choice == annotate || choice == annotate_t || choice == annotate_f) { struct hist_entry *he; int err; do_annotate: he = hist_browser__selected_entry(browser); if (he == NULL) continue; + + /* + * we stash the branch_info symbol + map into the + * the ms so we don't have to rewrite all the annotation + * code to use branch_info. + * in branch mode, the ms struct is not used + */ + if (choice == annotate_f) { + he->ms.sym = he->branch_info->from.sym; + he->ms.map = he->branch_info->from.map; + } else if (choice == annotate_t) { + he->ms.sym = he->branch_info->to.sym; + he->ms.map = he->branch_info->to.map; + } + /* * Don't let this be freed, say, by hists__decay_entry. */ @@ -1021,9 +1097,18 @@ do_annotate: err = hist_entry__tui_annotate(he, evsel->idx, timer, arg, delay_secs); he->used = false; + /* + * offer option to annotate the other branch source or target + * (if they exists) when returning from annotate + */ + if ((err == 'q' || err == CTRL('c')) + && annotate_t != -2 && annotate_f != -2) + goto retry_popup_menu; + ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); if (err) ui_browser__handle_resize(&browser->b); + } else if (choice == browse_map) map__browse(browser->selection->map); else if (choice == zoom_dso) { @@ -1069,6 +1154,7 @@ out_free_stack: pstack__delete(fstack); out: hist_browser__delete(browser); + free_popup_options(options, nr_options - 1); return key; } @@ -1095,7 +1181,7 @@ static void perf_evsel_menu__write(struct ui_browser *browser, HE_COLORSET_NORMAL); nr_events = convert_unit(nr_events, &unit); - printed = snprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, + printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, unit, unit == ' ' ? "" : " ", ev_name); slsmg_printf("%s", bf); @@ -1105,8 +1191,8 @@ static void perf_evsel_menu__write(struct ui_browser *browser, if (!current_entry) ui_browser__set_color(browser, HE_COLORSET_TOP); nr_events = convert_unit(nr_events, &unit); - snprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!", nr_events, - unit, unit == ' ' ? "" : " "); + printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!", + nr_events, unit, unit == ' ' ? "" : " "); warn = bf; } diff --git a/tools/perf/util/ui/browsers/map.c b/tools/perf/ui/browsers/map.c index 6905bcc8be2df..98851d55a53e4 100644 --- a/tools/perf/util/ui/browsers/map.c +++ b/tools/perf/ui/browsers/map.c @@ -3,11 +3,11 @@ #include <newt.h> #include <inttypes.h> #include <sys/ttydefaults.h> -#include <ctype.h> #include <string.h> #include <linux/bitops.h> -#include "../../debug.h" -#include "../../symbol.h" +#include "../../util/util.h" +#include "../../util/debug.h" +#include "../../util/symbol.h" #include "../browser.h" #include "../helpline.h" #include "map.h" diff --git a/tools/perf/util/ui/browsers/map.h b/tools/perf/ui/browsers/map.h index df8581a43e17b..df8581a43e17b 100644 --- a/tools/perf/util/ui/browsers/map.h +++ b/tools/perf/ui/browsers/map.h diff --git a/tools/perf/ui/gtk/browser.c b/tools/perf/ui/gtk/browser.c new file mode 100644 index 0000000000000..0656c381a89ca --- /dev/null +++ b/tools/perf/ui/gtk/browser.c @@ -0,0 +1,178 @@ +#include "../evlist.h" +#include "../cache.h" +#include "../evsel.h" +#include "../sort.h" +#include "../hist.h" +#include "gtk.h" + +#include <signal.h> + +#define MAX_COLUMNS 32 + +static void perf_gtk__signal(int sig) +{ + psignal(sig, "perf"); + gtk_main_quit(); +} + +static void perf_gtk__resize_window(GtkWidget *window) +{ + GdkRectangle rect; + GdkScreen *screen; + int monitor; + int height; + int width; + + screen = gtk_widget_get_screen(window); + + monitor = gdk_screen_get_monitor_at_window(screen, window->window); + + gdk_screen_get_monitor_geometry(screen, monitor, &rect); + + width = rect.width * 3 / 4; + height = rect.height * 3 / 4; + + gtk_window_resize(GTK_WINDOW(window), width, height); +} + +static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists) +{ + GType col_types[MAX_COLUMNS]; + GtkCellRenderer *renderer; + struct sort_entry *se; + GtkListStore *store; + struct rb_node *nd; + u64 total_period; + GtkWidget *view; + int col_idx; + int nr_cols; + + nr_cols = 0; + + /* The percentage column */ + col_types[nr_cols++] = G_TYPE_STRING; + + list_for_each_entry(se, &hist_entry__sort_list, list) { + if (se->elide) + continue; + + col_types[nr_cols++] = G_TYPE_STRING; + } + + store = gtk_list_store_newv(nr_cols, col_types); + + view = gtk_tree_view_new(); + + renderer = gtk_cell_renderer_text_new(); + + col_idx = 0; + + /* The percentage column */ + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), + -1, "Overhead (%)", + renderer, "text", + col_idx++, NULL); + + list_for_each_entry(se, &hist_entry__sort_list, list) { + if (se->elide) + continue; + + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), + -1, se->se_header, + renderer, "text", + col_idx++, NULL); + } + + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store)); + + g_object_unref(GTK_TREE_MODEL(store)); + + total_period = hists->stats.total_period; + + for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { + struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); + GtkTreeIter iter; + double percent; + char s[512]; + + if (h->filtered) + continue; + + gtk_list_store_append(store, &iter); + + col_idx = 0; + + percent = (h->period * 100.0) / total_period; + + snprintf(s, ARRAY_SIZE(s), "%.2f", percent); + + gtk_list_store_set(store, &iter, col_idx++, s, -1); + + list_for_each_entry(se, &hist_entry__sort_list, list) { + if (se->elide) + continue; + + se->se_snprintf(h, s, ARRAY_SIZE(s), + hists__col_len(hists, se->se_width_idx)); + + gtk_list_store_set(store, &iter, col_idx++, s, -1); + } + } + + gtk_container_add(GTK_CONTAINER(window), view); +} + +int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, + const char *help __used, + void (*timer) (void *arg)__used, + void *arg __used, int delay_secs __used) +{ + struct perf_evsel *pos; + GtkWidget *notebook; + GtkWidget *window; + + signal(SIGSEGV, perf_gtk__signal); + signal(SIGFPE, perf_gtk__signal); + signal(SIGINT, perf_gtk__signal); + signal(SIGQUIT, perf_gtk__signal); + signal(SIGTERM, perf_gtk__signal); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title(GTK_WINDOW(window), "perf report"); + + g_signal_connect(window, "delete_event", gtk_main_quit, NULL); + + notebook = gtk_notebook_new(); + + list_for_each_entry(pos, &evlist->entries, node) { + struct hists *hists = &pos->hists; + const char *evname = event_name(pos); + GtkWidget *scrolled_window; + GtkWidget *tab_label; + + scrolled_window = gtk_scrolled_window_new(NULL, NULL); + + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + perf_gtk__show_hists(scrolled_window, hists); + + tab_label = gtk_label_new(evname); + + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window, tab_label); + } + + gtk_container_add(GTK_CONTAINER(window), notebook); + + gtk_widget_show_all(window); + + perf_gtk__resize_window(window); + + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + + gtk_main(); + + return 0; +} diff --git a/tools/perf/ui/gtk/gtk.h b/tools/perf/ui/gtk/gtk.h new file mode 100644 index 0000000000000..75177ee04032d --- /dev/null +++ b/tools/perf/ui/gtk/gtk.h @@ -0,0 +1,8 @@ +#ifndef _PERF_GTK_H_ +#define _PERF_GTK_H_ 1 + +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#include <gtk/gtk.h> +#pragma GCC diagnostic error "-Wstrict-prototypes" + +#endif /* _PERF_GTK_H_ */ diff --git a/tools/perf/ui/gtk/setup.c b/tools/perf/ui/gtk/setup.c new file mode 100644 index 0000000000000..8295299577660 --- /dev/null +++ b/tools/perf/ui/gtk/setup.c @@ -0,0 +1,12 @@ +#include "gtk.h" +#include "../../util/cache.h" + +int perf_gtk__init(void) +{ + return gtk_init_check(NULL, NULL) ? 0 : -1; +} + +void perf_gtk__exit(bool wait_for_ok __used) +{ + gtk_main_quit(); +} diff --git a/tools/perf/util/ui/helpline.c b/tools/perf/ui/helpline.c index 4f48f5901b303..2f950c2641c8b 100644 --- a/tools/perf/util/ui/helpline.c +++ b/tools/perf/ui/helpline.c @@ -64,7 +64,7 @@ int ui_helpline__show_help(const char *format, va_list ap) static int backlog; pthread_mutex_lock(&ui__lock); - ret = vsnprintf(ui_helpline__last_msg + backlog, + ret = vscnprintf(ui_helpline__last_msg + backlog, sizeof(ui_helpline__last_msg) - backlog, format, ap); backlog += ret; diff --git a/tools/perf/util/ui/helpline.h b/tools/perf/ui/helpline.h index 7bab6b34e35ee..7bab6b34e35ee 100644 --- a/tools/perf/util/ui/helpline.h +++ b/tools/perf/ui/helpline.h diff --git a/tools/perf/util/ui/keysyms.h b/tools/perf/ui/keysyms.h index 3458b1985761b..809eca5707fae 100644 --- a/tools/perf/util/ui/keysyms.h +++ b/tools/perf/ui/keysyms.h @@ -16,6 +16,8 @@ #define K_TAB '\t' #define K_UNTAB SL_KEY_UNTAB #define K_UP SL_KEY_UP +#define K_BKSPC 0x7f +#define K_DEL SL_KEY_DELETE /* Not really keys */ #define K_TIMER -1 diff --git a/tools/perf/util/ui/libslang.h b/tools/perf/ui/libslang.h index 4d54b6450f5bc..4d54b6450f5bc 100644 --- a/tools/perf/util/ui/libslang.h +++ b/tools/perf/ui/libslang.h diff --git a/tools/perf/util/ui/progress.c b/tools/perf/ui/progress.c index 13aa64e50e11c..13aa64e50e11c 100644 --- a/tools/perf/util/ui/progress.c +++ b/tools/perf/ui/progress.c diff --git a/tools/perf/util/ui/progress.h b/tools/perf/ui/progress.h index d9c205b59aa1f..d9c205b59aa1f 100644 --- a/tools/perf/util/ui/progress.h +++ b/tools/perf/ui/progress.h diff --git a/tools/perf/ui/setup.c b/tools/perf/ui/setup.c new file mode 100644 index 0000000000000..9f5f888f73e30 --- /dev/null +++ b/tools/perf/ui/setup.c @@ -0,0 +1,45 @@ +#include "../cache.h" +#include "../debug.h" + + +void setup_browser(bool fallback_to_pager) +{ + if (!isatty(1) || dump_trace) + use_browser = 0; + + /* default to TUI */ + if (use_browser < 0) + use_browser = 1; + + switch (use_browser) { + case 2: + if (perf_gtk__init() == 0) + break; + /* fall through */ + case 1: + use_browser = 1; + if (ui__init() == 0) + break; + /* fall through */ + default: + if (fallback_to_pager) + setup_pager(); + break; + } +} + +void exit_browser(bool wait_for_ok) +{ + switch (use_browser) { + case 2: + perf_gtk__exit(wait_for_ok); + break; + + case 1: + ui__exit(wait_for_ok); + break; + + default: + break; + } +} diff --git a/tools/perf/util/ui/setup.c b/tools/perf/ui/tui/setup.c index 85a69faa09aa6..d33e943ac4346 100644 --- a/tools/perf/util/ui/setup.c +++ b/tools/perf/ui/tui/setup.c @@ -2,14 +2,14 @@ #include <signal.h> #include <stdbool.h> -#include "../cache.h" -#include "../debug.h" -#include "browser.h" -#include "helpline.h" -#include "ui.h" -#include "util.h" -#include "libslang.h" -#include "keysyms.h" +#include "../../util/cache.h" +#include "../../util/debug.h" +#include "../browser.h" +#include "../helpline.h" +#include "../ui.h" +#include "../util.h" +#include "../libslang.h" +#include "../keysyms.h" pthread_mutex_t ui__lock = PTHREAD_MUTEX_INITIALIZER; @@ -93,45 +93,26 @@ static void newt_suspend(void *d __used) newtResume(); } -static int ui__init(void) -{ - int err = SLkp_init(); - - if (err < 0) - goto out; - - SLkp_define_keysym((char *)"^(kB)", SL_KEY_UNTAB); -out: - return err; -} - -static void ui__exit(void) -{ - SLtt_set_cursor_visibility(1); - SLsmg_refresh(); - SLsmg_reset_smg(); - SLang_reset_tty(); -} - static void ui__signal(int sig) { - ui__exit(); + ui__exit(false); psignal(sig, "perf"); exit(0); } -void setup_browser(bool fallback_to_pager) +int ui__init(void) { - if (!isatty(1) || !use_browser || dump_trace) { - use_browser = 0; - if (fallback_to_pager) - setup_pager(); - return; - } + int err; - use_browser = 1; newtInit(); - ui__init(); + err = SLkp_init(); + if (err < 0) { + pr_err("TUI initialization failed.\n"); + goto out; + } + + SLkp_define_keysym((char *)"^(kB)", SL_KEY_UNTAB); + newtSetSuspendCallback(newt_suspend, NULL); ui_helpline__init(); ui_browser__init(); @@ -141,15 +122,19 @@ void setup_browser(bool fallback_to_pager) signal(SIGINT, ui__signal); signal(SIGQUIT, ui__signal); signal(SIGTERM, ui__signal); +out: + return err; } -void exit_browser(bool wait_for_ok) +void ui__exit(bool wait_for_ok) { - if (use_browser > 0) { - if (wait_for_ok) - ui__question_window("Fatal Error", - ui_helpline__last_msg, - "Press any key...", 0); - ui__exit(); - } + if (wait_for_ok) + ui__question_window("Fatal Error", + ui_helpline__last_msg, + "Press any key...", 0); + + SLtt_set_cursor_visibility(1); + SLsmg_refresh(); + SLsmg_reset_smg(); + SLang_reset_tty(); } diff --git a/tools/perf/util/ui/ui.h b/tools/perf/ui/ui.h index 7b67045479f60..7b67045479f60 100644 --- a/tools/perf/util/ui/ui.h +++ b/tools/perf/ui/ui.h diff --git a/tools/perf/util/ui/util.c b/tools/perf/ui/util.c index 45daa7c41dad9..ad4374a16bb08 100644 --- a/tools/perf/util/ui/util.c +++ b/tools/perf/ui/util.c @@ -69,6 +69,88 @@ int ui__popup_menu(int argc, char * const argv[]) return popup_menu__run(&menu); } +int ui_browser__input_window(const char *title, const char *text, char *input, + const char *exit_msg, int delay_secs) +{ + int x, y, len, key; + int max_len = 60, nr_lines = 0; + static char buf[50]; + const char *t; + + t = text; + while (1) { + const char *sep = strchr(t, '\n'); + + 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; + } + + max_len += 2; + nr_lines += 8; + y = SLtt_Screen_Rows / 2 - nr_lines / 2; + x = SLtt_Screen_Cols / 2 - max_len / 2; + + SLsmg_set_color(0); + SLsmg_draw_box(y, x++, nr_lines, max_len); + if (title) { + SLsmg_gotorc(y, x + 1); + SLsmg_write_string((char *)title); + } + SLsmg_gotorc(++y, x); + nr_lines -= 7; + max_len -= 2; + SLsmg_write_wrapped_string((unsigned char *)text, y, x, + nr_lines, max_len, 1); + y += nr_lines; + len = 5; + while (len--) { + SLsmg_gotorc(y + len - 1, x); + SLsmg_write_nstring((char *)" ", max_len); + } + SLsmg_draw_box(y++, x + 1, 3, max_len - 2); + + SLsmg_gotorc(y + 3, x); + SLsmg_write_nstring((char *)exit_msg, max_len); + SLsmg_refresh(); + + x += 2; + len = 0; + key = ui__getch(delay_secs); + while (key != K_TIMER && key != K_ENTER && key != K_ESC) { + if (key == K_BKSPC) { + if (len == 0) + goto next_key; + SLsmg_gotorc(y, x + --len); + SLsmg_write_char(' '); + } else { + buf[len] = key; + SLsmg_gotorc(y, x + len++); + SLsmg_write_char(key); + } + SLsmg_refresh(); + + /* XXX more graceful overflow handling needed */ + if (len == sizeof(buf) - 1) { + ui_helpline__push("maximum size of symbol name reached!"); + key = K_ENTER; + break; + } +next_key: + key = ui__getch(delay_secs); + } + + buf[len] = '\0'; + strncpy(input, buf, len+1); + return key; +} + int ui__question_window(const char *title, const char *text, const char *exit_msg, int delay_secs) { diff --git a/tools/perf/util/ui/util.h b/tools/perf/ui/util.h index 2d1738bd71c8a..2d1738bd71c8a 100644 --- a/tools/perf/util/ui/util.h +++ b/tools/perf/ui/util.h diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index 011ed2676604c..8069dfb5ba777 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -18,6 +18,403 @@ const char *disassembler_style; +static struct ins *ins__find(const char *name); +static int disasm_line__parse(char *line, char **namep, char **rawp); + +static void ins__delete(struct ins_operands *ops) +{ + free(ops->source.raw); + free(ops->source.name); + free(ops->target.raw); + free(ops->target.name); +} + +static int ins__raw_scnprintf(struct ins *ins, char *bf, size_t size, + struct ins_operands *ops) +{ + return scnprintf(bf, size, "%-6.6s %s", ins->name, ops->raw); +} + +int ins__scnprintf(struct ins *ins, char *bf, size_t size, + struct ins_operands *ops) +{ + if (ins->ops->scnprintf) + return ins->ops->scnprintf(ins, bf, size, ops); + + return ins__raw_scnprintf(ins, bf, size, ops); +} + +static int call__parse(struct ins_operands *ops) +{ + char *endptr, *tok, *name; + + ops->target.addr = strtoull(ops->raw, &endptr, 16); + + name = strchr(endptr, '<'); + if (name == NULL) + goto indirect_call; + + name++; + + tok = strchr(name, '>'); + if (tok == NULL) + return -1; + + *tok = '\0'; + ops->target.name = strdup(name); + *tok = '>'; + + return ops->target.name == NULL ? -1 : 0; + +indirect_call: + tok = strchr(endptr, '('); + if (tok != NULL) { + ops->target.addr = 0; + return 0; + } + + tok = strchr(endptr, '*'); + if (tok == NULL) + return -1; + + ops->target.addr = strtoull(tok + 1, NULL, 16); + return 0; +} + +static int call__scnprintf(struct ins *ins, char *bf, size_t size, + struct ins_operands *ops) +{ + if (ops->target.name) + return scnprintf(bf, size, "%-6.6s %s", ins->name, ops->target.name); + + if (ops->target.addr == 0) + return ins__raw_scnprintf(ins, bf, size, ops); + + return scnprintf(bf, size, "%-6.6s *%" PRIx64, ins->name, ops->target.addr); +} + +static struct ins_ops call_ops = { + .parse = call__parse, + .scnprintf = call__scnprintf, +}; + +bool ins__is_call(const struct ins *ins) +{ + return ins->ops == &call_ops; +} + +static int jump__parse(struct ins_operands *ops) +{ + const char *s = strchr(ops->raw, '+'); + + ops->target.addr = strtoll(ops->raw, NULL, 16); + + if (s++ != NULL) + ops->target.offset = strtoll(s, NULL, 16); + else + ops->target.offset = UINT64_MAX; + + return 0; +} + +static int jump__scnprintf(struct ins *ins, char *bf, size_t size, + struct ins_operands *ops) +{ + return scnprintf(bf, size, "%-6.6s %" PRIx64, ins->name, ops->target.offset); +} + +static struct ins_ops jump_ops = { + .parse = jump__parse, + .scnprintf = jump__scnprintf, +}; + +bool ins__is_jump(const struct ins *ins) +{ + return ins->ops == &jump_ops; +} + +static int comment__symbol(char *raw, char *comment, u64 *addrp, char **namep) +{ + char *endptr, *name, *t; + + if (strstr(raw, "(%rip)") == NULL) + return 0; + + *addrp = strtoull(comment, &endptr, 16); + name = strchr(endptr, '<'); + if (name == NULL) + return -1; + + name++; + + t = strchr(name, '>'); + if (t == NULL) + return 0; + + *t = '\0'; + *namep = strdup(name); + *t = '>'; + + return 0; +} + +static int lock__parse(struct ins_operands *ops) +{ + char *name; + + ops->locked.ops = zalloc(sizeof(*ops->locked.ops)); + if (ops->locked.ops == NULL) + return 0; + + if (disasm_line__parse(ops->raw, &name, &ops->locked.ops->raw) < 0) + goto out_free_ops; + + ops->locked.ins = ins__find(name); + if (ops->locked.ins == NULL) + goto out_free_ops; + + if (!ops->locked.ins->ops) + return 0; + + if (ops->locked.ins->ops->parse) + ops->locked.ins->ops->parse(ops->locked.ops); + + return 0; + +out_free_ops: + free(ops->locked.ops); + ops->locked.ops = NULL; + return 0; +} + +static int lock__scnprintf(struct ins *ins, char *bf, size_t size, + struct ins_operands *ops) +{ + int printed; + + if (ops->locked.ins == NULL) + return ins__raw_scnprintf(ins, bf, size, ops); + + printed = scnprintf(bf, size, "%-6.6s ", ins->name); + return printed + ins__scnprintf(ops->locked.ins, bf + printed, + size - printed, ops->locked.ops); +} + +static void lock__delete(struct ins_operands *ops) +{ + free(ops->locked.ops); + free(ops->target.raw); + free(ops->target.name); +} + +static struct ins_ops lock_ops = { + .free = lock__delete, + .parse = lock__parse, + .scnprintf = lock__scnprintf, +}; + +static int mov__parse(struct ins_operands *ops) +{ + char *s = strchr(ops->raw, ','), *target, *comment, prev; + + if (s == NULL) + return -1; + + *s = '\0'; + ops->source.raw = strdup(ops->raw); + *s = ','; + + if (ops->source.raw == NULL) + return -1; + + target = ++s; + + while (s[0] != '\0' && !isspace(s[0])) + ++s; + prev = *s; + *s = '\0'; + + ops->target.raw = strdup(target); + *s = prev; + + if (ops->target.raw == NULL) + goto out_free_source; + + comment = strchr(s, '#'); + if (comment == NULL) + return 0; + + while (comment[0] != '\0' && isspace(comment[0])) + ++comment; + + comment__symbol(ops->source.raw, comment, &ops->source.addr, &ops->source.name); + comment__symbol(ops->target.raw, comment, &ops->target.addr, &ops->target.name); + + return 0; + +out_free_source: + free(ops->source.raw); + ops->source.raw = NULL; + return -1; +} + +static int mov__scnprintf(struct ins *ins, char *bf, size_t size, + struct ins_operands *ops) +{ + return scnprintf(bf, size, "%-6.6s %s,%s", ins->name, + ops->source.name ?: ops->source.raw, + ops->target.name ?: ops->target.raw); +} + +static struct ins_ops mov_ops = { + .parse = mov__parse, + .scnprintf = mov__scnprintf, +}; + +static int dec__parse(struct ins_operands *ops) +{ + char *target, *comment, *s, prev; + + target = s = ops->raw; + + while (s[0] != '\0' && !isspace(s[0])) + ++s; + prev = *s; + *s = '\0'; + + ops->target.raw = strdup(target); + *s = prev; + + if (ops->target.raw == NULL) + return -1; + + comment = strchr(s, '#'); + if (comment == NULL) + return 0; + + while (comment[0] != '\0' && isspace(comment[0])) + ++comment; + + comment__symbol(ops->target.raw, comment, &ops->target.addr, &ops->target.name); + + return 0; +} + +static int dec__scnprintf(struct ins *ins, char *bf, size_t size, + struct ins_operands *ops) +{ + return scnprintf(bf, size, "%-6.6s %s", ins->name, + ops->target.name ?: ops->target.raw); +} + +static struct ins_ops dec_ops = { + .parse = dec__parse, + .scnprintf = dec__scnprintf, +}; + +static int nop__scnprintf(struct ins *ins __used, char *bf, size_t size, + struct ins_operands *ops __used) +{ + return scnprintf(bf, size, "%-6.6s", "nop"); +} + +static struct ins_ops nop_ops = { + .scnprintf = nop__scnprintf, +}; + +/* + * Must be sorted by name! + */ +static struct ins instructions[] = { + { .name = "add", .ops = &mov_ops, }, + { .name = "addl", .ops = &mov_ops, }, + { .name = "addq", .ops = &mov_ops, }, + { .name = "addw", .ops = &mov_ops, }, + { .name = "and", .ops = &mov_ops, }, + { .name = "bts", .ops = &mov_ops, }, + { .name = "call", .ops = &call_ops, }, + { .name = "callq", .ops = &call_ops, }, + { .name = "cmp", .ops = &mov_ops, }, + { .name = "cmpb", .ops = &mov_ops, }, + { .name = "cmpl", .ops = &mov_ops, }, + { .name = "cmpq", .ops = &mov_ops, }, + { .name = "cmpw", .ops = &mov_ops, }, + { .name = "cmpxch", .ops = &mov_ops, }, + { .name = "dec", .ops = &dec_ops, }, + { .name = "decl", .ops = &dec_ops, }, + { .name = "imul", .ops = &mov_ops, }, + { .name = "inc", .ops = &dec_ops, }, + { .name = "incl", .ops = &dec_ops, }, + { .name = "ja", .ops = &jump_ops, }, + { .name = "jae", .ops = &jump_ops, }, + { .name = "jb", .ops = &jump_ops, }, + { .name = "jbe", .ops = &jump_ops, }, + { .name = "jc", .ops = &jump_ops, }, + { .name = "jcxz", .ops = &jump_ops, }, + { .name = "je", .ops = &jump_ops, }, + { .name = "jecxz", .ops = &jump_ops, }, + { .name = "jg", .ops = &jump_ops, }, + { .name = "jge", .ops = &jump_ops, }, + { .name = "jl", .ops = &jump_ops, }, + { .name = "jle", .ops = &jump_ops, }, + { .name = "jmp", .ops = &jump_ops, }, + { .name = "jmpq", .ops = &jump_ops, }, + { .name = "jna", .ops = &jump_ops, }, + { .name = "jnae", .ops = &jump_ops, }, + { .name = "jnb", .ops = &jump_ops, }, + { .name = "jnbe", .ops = &jump_ops, }, + { .name = "jnc", .ops = &jump_ops, }, + { .name = "jne", .ops = &jump_ops, }, + { .name = "jng", .ops = &jump_ops, }, + { .name = "jnge", .ops = &jump_ops, }, + { .name = "jnl", .ops = &jump_ops, }, + { .name = "jnle", .ops = &jump_ops, }, + { .name = "jno", .ops = &jump_ops, }, + { .name = "jnp", .ops = &jump_ops, }, + { .name = "jns", .ops = &jump_ops, }, + { .name = "jnz", .ops = &jump_ops, }, + { .name = "jo", .ops = &jump_ops, }, + { .name = "jp", .ops = &jump_ops, }, + { .name = "jpe", .ops = &jump_ops, }, + { .name = "jpo", .ops = &jump_ops, }, + { .name = "jrcxz", .ops = &jump_ops, }, + { .name = "js", .ops = &jump_ops, }, + { .name = "jz", .ops = &jump_ops, }, + { .name = "lea", .ops = &mov_ops, }, + { .name = "lock", .ops = &lock_ops, }, + { .name = "mov", .ops = &mov_ops, }, + { .name = "movb", .ops = &mov_ops, }, + { .name = "movdqa",.ops = &mov_ops, }, + { .name = "movl", .ops = &mov_ops, }, + { .name = "movq", .ops = &mov_ops, }, + { .name = "movslq", .ops = &mov_ops, }, + { .name = "movzbl", .ops = &mov_ops, }, + { .name = "movzwl", .ops = &mov_ops, }, + { .name = "nop", .ops = &nop_ops, }, + { .name = "nopl", .ops = &nop_ops, }, + { .name = "nopw", .ops = &nop_ops, }, + { .name = "or", .ops = &mov_ops, }, + { .name = "orl", .ops = &mov_ops, }, + { .name = "test", .ops = &mov_ops, }, + { .name = "testb", .ops = &mov_ops, }, + { .name = "testl", .ops = &mov_ops, }, + { .name = "xadd", .ops = &mov_ops, }, +}; + +static int ins__cmp(const void *name, const void *insp) +{ + const struct ins *ins = insp; + + return strcmp(name, ins->name); +} + +static struct ins *ins__find(const char *name) +{ + const int nmemb = ARRAY_SIZE(instructions); + + return bsearch(name, instructions, nmemb, sizeof(struct ins), ins__cmp); +} + int symbol__annotate_init(struct map *map __used, struct symbol *sym) { struct annotation *notes = symbol__annotation(sym); @@ -28,8 +425,8 @@ int symbol__annotate_init(struct map *map __used, struct symbol *sym) int symbol__alloc_hist(struct symbol *sym) { struct annotation *notes = symbol__annotation(sym); - size_t sizeof_sym_hist = (sizeof(struct sym_hist) + - (sym->end - sym->start) * sizeof(u64)); + const size_t size = symbol__size(sym); + size_t sizeof_sym_hist = (sizeof(struct sym_hist) + size * sizeof(u64)); notes->src = zalloc(sizeof(*notes->src) + symbol_conf.nr_events * sizeof_sym_hist); if (notes->src == NULL) @@ -64,8 +461,8 @@ int symbol__inc_addr_samples(struct symbol *sym, struct map *map, pr_debug3("%s: addr=%#" PRIx64 "\n", __func__, map->unmap_ip(map, addr)); - if (addr >= sym->end) - return 0; + if (addr < sym->start || addr > sym->end) + return -ERANGE; offset = addr - sym->start; h = annotation__histogram(notes, evidx); @@ -78,31 +475,110 @@ int symbol__inc_addr_samples(struct symbol *sym, struct map *map, return 0; } -static struct objdump_line *objdump_line__new(s64 offset, char *line, size_t privsize) +static void disasm_line__init_ins(struct disasm_line *dl) +{ + dl->ins = ins__find(dl->name); + + if (dl->ins == NULL) + return; + + if (!dl->ins->ops) + return; + + if (dl->ins->ops->parse) + dl->ins->ops->parse(&dl->ops); +} + +static int disasm_line__parse(char *line, char **namep, char **rawp) +{ + char *name = line, tmp; + + while (isspace(name[0])) + ++name; + + if (name[0] == '\0') + return -1; + + *rawp = name + 1; + + while ((*rawp)[0] != '\0' && !isspace((*rawp)[0])) + ++*rawp; + + tmp = (*rawp)[0]; + (*rawp)[0] = '\0'; + *namep = strdup(name); + + if (*namep == NULL) + goto out_free_name; + + (*rawp)[0] = tmp; + + if ((*rawp)[0] != '\0') { + (*rawp)++; + while (isspace((*rawp)[0])) + ++(*rawp); + } + + return 0; + +out_free_name: + free(*namep); + *namep = NULL; + return -1; +} + +static struct disasm_line *disasm_line__new(s64 offset, char *line, size_t privsize) { - struct objdump_line *self = malloc(sizeof(*self) + privsize); + struct disasm_line *dl = zalloc(sizeof(*dl) + privsize); - if (self != NULL) { - self->offset = offset; - self->line = line; + if (dl != NULL) { + dl->offset = offset; + dl->line = strdup(line); + if (dl->line == NULL) + goto out_delete; + + if (offset != -1) { + if (disasm_line__parse(dl->line, &dl->name, &dl->ops.raw) < 0) + goto out_free_line; + + disasm_line__init_ins(dl); + } } - return self; + return dl; + +out_free_line: + free(dl->line); +out_delete: + free(dl); + return NULL; } -void objdump_line__free(struct objdump_line *self) +void disasm_line__free(struct disasm_line *dl) { - free(self->line); - free(self); + free(dl->line); + free(dl->name); + if (dl->ins && dl->ins->ops->free) + dl->ins->ops->free(&dl->ops); + else + ins__delete(&dl->ops); + free(dl); } -static void objdump__add_line(struct list_head *head, struct objdump_line *line) +int disasm_line__scnprintf(struct disasm_line *dl, char *bf, size_t size, bool raw) +{ + if (raw || !dl->ins) + return scnprintf(bf, size, "%-6.6s %s", dl->name, dl->ops.raw); + + return ins__scnprintf(dl->ins, bf, size, &dl->ops); +} + +static void disasm__add(struct list_head *head, struct disasm_line *line) { list_add_tail(&line->node, head); } -struct objdump_line *objdump__get_next_ip_line(struct list_head *head, - struct objdump_line *pos) +struct disasm_line *disasm__get_next_ip_line(struct list_head *head, struct disasm_line *pos) { list_for_each_entry_continue(pos, head, node) if (pos->offset >= 0) @@ -111,15 +587,14 @@ struct objdump_line *objdump__get_next_ip_line(struct list_head *head, return NULL; } -static int objdump_line__print(struct objdump_line *oline, struct symbol *sym, - int evidx, u64 len, int min_pcnt, - int printed, int max_lines, - struct objdump_line *queue) +static int disasm_line__print(struct disasm_line *dl, struct symbol *sym, u64 start, + int evidx, u64 len, int min_pcnt, int printed, + int max_lines, struct disasm_line *queue) { static const char *prev_line; static const char *prev_color; - if (oline->offset != -1) { + if (dl->offset != -1) { const char *path = NULL; unsigned int hits = 0; double percent = 0.0; @@ -127,10 +602,11 @@ static int objdump_line__print(struct objdump_line *oline, struct symbol *sym, struct annotation *notes = symbol__annotation(sym); struct source_line *src_line = notes->src->lines; struct sym_hist *h = annotation__histogram(notes, evidx); - s64 offset = oline->offset; - struct objdump_line *next; + s64 offset = dl->offset; + const u64 addr = start + offset; + struct disasm_line *next; - next = objdump__get_next_ip_line(¬es->src->source, oline); + next = disasm__get_next_ip_line(¬es->src->source, dl); while (offset < (s64)len && (next == NULL || offset < next->offset)) { @@ -155,9 +631,9 @@ static int objdump_line__print(struct objdump_line *oline, struct symbol *sym, if (queue != NULL) { list_for_each_entry_from(queue, ¬es->src->source, node) { - if (queue == oline) + if (queue == dl) break; - objdump_line__print(queue, sym, evidx, len, + disasm_line__print(queue, sym, start, evidx, len, 0, 0, 1, NULL); } } @@ -180,17 +656,18 @@ static int objdump_line__print(struct objdump_line *oline, struct symbol *sym, color_fprintf(stdout, color, " %7.2f", percent); printf(" : "); - color_fprintf(stdout, PERF_COLOR_BLUE, "%s\n", oline->line); + color_fprintf(stdout, PERF_COLOR_MAGENTA, " %" PRIx64 ":", addr); + color_fprintf(stdout, PERF_COLOR_BLUE, "%s\n", dl->line); } else if (max_lines && printed >= max_lines) return 1; else { if (queue) return -1; - if (!*oline->line) + if (!*dl->line) printf(" :\n"); else - printf(" : %s\n", oline->line); + printf(" : %s\n", dl->line); } return 0; @@ -200,8 +677,8 @@ static int symbol__parse_objdump_line(struct symbol *sym, struct map *map, FILE *file, size_t privsize) { struct annotation *notes = symbol__annotation(sym); - struct objdump_line *objdump_line; - char *line = NULL, *tmp, *tmp2, *c; + struct disasm_line *dl; + char *line = NULL, *parsed_line, *tmp, *tmp2, *c; size_t line_len; s64 line_ip, offset = -1; @@ -219,6 +696,7 @@ static int symbol__parse_objdump_line(struct symbol *sym, struct map *map, *c = 0; line_ip = -1; + parsed_line = line; /* * Strip leading spaces: @@ -246,14 +724,17 @@ static int symbol__parse_objdump_line(struct symbol *sym, struct map *map, offset = line_ip - start; if (offset < 0 || (u64)line_ip > end) offset = -1; + else + parsed_line = tmp2 + 1; } - objdump_line = objdump_line__new(offset, line, privsize); - if (objdump_line == NULL) { - free(line); + dl = disasm_line__new(offset, parsed_line, privsize); + free(line); + + if (dl == NULL) return -1; - } - objdump__add_line(¬es->src->source, objdump_line); + + disasm__add(¬es->src->source, dl); return 0; } @@ -315,7 +796,7 @@ fallback: "Please use:\n\n" " perf buildid-cache -av vmlinux\n\n" "or:\n\n" - " --vmlinux vmlinux", + " --vmlinux vmlinux\n", sym->name, build_id_msg ?: ""); goto out_free_filename; } @@ -408,7 +889,7 @@ static int symbol__get_source_line(struct symbol *sym, struct map *map, if (!notes->src->lines) return -1; - start = map->unmap_ip(map, sym->start); + start = map__rip_2objdump(map, sym->start); for (i = 0; i < len; i++) { char *path = NULL; @@ -476,7 +957,7 @@ static void symbol__annotate_hits(struct symbol *sym, int evidx) { struct annotation *notes = symbol__annotation(sym); struct sym_hist *h = annotation__histogram(notes, evidx); - u64 len = sym->end - sym->start, offset; + u64 len = symbol__size(sym), offset; for (offset = 0; offset < len; ++offset) if (h->addr[offset] != 0) @@ -492,7 +973,8 @@ int symbol__annotate_printf(struct symbol *sym, struct map *map, int evidx, struct dso *dso = map->dso; const char *filename = dso->long_name, *d_filename; struct annotation *notes = symbol__annotation(sym); - struct objdump_line *pos, *queue = NULL; + struct disasm_line *pos, *queue = NULL; + u64 start = map__rip_2objdump(map, sym->start); int printed = 2, queue_len = 0; int more = 0; u64 len; @@ -502,7 +984,7 @@ int symbol__annotate_printf(struct symbol *sym, struct map *map, int evidx, else d_filename = basename(filename); - len = sym->end - sym->start; + len = symbol__size(sym); printf(" Percent | Source code & Disassembly of %s\n", d_filename); printf("------------------------------------------------\n"); @@ -516,8 +998,9 @@ int symbol__annotate_printf(struct symbol *sym, struct map *map, int evidx, queue_len = 0; } - switch (objdump_line__print(pos, sym, evidx, len, min_pcnt, - printed, max_lines, queue)) { + switch (disasm_line__print(pos, sym, start, evidx, len, + min_pcnt, printed, max_lines, + queue)) { case 0: ++printed; if (context) { @@ -561,27 +1044,51 @@ void symbol__annotate_decay_histogram(struct symbol *sym, int evidx) { struct annotation *notes = symbol__annotation(sym); struct sym_hist *h = annotation__histogram(notes, evidx); - struct objdump_line *pos; - int len = sym->end - sym->start; + int len = symbol__size(sym), offset; h->sum = 0; - - list_for_each_entry(pos, ¬es->src->source, node) { - if (pos->offset != -1 && pos->offset < len) { - h->addr[pos->offset] = h->addr[pos->offset] * 7 / 8; - h->sum += h->addr[pos->offset]; - } + for (offset = 0; offset < len; ++offset) { + h->addr[offset] = h->addr[offset] * 7 / 8; + h->sum += h->addr[offset]; } } -void objdump_line_list__purge(struct list_head *head) +void disasm__purge(struct list_head *head) { - struct objdump_line *pos, *n; + struct disasm_line *pos, *n; list_for_each_entry_safe(pos, n, head, node) { list_del(&pos->node); - objdump_line__free(pos); + disasm_line__free(pos); + } +} + +static size_t disasm_line__fprintf(struct disasm_line *dl, FILE *fp) +{ + size_t printed; + + if (dl->offset == -1) + return fprintf(fp, "%s\n", dl->line); + + printed = fprintf(fp, "%#" PRIx64 " %s", dl->offset, dl->name); + + if (dl->ops.raw[0] != '\0') { + printed += fprintf(fp, "%.*s %s\n", 6 - (int)printed, " ", + dl->ops.raw); } + + return printed + fprintf(fp, "\n"); +} + +size_t disasm__fprintf(struct list_head *head, FILE *fp) +{ + struct disasm_line *pos; + size_t printed = 0; + + list_for_each_entry(pos, head, node) + printed += disasm_line__fprintf(pos, fp); + + return printed; } int symbol__tty_annotate(struct symbol *sym, struct map *map, int evidx, @@ -596,7 +1103,7 @@ int symbol__tty_annotate(struct symbol *sym, struct map *map, int evidx, if (symbol__annotate(sym, map, 0) < 0) return -1; - len = sym->end - sym->start; + len = symbol__size(sym); if (print_lines) { symbol__get_source_line(sym, map, evidx, &source_line, @@ -609,7 +1116,7 @@ int symbol__tty_annotate(struct symbol *sym, struct map *map, int evidx, if (print_lines) symbol__free_source_line(sym, len); - objdump_line_list__purge(&symbol__annotation(sym)->src->source); + disasm__purge(&symbol__annotation(sym)->src->source); return 0; } diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h index efa5dc82bfaef..78a5692dd718d 100644 --- a/tools/perf/util/annotate.h +++ b/tools/perf/util/annotate.h @@ -2,20 +2,69 @@ #define __PERF_ANNOTATE_H #include <stdbool.h> +#include <stdint.h> #include "types.h" #include "symbol.h" #include <linux/list.h> #include <linux/rbtree.h> -struct objdump_line { - struct list_head node; - s64 offset; - char *line; +struct ins; + +struct ins_operands { + char *raw; + struct { + char *raw; + char *name; + u64 addr; + u64 offset; + } target; + union { + struct { + char *raw; + char *name; + u64 addr; + } source; + struct { + struct ins *ins; + struct ins_operands *ops; + } locked; + }; }; -void objdump_line__free(struct objdump_line *self); -struct objdump_line *objdump__get_next_ip_line(struct list_head *head, - struct objdump_line *pos); +struct ins_ops { + void (*free)(struct ins_operands *ops); + int (*parse)(struct ins_operands *ops); + int (*scnprintf)(struct ins *ins, char *bf, size_t size, + struct ins_operands *ops); +}; + +struct ins { + const char *name; + struct ins_ops *ops; +}; + +bool ins__is_jump(const struct ins *ins); +bool ins__is_call(const struct ins *ins); +int ins__scnprintf(struct ins *ins, char *bf, size_t size, struct ins_operands *ops); + +struct disasm_line { + struct list_head node; + s64 offset; + char *line; + char *name; + struct ins *ins; + struct ins_operands ops; +}; + +static inline bool disasm_line__has_offset(const struct disasm_line *dl) +{ + return dl->ops.target.offset != UINT64_MAX; +} + +void disasm_line__free(struct disasm_line *dl); +struct disasm_line *disasm__get_next_ip_line(struct list_head *head, struct disasm_line *pos); +int disasm_line__scnprintf(struct disasm_line *dl, char *bf, size_t size, bool raw); +size_t disasm__fprintf(struct list_head *head, FILE *fp); struct sym_hist { u64 sum; @@ -32,7 +81,7 @@ struct source_line { * * @histogram: Array of addr hit histograms per event being monitored * @lines: If 'print_lines' is specified, per source code line percentages - * @source: source parsed from objdump -dS + * @source: source parsed from a disassembler like objdump -dS * * lines is allocated, percentages calculated and all sorted by percentage * when the annotation is about to be presented, so the percentages are for @@ -82,7 +131,7 @@ int symbol__annotate_printf(struct symbol *sym, struct map *map, int evidx, int context); void symbol__annotate_zero_histogram(struct symbol *sym, int evidx); void symbol__annotate_decay_histogram(struct symbol *sym, int evidx); -void objdump_line_list__purge(struct list_head *head); +void disasm__purge(struct list_head *head); int symbol__tty_annotate(struct symbol *sym, struct map *map, int evidx, bool print_lines, bool full_paths, int min_pcnt, diff --git a/tools/perf/util/bitmap.c b/tools/perf/util/bitmap.c index 5e230acae1e95..0a1adc1111fd2 100644 --- a/tools/perf/util/bitmap.c +++ b/tools/perf/util/bitmap.c @@ -19,3 +19,13 @@ int __bitmap_weight(const unsigned long *bitmap, int bits) return w; } + +void __bitmap_or(unsigned long *dst, const unsigned long *bitmap1, + const unsigned long *bitmap2, int bits) +{ + int k; + int nr = BITS_TO_LONGS(bits); + + for (k = 0; k < nr; k++) + dst[k] = bitmap1[k] | bitmap2[k]; +} diff --git a/tools/perf/util/cache.h b/tools/perf/util/cache.h index fc5e5a09d5b94..cff18c617d137 100644 --- a/tools/perf/util/cache.h +++ b/tools/perf/util/cache.h @@ -33,7 +33,7 @@ extern int pager_use_color; extern int use_browser; -#ifdef NO_NEWT_SUPPORT +#if defined(NO_NEWT_SUPPORT) && defined(NO_GTK2_SUPPORT) static inline void setup_browser(bool fallback_to_pager) { if (fallback_to_pager) @@ -43,7 +43,29 @@ static inline void exit_browser(bool wait_for_ok __used) {} #else void setup_browser(bool fallback_to_pager); void exit_browser(bool wait_for_ok); + +#ifdef NO_NEWT_SUPPORT +static inline int ui__init(void) +{ + return -1; +} +static inline void ui__exit(bool wait_for_ok __used) {} +#else +int ui__init(void); +void ui__exit(bool wait_for_ok); +#endif + +#ifdef NO_GTK2_SUPPORT +static inline int perf_gtk__init(void) +{ + return -1; +} +static inline void perf_gtk__exit(bool wait_for_ok __used) {} +#else +int perf_gtk__init(void); +void perf_gtk__exit(bool wait_for_ok); #endif +#endif /* NO_NEWT_SUPPORT && NO_GTK2_SUPPORT */ char *alias_lookup(const char *alias); int split_cmdline(char *cmdline, const char ***argv); diff --git a/tools/perf/util/color.c b/tools/perf/util/color.c index 521c38a791909..11e46da17bbb3 100644 --- a/tools/perf/util/color.c +++ b/tools/perf/util/color.c @@ -1,3 +1,4 @@ +#include <linux/kernel.h> #include "cache.h" #include "color.h" @@ -182,12 +183,12 @@ static int __color_vsnprintf(char *bf, size_t size, const char *color, } if (perf_use_color_default && *color) - r += snprintf(bf, size, "%s", color); - r += vsnprintf(bf + r, size - r, fmt, args); + r += scnprintf(bf, size, "%s", color); + r += vscnprintf(bf + r, size - r, fmt, args); if (perf_use_color_default && *color) - r += snprintf(bf + r, size - r, "%s", PERF_COLOR_RESET); + r += scnprintf(bf + r, size - r, "%s", PERF_COLOR_RESET); if (trail) - r += snprintf(bf + r, size - r, "%s", trail); + r += scnprintf(bf + r, size - r, "%s", trail); return r; } diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c index 6893eec693aba..adc72f09914d0 100644 --- a/tools/perf/util/cpumap.c +++ b/tools/perf/util/cpumap.c @@ -166,6 +166,17 @@ out: return cpus; } +size_t cpu_map__fprintf(struct cpu_map *map, FILE *fp) +{ + int i; + size_t printed = fprintf(fp, "%d cpu%s: ", + map->nr, map->nr > 1 ? "s" : ""); + for (i = 0; i < map->nr; ++i) + printed += fprintf(fp, "%s%d", i ? ", " : "", map->map[i]); + + return printed + fprintf(fp, "\n"); +} + struct cpu_map *cpu_map__dummy_new(void) { struct cpu_map *cpus = malloc(sizeof(*cpus) + sizeof(int)); diff --git a/tools/perf/util/cpumap.h b/tools/perf/util/cpumap.h index 072c0a374794e..c41518573c6af 100644 --- a/tools/perf/util/cpumap.h +++ b/tools/perf/util/cpumap.h @@ -1,6 +1,8 @@ #ifndef __PERF_CPUMAP_H #define __PERF_CPUMAP_H +#include <stdio.h> + struct cpu_map { int nr; int map[]; @@ -10,4 +12,6 @@ struct cpu_map *cpu_map__new(const char *cpu_list); struct cpu_map *cpu_map__dummy_new(void); void cpu_map__delete(struct cpu_map *map); +size_t cpu_map__fprintf(struct cpu_map *map, FILE *fp); + #endif /* __PERF_CPUMAP_H */ diff --git a/tools/perf/util/ctype.c b/tools/perf/util/ctype.c index 35073621e5de9..aada3ac5e891f 100644 --- a/tools/perf/util/ctype.c +++ b/tools/perf/util/ctype.c @@ -3,7 +3,7 @@ * * No surprises, and works with signed and unsigned chars. */ -#include "cache.h" +#include "util.h" enum { S = GIT_SPACE, diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 26817daa2961b..efb1fce259a4f 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -11,6 +11,7 @@ #include "event.h" #include "debug.h" #include "util.h" +#include "target.h" int verbose; bool dump_trace = false, quiet = false; diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index f2ce88d04f54b..6bebe7f0a20c6 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -26,7 +26,7 @@ static inline void ui_progress__update(u64 curr __used, u64 total __used, #else extern char ui_helpline__last_msg[]; int ui_helpline__show_help(const char *format, va_list ap); -#include "ui/progress.h" +#include "../ui/progress.h" int ui__error(const char *format, ...) __attribute__((format(printf, 1, 2))); #endif diff --git a/tools/perf/util/debugfs.c b/tools/perf/util/debugfs.c index ffc35e748e89e..dd8b19319c03d 100644 --- a/tools/perf/util/debugfs.c +++ b/tools/perf/util/debugfs.c @@ -15,32 +15,6 @@ static const char *debugfs_known_mountpoints[] = { 0, }; -/* use this to force a umount */ -void debugfs_force_cleanup(void) -{ - debugfs_find_mountpoint(); - debugfs_premounted = 0; - debugfs_umount(); -} - -/* construct a full path to a debugfs element */ -int debugfs_make_path(const char *element, char *buffer, int size) -{ - int len; - - if (strlen(debugfs_mountpoint) == 0) { - buffer[0] = '\0'; - return -1; - } - - len = strlen(debugfs_mountpoint) + strlen(element) + 1; - if (len >= size) - return len+1; - - snprintf(buffer, size-1, "%s/%s", debugfs_mountpoint, element); - return 0; -} - static int debugfs_found; /* find the path to the mounted debugfs */ @@ -97,17 +71,6 @@ int debugfs_valid_mountpoint(const char *debugfs) return 0; } - -int debugfs_valid_entry(const char *path) -{ - struct stat st; - - if (stat(path, &st)) - return -errno; - - return 0; -} - static void debugfs_set_tracing_events_path(const char *mountpoint) { snprintf(tracing_events_path, sizeof(tracing_events_path), "%s/%s", @@ -149,107 +112,3 @@ void debugfs_set_path(const char *mountpoint) snprintf(debugfs_mountpoint, sizeof(debugfs_mountpoint), "%s", mountpoint); debugfs_set_tracing_events_path(mountpoint); } - -/* umount the debugfs */ - -int debugfs_umount(void) -{ - char umountcmd[128]; - int ret; - - /* if it was already mounted, leave it */ - if (debugfs_premounted) - return 0; - - /* make sure it's a valid mount point */ - ret = debugfs_valid_mountpoint(debugfs_mountpoint); - if (ret) - return ret; - - snprintf(umountcmd, sizeof(umountcmd), - "/bin/umount %s", debugfs_mountpoint); - return system(umountcmd); -} - -int debugfs_write(const char *entry, const char *value) -{ - char path[PATH_MAX + 1]; - int ret, count; - int fd; - - /* construct the path */ - snprintf(path, sizeof(path), "%s/%s", debugfs_mountpoint, entry); - - /* verify that it exists */ - ret = debugfs_valid_entry(path); - if (ret) - return ret; - - /* get how many chars we're going to write */ - count = strlen(value); - - /* open the debugfs entry */ - fd = open(path, O_RDWR); - if (fd < 0) - return -errno; - - while (count > 0) { - /* write it */ - ret = write(fd, value, count); - if (ret <= 0) { - if (ret == EAGAIN) - continue; - close(fd); - return -errno; - } - count -= ret; - } - - /* close it */ - close(fd); - - /* return success */ - return 0; -} - -/* - * read a debugfs entry - * returns the number of chars read or a negative errno - */ -int debugfs_read(const char *entry, char *buffer, size_t size) -{ - char path[PATH_MAX + 1]; - int ret; - int fd; - - /* construct the path */ - snprintf(path, sizeof(path), "%s/%s", debugfs_mountpoint, entry); - - /* verify that it exists */ - ret = debugfs_valid_entry(path); - if (ret) - return ret; - - /* open the debugfs entry */ - fd = open(path, O_RDONLY); - if (fd < 0) - return -errno; - - do { - /* read it */ - ret = read(fd, buffer, size); - if (ret == 0) { - close(fd); - return EOF; - } - } while (ret < 0 && errno == EAGAIN); - - /* close it */ - close(fd); - - /* make *sure* there's a null character at the end */ - buffer[ret] = '\0'; - - /* return the number of chars read */ - return ret; -} diff --git a/tools/perf/util/debugfs.h b/tools/perf/util/debugfs.h index 4a878f735eb01..68f3e87ec57f3 100644 --- a/tools/perf/util/debugfs.h +++ b/tools/perf/util/debugfs.h @@ -3,14 +3,8 @@ const char *debugfs_find_mountpoint(void); int debugfs_valid_mountpoint(const char *debugfs); -int debugfs_valid_entry(const char *path); char *debugfs_mount(const char *mountpoint); -int debugfs_umount(void); void debugfs_set_path(const char *mountpoint); -int debugfs_write(const char *entry, const char *value); -int debugfs_read(const char *entry, char *buffer, size_t size); -void debugfs_force_cleanup(void); -int debugfs_make_path(const char *element, char *buffer, int size); extern char debugfs_mountpoint[]; extern char tracing_events_path[]; diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index cbdeaad9c5e5c..1b197280c621f 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -81,6 +81,7 @@ struct perf_sample { u32 raw_size; void *raw_data; struct ip_callchain *callchain; + struct branch_stack *branch_stack; }; #define BUILD_ID_SIZE 20 diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c index ea32a061f1c88..4ac5f5ae4ce90 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -11,6 +11,7 @@ #include <poll.h> #include "cpumap.h" #include "thread_map.h" +#include "target.h" #include "evlist.h" #include "evsel.h" #include <unistd.h> @@ -51,13 +52,15 @@ struct perf_evlist *perf_evlist__new(struct cpu_map *cpus, void perf_evlist__config_attrs(struct perf_evlist *evlist, struct perf_record_opts *opts) { - struct perf_evsel *evsel; + struct perf_evsel *evsel, *first; if (evlist->cpus->map[0] < 0) opts->no_inherit = true; + first = list_entry(evlist->entries.next, struct perf_evsel, node); + list_for_each_entry(evsel, &evlist->entries, node) { - perf_evsel__config(evsel, opts); + perf_evsel__config(evsel, opts, first); if (evlist->nr_entries > 1) evsel->attr.sample_type |= PERF_SAMPLE_ID; @@ -97,9 +100,9 @@ void perf_evlist__add(struct perf_evlist *evlist, struct perf_evsel *entry) ++evlist->nr_entries; } -static void perf_evlist__splice_list_tail(struct perf_evlist *evlist, - struct list_head *list, - int nr_entries) +void perf_evlist__splice_list_tail(struct perf_evlist *evlist, + struct list_head *list, + int nr_entries) { list_splice_tail(list, &evlist->entries); evlist->nr_entries += nr_entries; @@ -597,18 +600,21 @@ int perf_evlist__mmap(struct perf_evlist *evlist, unsigned int pages, return perf_evlist__mmap_per_cpu(evlist, prot, mask); } -int perf_evlist__create_maps(struct perf_evlist *evlist, pid_t target_pid, - pid_t target_tid, const char *cpu_list) +int perf_evlist__create_maps(struct perf_evlist *evlist, + struct perf_target *target) { - evlist->threads = thread_map__new(target_pid, target_tid); + evlist->threads = thread_map__new_str(target->pid, target->tid, + target->uid); if (evlist->threads == NULL) return -1; - if (cpu_list == NULL && target_tid != -1) + if (perf_target__has_task(target)) + evlist->cpus = cpu_map__dummy_new(); + else if (!perf_target__has_cpu(target) && !target->uses_mmap) evlist->cpus = cpu_map__dummy_new(); else - evlist->cpus = cpu_map__new(cpu_list); + evlist->cpus = cpu_map__new(target->cpu_list); if (evlist->cpus == NULL) goto out_delete_threads; @@ -765,6 +771,7 @@ out_err: list_for_each_entry_reverse(evsel, &evlist->entries, node) perf_evsel__close(evsel, ncpus, nthreads); + errno = -err; return err; } @@ -824,7 +831,7 @@ int perf_evlist__prepare_workload(struct perf_evlist *evlist, exit(-1); } - if (!opts->system_wide && opts->target_tid == -1 && opts->target_pid == -1) + if (perf_target__none(&opts->target)) evlist->threads->map[0] = evlist->workload.pid; close(child_ready_pipe[1]); diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h index 8922aeed04672..58abb63ac13a4 100644 --- a/tools/perf/util/evlist.h +++ b/tools/perf/util/evlist.h @@ -106,8 +106,8 @@ static inline void perf_evlist__set_maps(struct perf_evlist *evlist, evlist->threads = threads; } -int perf_evlist__create_maps(struct perf_evlist *evlist, pid_t target_pid, - pid_t target_tid, const char *cpu_list); +int perf_evlist__create_maps(struct perf_evlist *evlist, + struct perf_target *target); void perf_evlist__delete_maps(struct perf_evlist *evlist); int perf_evlist__set_filters(struct perf_evlist *evlist); @@ -117,4 +117,9 @@ u16 perf_evlist__id_hdr_size(const struct perf_evlist *evlist); bool perf_evlist__valid_sample_type(const struct perf_evlist *evlist); bool perf_evlist__valid_sample_id_all(const struct perf_evlist *evlist); + +void perf_evlist__splice_list_tail(struct perf_evlist *evlist, + struct list_head *list, + int nr_entries); + #endif /* __PERF_EVLIST_H */ diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 7132ee834e0e5..f4f427ce4d641 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -14,6 +14,7 @@ #include "util.h" #include "cpumap.h" #include "thread_map.h" +#include "target.h" #define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y)) #define GROUP_FD(group_fd, cpu) (*(int *)xyarray__entry(group_fd, cpu, 0)) @@ -34,7 +35,7 @@ int __perf_evsel__sample_size(u64 sample_type) return size; } -static void hists__init(struct hists *hists) +void hists__init(struct hists *hists) { memset(hists, 0, sizeof(*hists)); hists->entries_in_array[0] = hists->entries_in_array[1] = RB_ROOT; @@ -63,12 +64,14 @@ struct perf_evsel *perf_evsel__new(struct perf_event_attr *attr, int idx) return evsel; } -void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts) +void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts, + struct perf_evsel *first) { struct perf_event_attr *attr = &evsel->attr; int track = !evsel->idx; /* only the first counter needs these */ - attr->sample_id_all = opts->sample_id_all_avail ? 1 : 0; + attr->disabled = 1; + attr->sample_id_all = opts->sample_id_all_missing ? 0 : 1; attr->inherit = !opts->no_inherit; attr->read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | @@ -105,15 +108,15 @@ void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts) if (opts->call_graph) attr->sample_type |= PERF_SAMPLE_CALLCHAIN; - if (opts->system_wide) + if (opts->target.system_wide) attr->sample_type |= PERF_SAMPLE_CPU; if (opts->period) attr->sample_type |= PERF_SAMPLE_PERIOD; - if (opts->sample_id_all_avail && - (opts->sample_time || opts->system_wide || - !opts->no_inherit || opts->cpu_list)) + if (!opts->sample_id_all_missing && + (opts->sample_time || !opts->no_inherit || + perf_target__has_cpu(&opts->target))) attr->sample_type |= PERF_SAMPLE_TIME; if (opts->raw_samples) { @@ -126,12 +129,16 @@ void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts) attr->watermark = 0; attr->wakeup_events = 1; } + if (opts->branch_stack) { + attr->sample_type |= PERF_SAMPLE_BRANCH_STACK; + attr->branch_sample_type = opts->branch_stack; + } attr->mmap = track; attr->comm = track; - if (opts->target_pid == -1 && opts->target_tid == -1 && !opts->system_wide) { - attr->disabled = 1; + if (perf_target__none(&opts->target) && + (!opts->group || evsel == first)) { attr->enable_on_exec = 1; } } @@ -536,7 +543,7 @@ int perf_event__parse_sample(const union perf_event *event, u64 type, } if (type & PERF_SAMPLE_READ) { - fprintf(stderr, "PERF_SAMPLE_READ is unsuported for now\n"); + fprintf(stderr, "PERF_SAMPLE_READ is unsupported for now\n"); return -1; } @@ -574,8 +581,20 @@ int perf_event__parse_sample(const union perf_event *event, u64 type, return -EFAULT; data->raw_data = (void *) pdata; + + array = (void *)array + data->raw_size + sizeof(u32); } + if (type & PERF_SAMPLE_BRANCH_STACK) { + u64 sz; + + data->branch_stack = (struct branch_stack *)array; + array++; /* nr */ + + sz = data->branch_stack->nr * sizeof(struct branch_entry); + sz /= sizeof(u64); + array += sz; + } return 0; } diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 326b8e4d50357..3d6b3e4cb66bb 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -80,7 +80,8 @@ void perf_evsel__exit(struct perf_evsel *evsel); void perf_evsel__delete(struct perf_evsel *evsel); void perf_evsel__config(struct perf_evsel *evsel, - struct perf_record_opts *opts); + struct perf_record_opts *opts, + struct perf_evsel *first); int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads); int perf_evsel__alloc_id(struct perf_evsel *evsel, int ncpus, int nthreads); @@ -169,4 +170,6 @@ static inline int perf_evsel__sample_size(struct perf_evsel *evsel) return __perf_evsel__sample_size(evsel->attr.sample_type); } +void hists__init(struct hists *hists); + #endif /* __PERF_EVSEL_H */ diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index ecd7f4dd7eea0..538598012139c 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -31,21 +31,16 @@ static const char **header_argv; int perf_header__push_event(u64 id, const char *name) { + struct perf_trace_event_type *nevents; + if (strlen(name) > MAX_EVENT_NAME) pr_warning("Event %s will be truncated\n", name); - if (!events) { - events = malloc(sizeof(struct perf_trace_event_type)); - if (events == NULL) - return -ENOMEM; - } else { - struct perf_trace_event_type *nevents; + nevents = realloc(events, (event_count + 1) * sizeof(*events)); + if (nevents == NULL) + return -ENOMEM; + events = nevents; - nevents = realloc(events, (event_count + 1) * sizeof(*events)); - if (nevents == NULL) - return -ENOMEM; - events = nevents; - } memset(&events[event_count], 0, sizeof(struct perf_trace_event_type)); events[event_count].event_id = id; strncpy(events[event_count].name, name, MAX_EVENT_NAME - 1); @@ -63,9 +58,20 @@ char *perf_header__find_event(u64 id) return NULL; } -static const char *__perf_magic = "PERFFILE"; +/* + * magic2 = "PERFILE2" + * must be a numerical value to let the endianness + * determine the memory layout. That way we are able + * to detect endianness when reading the perf.data file + * back. + * + * we check for legacy (PERFFILE) format. + */ +static const char *__perf_magic1 = "PERFFILE"; +static const u64 __perf_magic2 = 0x32454c4946524550ULL; +static const u64 __perf_magic2_sw = 0x50455246494c4532ULL; -#define PERF_MAGIC (*(u64 *)__perf_magic) +#define PERF_MAGIC __perf_magic2 struct perf_file_attr { struct perf_event_attr attr; @@ -280,12 +286,12 @@ int build_id_cache__add_s(const char *sbuild_id, const char *debugdir, if (realname == NULL || filename == NULL || linkname == NULL) goto out_free; - len = snprintf(filename, size, "%s%s%s", + len = scnprintf(filename, size, "%s%s%s", debugdir, is_kallsyms ? "/" : "", realname); if (mkdir_p(filename, 0755)) goto out_free; - snprintf(filename + len, sizeof(filename) - len, "/%s", sbuild_id); + snprintf(filename + len, size - len, "/%s", sbuild_id); if (access(filename, F_OK)) { if (is_kallsyms) { @@ -295,7 +301,7 @@ int build_id_cache__add_s(const char *sbuild_id, const char *debugdir, goto out_free; } - len = snprintf(linkname, size, "%s/.build-id/%.2s", + len = scnprintf(linkname, size, "%s/.build-id/%.2s", debugdir, sbuild_id); if (access(linkname, X_OK) && mkdir_p(linkname, 0755)) @@ -1012,6 +1018,12 @@ write_it: return do_write_string(fd, buffer); } +static int write_branch_stack(int fd __used, struct perf_header *h __used, + struct perf_evlist *evlist __used) +{ + return 0; +} + static void print_hostname(struct perf_header *ph, int fd, FILE *fp) { char *str = do_read_string(fd, ph); @@ -1133,8 +1145,9 @@ static void print_event_desc(struct perf_header *ph, int fd, FILE *fp) uint64_t id; void *buf = NULL; char *str; - u32 nre, sz, nr, i, j, msz; - int ret; + u32 nre, sz, nr, i, j; + ssize_t ret; + size_t msz; /* number of events */ ret = read(fd, &nre, sizeof(nre)); @@ -1151,15 +1164,9 @@ static void print_event_desc(struct perf_header *ph, int fd, FILE *fp) if (ph->needs_swap) sz = bswap_32(sz); - /* - * ensure it is at least to our ABI rev - */ - if (sz < (u32)sizeof(attr)) - goto error; - memset(&attr, 0, sizeof(attr)); - /* read entire region to sync up to next field */ + /* buffer to hold on file attr struct */ buf = malloc(sz); if (!buf) goto error; @@ -1170,6 +1177,10 @@ static void print_event_desc(struct perf_header *ph, int fd, FILE *fp) for (i = 0 ; i < nre; i++) { + /* + * must read entire on-file attr struct to + * sync up with layout. + */ ret = read(fd, buf, sz); if (ret != (ssize_t)sz) goto error; @@ -1305,25 +1316,204 @@ static void print_cpuid(struct perf_header *ph, int fd, FILE *fp) free(str); } +static void print_branch_stack(struct perf_header *ph __used, int fd __used, + FILE *fp) +{ + fprintf(fp, "# contains samples with branch stack\n"); +} + +static int __event_process_build_id(struct build_id_event *bev, + char *filename, + struct perf_session *session) +{ + int err = -1; + struct list_head *head; + struct machine *machine; + u16 misc; + struct dso *dso; + enum dso_kernel_type dso_type; + + machine = perf_session__findnew_machine(session, bev->pid); + if (!machine) + goto out; + + misc = bev->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + + switch (misc) { + case PERF_RECORD_MISC_KERNEL: + dso_type = DSO_TYPE_KERNEL; + head = &machine->kernel_dsos; + break; + case PERF_RECORD_MISC_GUEST_KERNEL: + dso_type = DSO_TYPE_GUEST_KERNEL; + head = &machine->kernel_dsos; + break; + case PERF_RECORD_MISC_USER: + case PERF_RECORD_MISC_GUEST_USER: + dso_type = DSO_TYPE_USER; + head = &machine->user_dsos; + break; + default: + goto out; + } + + dso = __dsos__findnew(head, filename); + if (dso != NULL) { + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + + dso__set_build_id(dso, &bev->build_id); + + if (filename[0] == '[') + dso->kernel = dso_type; + + build_id__sprintf(dso->build_id, sizeof(dso->build_id), + sbuild_id); + pr_debug("build id event received for %s: %s\n", + dso->long_name, sbuild_id); + } + + err = 0; +out: + return err; +} + +static int perf_header__read_build_ids_abi_quirk(struct perf_header *header, + int input, u64 offset, u64 size) +{ + struct perf_session *session = container_of(header, struct perf_session, header); + struct { + struct perf_event_header header; + u8 build_id[ALIGN(BUILD_ID_SIZE, sizeof(u64))]; + char filename[0]; + } old_bev; + struct build_id_event bev; + char filename[PATH_MAX]; + u64 limit = offset + size; + + while (offset < limit) { + ssize_t len; + + if (read(input, &old_bev, sizeof(old_bev)) != sizeof(old_bev)) + return -1; + + if (header->needs_swap) + perf_event_header__bswap(&old_bev.header); + + len = old_bev.header.size - sizeof(old_bev); + if (read(input, filename, len) != len) + return -1; + + bev.header = old_bev.header; + + /* + * As the pid is the missing value, we need to fill + * it properly. The header.misc value give us nice hint. + */ + bev.pid = HOST_KERNEL_ID; + if (bev.header.misc == PERF_RECORD_MISC_GUEST_USER || + bev.header.misc == PERF_RECORD_MISC_GUEST_KERNEL) + bev.pid = DEFAULT_GUEST_KERNEL_ID; + + memcpy(bev.build_id, old_bev.build_id, sizeof(bev.build_id)); + __event_process_build_id(&bev, filename, session); + + offset += bev.header.size; + } + + return 0; +} + +static int perf_header__read_build_ids(struct perf_header *header, + int input, u64 offset, u64 size) +{ + struct perf_session *session = container_of(header, struct perf_session, header); + struct build_id_event bev; + char filename[PATH_MAX]; + u64 limit = offset + size, orig_offset = offset; + int err = -1; + + while (offset < limit) { + ssize_t len; + + if (read(input, &bev, sizeof(bev)) != sizeof(bev)) + goto out; + + if (header->needs_swap) + perf_event_header__bswap(&bev.header); + + len = bev.header.size - sizeof(bev); + if (read(input, filename, len) != len) + goto out; + /* + * The a1645ce1 changeset: + * + * "perf: 'perf kvm' tool for monitoring guest performance from host" + * + * Added a field to struct build_id_event that broke the file + * format. + * + * Since the kernel build-id is the first entry, process the + * table using the old format if the well known + * '[kernel.kallsyms]' string for the kernel build-id has the + * first 4 characters chopped off (where the pid_t sits). + */ + if (memcmp(filename, "nel.kallsyms]", 13) == 0) { + if (lseek(input, orig_offset, SEEK_SET) == (off_t)-1) + return -1; + return perf_header__read_build_ids_abi_quirk(header, input, offset, size); + } + + __event_process_build_id(&bev, filename, session); + + offset += bev.header.size; + } + err = 0; +out: + return err; +} + +static int process_trace_info(struct perf_file_section *section __unused, + struct perf_header *ph __unused, + int feat __unused, int fd) +{ + trace_report(fd, false); + return 0; +} + +static int process_build_id(struct perf_file_section *section, + struct perf_header *ph, + int feat __unused, int fd) +{ + if (perf_header__read_build_ids(ph, fd, section->offset, section->size)) + pr_debug("Failed to read buildids, continuing...\n"); + return 0; +} + struct feature_ops { int (*write)(int fd, struct perf_header *h, struct perf_evlist *evlist); void (*print)(struct perf_header *h, int fd, FILE *fp); + int (*process)(struct perf_file_section *section, + struct perf_header *h, int feat, int fd); const char *name; bool full_only; }; #define FEAT_OPA(n, func) \ [n] = { .name = #n, .write = write_##func, .print = print_##func } +#define FEAT_OPP(n, func) \ + [n] = { .name = #n, .write = write_##func, .print = print_##func, \ + .process = process_##func } #define FEAT_OPF(n, func) \ - [n] = { .name = #n, .write = write_##func, .print = print_##func, .full_only = true } + [n] = { .name = #n, .write = write_##func, .print = print_##func, \ + .full_only = true } /* feature_ops not implemented: */ #define print_trace_info NULL #define print_build_id NULL static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = { - FEAT_OPA(HEADER_TRACE_INFO, trace_info), - FEAT_OPA(HEADER_BUILD_ID, build_id), + FEAT_OPP(HEADER_TRACE_INFO, trace_info), + FEAT_OPP(HEADER_BUILD_ID, build_id), FEAT_OPA(HEADER_HOSTNAME, hostname), FEAT_OPA(HEADER_OSRELEASE, osrelease), FEAT_OPA(HEADER_VERSION, version), @@ -1336,6 +1526,7 @@ static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = { FEAT_OPA(HEADER_CMDLINE, cmdline), FEAT_OPF(HEADER_CPU_TOPOLOGY, cpu_topology), FEAT_OPF(HEADER_NUMA_TOPOLOGY, numa_topology), + FEAT_OPA(HEADER_BRANCH_STACK, branch_stack), }; struct header_print_data { @@ -1620,24 +1811,128 @@ out_free: return err; } +static const int attr_file_abi_sizes[] = { + [0] = PERF_ATTR_SIZE_VER0, + [1] = PERF_ATTR_SIZE_VER1, + 0, +}; + +/* + * In the legacy file format, the magic number is not used to encode endianness. + * hdr_sz was used to encode endianness. But given that hdr_sz can vary based + * on ABI revisions, we need to try all combinations for all endianness to + * detect the endianness. + */ +static int try_all_file_abis(uint64_t hdr_sz, struct perf_header *ph) +{ + uint64_t ref_size, attr_size; + int i; + + for (i = 0 ; attr_file_abi_sizes[i]; i++) { + ref_size = attr_file_abi_sizes[i] + + sizeof(struct perf_file_section); + if (hdr_sz != ref_size) { + attr_size = bswap_64(hdr_sz); + if (attr_size != ref_size) + continue; + + ph->needs_swap = true; + } + pr_debug("ABI%d perf.data file detected, need_swap=%d\n", + i, + ph->needs_swap); + return 0; + } + /* could not determine endianness */ + return -1; +} + +#define PERF_PIPE_HDR_VER0 16 + +static const size_t attr_pipe_abi_sizes[] = { + [0] = PERF_PIPE_HDR_VER0, + 0, +}; + +/* + * In the legacy pipe format, there is an implicit assumption that endiannesss + * between host recording the samples, and host parsing the samples is the + * same. This is not always the case given that the pipe output may always be + * redirected into a file and analyzed on a different machine with possibly a + * different endianness and perf_event ABI revsions in the perf tool itself. + */ +static int try_all_pipe_abis(uint64_t hdr_sz, struct perf_header *ph) +{ + u64 attr_size; + int i; + + for (i = 0 ; attr_pipe_abi_sizes[i]; i++) { + if (hdr_sz != attr_pipe_abi_sizes[i]) { + attr_size = bswap_64(hdr_sz); + if (attr_size != hdr_sz) + continue; + + ph->needs_swap = true; + } + pr_debug("Pipe ABI%d perf.data file detected\n", i); + return 0; + } + return -1; +} + +static int check_magic_endian(u64 magic, uint64_t hdr_sz, + bool is_pipe, struct perf_header *ph) +{ + int ret; + + /* check for legacy format */ + ret = memcmp(&magic, __perf_magic1, sizeof(magic)); + if (ret == 0) { + pr_debug("legacy perf.data format\n"); + if (is_pipe) + return try_all_pipe_abis(hdr_sz, ph); + + return try_all_file_abis(hdr_sz, ph); + } + /* + * the new magic number serves two purposes: + * - unique number to identify actual perf.data files + * - encode endianness of file + */ + + /* check magic number with one endianness */ + if (magic == __perf_magic2) + return 0; + + /* check magic number with opposite endianness */ + if (magic != __perf_magic2_sw) + return -1; + + ph->needs_swap = true; + + return 0; +} + int perf_file_header__read(struct perf_file_header *header, struct perf_header *ph, int fd) { + int ret; + lseek(fd, 0, SEEK_SET); - if (readn(fd, header, sizeof(*header)) <= 0 || - memcmp(&header->magic, __perf_magic, sizeof(header->magic))) + ret = readn(fd, header, sizeof(*header)); + if (ret <= 0) return -1; - if (header->attr_size != sizeof(struct perf_file_attr)) { - u64 attr_size = bswap_64(header->attr_size); - - if (attr_size != sizeof(struct perf_file_attr)) - return -1; + if (check_magic_endian(header->magic, + header->attr_size, false, ph) < 0) { + pr_debug("magic/endian check failed\n"); + return -1; + } + if (ph->needs_swap) { mem_bswap_64(header, offsetof(struct perf_file_header, - adds_features)); - ph->needs_swap = true; + adds_features)); } if (header->size != sizeof(*header)) { @@ -1689,156 +1984,6 @@ int perf_file_header__read(struct perf_file_header *header, return 0; } -static int __event_process_build_id(struct build_id_event *bev, - char *filename, - struct perf_session *session) -{ - int err = -1; - struct list_head *head; - struct machine *machine; - u16 misc; - struct dso *dso; - enum dso_kernel_type dso_type; - - machine = perf_session__findnew_machine(session, bev->pid); - if (!machine) - goto out; - - misc = bev->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - - switch (misc) { - case PERF_RECORD_MISC_KERNEL: - dso_type = DSO_TYPE_KERNEL; - head = &machine->kernel_dsos; - break; - case PERF_RECORD_MISC_GUEST_KERNEL: - dso_type = DSO_TYPE_GUEST_KERNEL; - head = &machine->kernel_dsos; - break; - case PERF_RECORD_MISC_USER: - case PERF_RECORD_MISC_GUEST_USER: - dso_type = DSO_TYPE_USER; - head = &machine->user_dsos; - break; - default: - goto out; - } - - dso = __dsos__findnew(head, filename); - if (dso != NULL) { - char sbuild_id[BUILD_ID_SIZE * 2 + 1]; - - dso__set_build_id(dso, &bev->build_id); - - if (filename[0] == '[') - dso->kernel = dso_type; - - build_id__sprintf(dso->build_id, sizeof(dso->build_id), - sbuild_id); - pr_debug("build id event received for %s: %s\n", - dso->long_name, sbuild_id); - } - - err = 0; -out: - return err; -} - -static int perf_header__read_build_ids_abi_quirk(struct perf_header *header, - int input, u64 offset, u64 size) -{ - struct perf_session *session = container_of(header, struct perf_session, header); - struct { - struct perf_event_header header; - u8 build_id[ALIGN(BUILD_ID_SIZE, sizeof(u64))]; - char filename[0]; - } old_bev; - struct build_id_event bev; - char filename[PATH_MAX]; - u64 limit = offset + size; - - while (offset < limit) { - ssize_t len; - - if (read(input, &old_bev, sizeof(old_bev)) != sizeof(old_bev)) - return -1; - - if (header->needs_swap) - perf_event_header__bswap(&old_bev.header); - - len = old_bev.header.size - sizeof(old_bev); - if (read(input, filename, len) != len) - return -1; - - bev.header = old_bev.header; - - /* - * As the pid is the missing value, we need to fill - * it properly. The header.misc value give us nice hint. - */ - bev.pid = HOST_KERNEL_ID; - if (bev.header.misc == PERF_RECORD_MISC_GUEST_USER || - bev.header.misc == PERF_RECORD_MISC_GUEST_KERNEL) - bev.pid = DEFAULT_GUEST_KERNEL_ID; - - memcpy(bev.build_id, old_bev.build_id, sizeof(bev.build_id)); - __event_process_build_id(&bev, filename, session); - - offset += bev.header.size; - } - - return 0; -} - -static int perf_header__read_build_ids(struct perf_header *header, - int input, u64 offset, u64 size) -{ - struct perf_session *session = container_of(header, struct perf_session, header); - struct build_id_event bev; - char filename[PATH_MAX]; - u64 limit = offset + size, orig_offset = offset; - int err = -1; - - while (offset < limit) { - ssize_t len; - - if (read(input, &bev, sizeof(bev)) != sizeof(bev)) - goto out; - - if (header->needs_swap) - perf_event_header__bswap(&bev.header); - - len = bev.header.size - sizeof(bev); - if (read(input, filename, len) != len) - goto out; - /* - * The a1645ce1 changeset: - * - * "perf: 'perf kvm' tool for monitoring guest performance from host" - * - * Added a field to struct build_id_event that broke the file - * format. - * - * Since the kernel build-id is the first entry, process the - * table using the old format if the well known - * '[kernel.kallsyms]' string for the kernel build-id has the - * first 4 characters chopped off (where the pid_t sits). - */ - if (memcmp(filename, "nel.kallsyms]", 13) == 0) { - if (lseek(input, orig_offset, SEEK_SET) == (off_t)-1) - return -1; - return perf_header__read_build_ids_abi_quirk(header, input, offset, size); - } - - __event_process_build_id(&bev, filename, session); - - offset += bev.header.size; - } - err = 0; -out: - return err; -} - static int perf_file_section__process(struct perf_file_section *section, struct perf_header *ph, int feat, int fd, void *data __used) @@ -1854,40 +1999,32 @@ static int perf_file_section__process(struct perf_file_section *section, return 0; } - switch (feat) { - case HEADER_TRACE_INFO: - trace_report(fd, false); - break; - case HEADER_BUILD_ID: - if (perf_header__read_build_ids(ph, fd, section->offset, section->size)) - pr_debug("Failed to read buildids, continuing...\n"); - break; - default: - break; - } + if (!feat_ops[feat].process) + return 0; - return 0; + return feat_ops[feat].process(section, ph, feat, fd); } static int perf_file_header__read_pipe(struct perf_pipe_file_header *header, struct perf_header *ph, int fd, bool repipe) { - if (readn(fd, header, sizeof(*header)) <= 0 || - memcmp(&header->magic, __perf_magic, sizeof(header->magic))) - return -1; + int ret; - if (repipe && do_write(STDOUT_FILENO, header, sizeof(*header)) < 0) + ret = readn(fd, header, sizeof(*header)); + if (ret <= 0) return -1; - if (header->size != sizeof(*header)) { - u64 size = bswap_64(header->size); + if (check_magic_endian(header->magic, header->size, true, ph) < 0) { + pr_debug("endian/magic failed\n"); + return -1; + } - if (size != sizeof(*header)) - return -1; + if (ph->needs_swap) + header->size = bswap_64(header->size); - ph->needs_swap = true; - } + if (repipe && do_write(STDOUT_FILENO, header, sizeof(*header)) < 0) + return -1; return 0; } @@ -1908,6 +2045,52 @@ static int perf_header__read_pipe(struct perf_session *session, int fd) return 0; } +static int read_attr(int fd, struct perf_header *ph, + struct perf_file_attr *f_attr) +{ + struct perf_event_attr *attr = &f_attr->attr; + size_t sz, left; + size_t our_sz = sizeof(f_attr->attr); + int ret; + + memset(f_attr, 0, sizeof(*f_attr)); + + /* read minimal guaranteed structure */ + ret = readn(fd, attr, PERF_ATTR_SIZE_VER0); + if (ret <= 0) { + pr_debug("cannot read %d bytes of header attr\n", + PERF_ATTR_SIZE_VER0); + return -1; + } + + /* on file perf_event_attr size */ + sz = attr->size; + + if (ph->needs_swap) + sz = bswap_32(sz); + + if (sz == 0) { + /* assume ABI0 */ + sz = PERF_ATTR_SIZE_VER0; + } else if (sz > our_sz) { + pr_debug("file uses a more recent and unsupported ABI" + " (%zu bytes extra)\n", sz - our_sz); + return -1; + } + /* what we have not yet read and that we know about */ + left = sz - PERF_ATTR_SIZE_VER0; + if (left) { + void *ptr = attr; + ptr += PERF_ATTR_SIZE_VER0; + + ret = readn(fd, ptr, left); + } + /* read perf_file_section, ids are read in caller */ + ret = readn(fd, &f_attr->ids, sizeof(f_attr->ids)); + + return ret <= 0 ? -1 : 0; +} + int perf_session__read_header(struct perf_session *session, int fd) { struct perf_header *header = &session->header; @@ -1923,19 +2106,17 @@ int perf_session__read_header(struct perf_session *session, int fd) if (session->fd_pipe) return perf_header__read_pipe(session, fd); - if (perf_file_header__read(&f_header, header, fd) < 0) { - pr_debug("incompatible file format\n"); + if (perf_file_header__read(&f_header, header, fd) < 0) return -EINVAL; - } - nr_attrs = f_header.attrs.size / sizeof(f_attr); + nr_attrs = f_header.attrs.size / f_header.attr_size; lseek(fd, f_header.attrs.offset, SEEK_SET); for (i = 0; i < nr_attrs; i++) { struct perf_evsel *evsel; off_t tmp; - if (readn(fd, &f_attr, sizeof(f_attr)) <= 0) + if (read_attr(fd, header, &f_attr) < 0) goto out_errno; if (header->needs_swap) diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index ac4ec956024e2..21a6be09c129d 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -11,6 +11,7 @@ enum { HEADER_RESERVED = 0, /* always cleared */ + HEADER_FIRST_FEATURE = 1, HEADER_TRACE_INFO = 1, HEADER_BUILD_ID, @@ -26,7 +27,7 @@ enum { HEADER_EVENT_DESC, HEADER_CPU_TOPOLOGY, HEADER_NUMA_TOPOLOGY, - + HEADER_BRANCH_STACK, HEADER_LAST_FEATURE, HEADER_FEAT_BITS = 256, }; diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 6f505d1abac72..1293b5ebea4dd 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -10,11 +10,14 @@ static bool hists__filter_entry_by_dso(struct hists *hists, struct hist_entry *he); static bool hists__filter_entry_by_thread(struct hists *hists, struct hist_entry *he); +static bool hists__filter_entry_by_symbol(struct hists *hists, + struct hist_entry *he); enum hist_filter { HIST_FILTER__DSO, HIST_FILTER__THREAD, HIST_FILTER__PARENT, + HIST_FILTER__SYMBOL, }; struct callchain_param callchain_param = { @@ -50,21 +53,25 @@ static void hists__reset_col_len(struct hists *hists) hists__set_col_len(hists, col, 0); } +static void hists__set_unres_dso_col_len(struct hists *hists, int dso) +{ + const unsigned int unresolved_col_width = BITS_PER_LONG / 4; + + if (hists__col_len(hists, dso) < unresolved_col_width && + !symbol_conf.col_width_list_str && !symbol_conf.field_sep && + !symbol_conf.dso_list) + hists__set_col_len(hists, dso, unresolved_col_width); +} + static void hists__calc_col_len(struct hists *hists, struct hist_entry *h) { + const unsigned int unresolved_col_width = BITS_PER_LONG / 4; u16 len; if (h->ms.sym) - hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen); - else { - const unsigned int unresolved_col_width = BITS_PER_LONG / 4; - - if (hists__col_len(hists, HISTC_DSO) < unresolved_col_width && - !symbol_conf.col_width_list_str && !symbol_conf.field_sep && - !symbol_conf.dso_list) - hists__set_col_len(hists, HISTC_DSO, - unresolved_col_width); - } + hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen + 4); + else + hists__set_unres_dso_col_len(hists, HISTC_DSO); len = thread__comm_len(h->thread); if (hists__new_col_len(hists, HISTC_COMM, len)) @@ -74,6 +81,37 @@ static void hists__calc_col_len(struct hists *hists, struct hist_entry *h) len = dso__name_len(h->ms.map->dso); hists__new_col_len(hists, HISTC_DSO, len); } + + if (h->branch_info) { + int symlen; + /* + * +4 accounts for '[x] ' priv level info + * +2 account of 0x prefix on raw addresses + */ + if (h->branch_info->from.sym) { + symlen = (int)h->branch_info->from.sym->namelen + 4; + hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen); + + symlen = dso__name_len(h->branch_info->from.map->dso); + hists__new_col_len(hists, HISTC_DSO_FROM, symlen); + } else { + symlen = unresolved_col_width + 4 + 2; + hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen); + hists__set_unres_dso_col_len(hists, HISTC_DSO_FROM); + } + + if (h->branch_info->to.sym) { + symlen = (int)h->branch_info->to.sym->namelen + 4; + hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen); + + symlen = dso__name_len(h->branch_info->to.map->dso); + hists__new_col_len(hists, HISTC_DSO_TO, symlen); + } else { + symlen = unresolved_col_width + 4 + 2; + hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen); + hists__set_unres_dso_col_len(hists, HISTC_DSO_TO); + } + } } static void hist_entry__add_cpumode_period(struct hist_entry *he, @@ -195,26 +233,14 @@ static u8 symbol__parent_filter(const struct symbol *parent) return 0; } -struct hist_entry *__hists__add_entry(struct hists *hists, +static struct hist_entry *add_hist_entry(struct hists *hists, + struct hist_entry *entry, struct addr_location *al, - struct symbol *sym_parent, u64 period) + u64 period) { struct rb_node **p; struct rb_node *parent = NULL; struct hist_entry *he; - struct hist_entry entry = { - .thread = al->thread, - .ms = { - .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; pthread_mutex_lock(&hists->lock); @@ -225,11 +251,23 @@ struct hist_entry *__hists__add_entry(struct hists *hists, parent = *p; he = rb_entry(parent, struct hist_entry, rb_node_in); - cmp = hist_entry__cmp(&entry, he); + cmp = hist_entry__cmp(entry, he); if (!cmp) { he->period += period; ++he->nr_events; + + /* If the map of an existing hist_entry has + * become out-of-date due to an exec() or + * similar, update it. Otherwise we will + * mis-adjust symbol addresses when computing + * the history counter to increment. + */ + if (he->ms.map != entry->ms.map) { + he->ms.map = entry->ms.map; + if (he->ms.map) + he->ms.map->referenced = true; + } goto out; } @@ -239,7 +277,7 @@ struct hist_entry *__hists__add_entry(struct hists *hists, p = &(*p)->rb_right; } - he = hist_entry__new(&entry); + he = hist_entry__new(entry); if (!he) goto out_unlock; @@ -252,6 +290,51 @@ out_unlock: return he; } +struct hist_entry *__hists__add_branch_entry(struct hists *self, + struct addr_location *al, + struct symbol *sym_parent, + struct branch_info *bi, + u64 period) +{ + struct hist_entry entry = { + .thread = al->thread, + .ms = { + .map = bi->to.map, + .sym = bi->to.sym, + }, + .cpu = al->cpu, + .ip = bi->to.addr, + .level = al->level, + .period = period, + .parent = sym_parent, + .filtered = symbol__parent_filter(sym_parent), + .branch_info = bi, + }; + + return add_hist_entry(self, &entry, al, period); +} + +struct hist_entry *__hists__add_entry(struct hists *self, + struct addr_location *al, + struct symbol *sym_parent, u64 period) +{ + struct hist_entry entry = { + .thread = al->thread, + .ms = { + .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), + }; + + return add_hist_entry(self, &entry, al, period); +} + int64_t hist_entry__cmp(struct hist_entry *left, struct hist_entry *right) { @@ -352,6 +435,7 @@ static void hists__apply_filters(struct hists *hists, struct hist_entry *he) { hists__filter_entry_by_dso(hists, he); hists__filter_entry_by_thread(hists, he); + hists__filter_entry_by_symbol(hists, he); } static void __hists__collapse_resort(struct hists *hists, bool threaded) @@ -515,7 +599,7 @@ static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, if (chain->ms.sym) ret += fprintf(fp, "%s\n", chain->ms.sym->name); else - ret += fprintf(fp, "%p\n", (void *)(long)chain->ip); + ret += fprintf(fp, "0x%0" PRIx64 "\n", chain->ip); return ret; } @@ -535,7 +619,7 @@ static void init_rem_hits(void) rem_hits.ms.sym = rem_sq_bracket; } -static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, +static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, u64 total_samples, int depth, int depth_mask, int left_margin) { @@ -543,21 +627,16 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, struct callchain_node *child; struct callchain_list *chain; int new_depth_mask = depth_mask; - u64 new_total; u64 remaining; size_t ret = 0; int i; uint entries_printed = 0; - if (callchain_param.mode == CHAIN_GRAPH_REL) - new_total = self->children_hit; - else - new_total = total_samples; + remaining = total_samples; - remaining = new_total; - - node = rb_first(&self->rb_root); + node = rb_first(root); while (node) { + u64 new_total; u64 cumul; child = rb_entry(node, struct callchain_node, rb_node); @@ -585,11 +664,17 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, list_for_each_entry(chain, &child->val, list) { ret += ipchain__fprintf_graph(fp, chain, depth, new_depth_mask, i++, - new_total, + total_samples, cumul, left_margin); } - ret += __callchain__fprintf_graph(fp, child, new_total, + + if (callchain_param.mode == CHAIN_GRAPH_REL) + new_total = child->children_hit; + else + new_total = total_samples; + + ret += __callchain__fprintf_graph(fp, &child->rb_root, new_total, depth + 1, new_depth_mask | (1 << depth), left_margin); @@ -599,61 +684,75 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, } if (callchain_param.mode == CHAIN_GRAPH_REL && - remaining && remaining != new_total) { + remaining && remaining != total_samples) { if (!rem_sq_bracket) return ret; new_depth_mask &= ~(1 << (depth - 1)); - ret += ipchain__fprintf_graph(fp, &rem_hits, depth, - new_depth_mask, 0, new_total, + new_depth_mask, 0, total_samples, remaining, left_margin); } return ret; } -static size_t callchain__fprintf_graph(FILE *fp, struct callchain_node *self, +static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, u64 total_samples, int left_margin) { + struct callchain_node *cnode; struct callchain_list *chain; + u32 entries_printed = 0; bool printed = false; + struct rb_node *node; int i = 0; - int ret = 0; - u32 entries_printed = 0; - - list_for_each_entry(chain, &self->val, list) { - if (!i++ && sort__first_dimension == SORT_SYM) - continue; - - if (!printed) { - ret += callchain__fprintf_left_margin(fp, left_margin); - ret += fprintf(fp, "|\n"); - ret += callchain__fprintf_left_margin(fp, left_margin); - ret += fprintf(fp, "---"); - - left_margin += 3; - printed = true; - } else - ret += callchain__fprintf_left_margin(fp, left_margin); + int ret; - if (chain->ms.sym) - ret += fprintf(fp, " %s\n", chain->ms.sym->name); - else - ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); + /* + * If have one single callchain root, don't bother printing + * its percentage (100 % in fractal mode and the same percentage + * than the hist in graph mode). This also avoid one level of column. + */ + node = rb_first(root); + if (node && !rb_next(node)) { + cnode = rb_entry(node, struct callchain_node, rb_node); + list_for_each_entry(chain, &cnode->val, list) { + /* + * If we sort by symbol, the first entry is the same than + * the symbol. No need to print it otherwise it appears as + * displayed twice. + */ + if (!i++ && sort__first_dimension == SORT_SYM) + continue; + if (!printed) { + ret += callchain__fprintf_left_margin(fp, left_margin); + ret += fprintf(fp, "|\n"); + ret += callchain__fprintf_left_margin(fp, left_margin); + ret += fprintf(fp, "---"); + left_margin += 3; + printed = true; + } else + ret += callchain__fprintf_left_margin(fp, left_margin); + + if (chain->ms.sym) + ret += fprintf(fp, " %s\n", chain->ms.sym->name); + else + ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); - if (++entries_printed == callchain_param.print_limit) - break; + if (++entries_printed == callchain_param.print_limit) + break; + } + root = &cnode->rb_root; } - ret += __callchain__fprintf_graph(fp, self, total_samples, 1, 1, left_margin); - - return ret; + return __callchain__fprintf_graph(fp, root, total_samples, + 1, 1, left_margin); } -static size_t callchain__fprintf_flat(FILE *fp, struct callchain_node *self, - u64 total_samples) +static size_t __callchain__fprintf_flat(FILE *fp, + struct callchain_node *self, + u64 total_samples) { struct callchain_list *chain; size_t ret = 0; @@ -661,7 +760,7 @@ static size_t callchain__fprintf_flat(FILE *fp, struct callchain_node *self, if (!self) return 0; - ret += callchain__fprintf_flat(fp, self->parent, total_samples); + ret += __callchain__fprintf_flat(fp, self->parent, total_samples); list_for_each_entry(chain, &self->val, list) { @@ -677,44 +776,58 @@ static size_t callchain__fprintf_flat(FILE *fp, struct callchain_node *self, return ret; } -static size_t hist_entry_callchain__fprintf(struct hist_entry *he, - u64 total_samples, int left_margin, - FILE *fp) +static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *self, + u64 total_samples) { - struct rb_node *rb_node; - struct callchain_node *chain; size_t ret = 0; u32 entries_printed = 0; + struct rb_node *rb_node; + struct callchain_node *chain; - rb_node = rb_first(&he->sorted_chain); + rb_node = rb_first(self); while (rb_node) { double percent; chain = rb_entry(rb_node, struct callchain_node, rb_node); percent = chain->hit * 100.0 / total_samples; - switch (callchain_param.mode) { - case CHAIN_FLAT: - ret += percent_color_fprintf(fp, " %6.2f%%\n", - percent); - ret += callchain__fprintf_flat(fp, chain, total_samples); - break; - case CHAIN_GRAPH_ABS: /* Falldown */ - case CHAIN_GRAPH_REL: - ret += callchain__fprintf_graph(fp, chain, total_samples, - left_margin); - case CHAIN_NONE: - default: - break; - } + + ret = percent_color_fprintf(fp, " %6.2f%%\n", percent); + ret += __callchain__fprintf_flat(fp, chain, total_samples); ret += fprintf(fp, "\n"); if (++entries_printed == callchain_param.print_limit) break; + rb_node = rb_next(rb_node); } return ret; } +static size_t hist_entry_callchain__fprintf(struct hist_entry *he, + u64 total_samples, int left_margin, + FILE *fp) +{ + switch (callchain_param.mode) { + case CHAIN_GRAPH_REL: + return callchain__fprintf_graph(fp, &he->sorted_chain, he->period, + left_margin); + break; + case CHAIN_GRAPH_ABS: + return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, + left_margin); + break; + case CHAIN_FLAT: + return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples); + break; + case CHAIN_NONE: + break; + default: + pr_err("Bad callchain mode\n"); + } + + return 0; +} + void hists__output_recalc_col_len(struct hists *hists, int max_rows) { struct rb_node *next = rb_first(&hists->entries); @@ -768,7 +881,7 @@ static int hist_entry__pcnt_snprintf(struct hist_entry *he, char *s, sep ? "%.2f" : " %6.2f%%", (period * 100.0) / total); else - ret = snprintf(s, size, sep ? "%.2f" : " %6.2f%%", + ret = scnprintf(s, size, sep ? "%.2f" : " %6.2f%%", (period * 100.0) / total); if (symbol_conf.show_cpu_utilization) { ret += percent_color_snprintf(s + ret, size - ret, @@ -791,20 +904,20 @@ static int hist_entry__pcnt_snprintf(struct hist_entry *he, char *s, } } } else - ret = snprintf(s, size, sep ? "%" PRIu64 : "%12" PRIu64 " ", period); + ret = scnprintf(s, size, sep ? "%" PRIu64 : "%12" PRIu64 " ", period); if (symbol_conf.show_nr_samples) { if (sep) - ret += snprintf(s + ret, size - ret, "%c%" PRIu64, *sep, nr_events); + ret += scnprintf(s + ret, size - ret, "%c%" PRIu64, *sep, nr_events); else - ret += snprintf(s + ret, size - ret, "%11" PRIu64, nr_events); + ret += scnprintf(s + ret, size - ret, "%11" PRIu64, nr_events); } if (symbol_conf.show_total_period) { if (sep) - ret += snprintf(s + ret, size - ret, "%c%" PRIu64, *sep, period); + ret += scnprintf(s + ret, size - ret, "%c%" PRIu64, *sep, period); else - ret += snprintf(s + ret, size - ret, " %12" PRIu64, period); + ret += scnprintf(s + ret, size - ret, " %12" PRIu64, period); } if (pair_hists) { @@ -819,25 +932,25 @@ static int hist_entry__pcnt_snprintf(struct hist_entry *he, char *s, diff = new_percent - old_percent; if (fabs(diff) >= 0.01) - snprintf(bf, sizeof(bf), "%+4.2F%%", diff); + scnprintf(bf, sizeof(bf), "%+4.2F%%", diff); else - snprintf(bf, sizeof(bf), " "); + scnprintf(bf, sizeof(bf), " "); if (sep) - ret += snprintf(s + ret, size - ret, "%c%s", *sep, bf); + ret += scnprintf(s + ret, size - ret, "%c%s", *sep, bf); else - ret += snprintf(s + ret, size - ret, "%11.11s", bf); + ret += scnprintf(s + ret, size - ret, "%11.11s", bf); if (show_displacement) { if (displacement) - snprintf(bf, sizeof(bf), "%+4ld", displacement); + scnprintf(bf, sizeof(bf), "%+4ld", displacement); else - snprintf(bf, sizeof(bf), " "); + scnprintf(bf, sizeof(bf), " "); if (sep) - ret += snprintf(s + ret, size - ret, "%c%s", *sep, bf); + ret += scnprintf(s + ret, size - ret, "%c%s", *sep, bf); else - ret += snprintf(s + ret, size - ret, "%6.6s", bf); + ret += scnprintf(s + ret, size - ret, "%6.6s", bf); } } @@ -855,7 +968,7 @@ int hist_entry__snprintf(struct hist_entry *he, char *s, size_t size, if (se->elide) continue; - ret += snprintf(s + ret, size - ret, "%s", sep ?: " "); + ret += scnprintf(s + ret, size - ret, "%s", sep ?: " "); ret += se->se_snprintf(he, s + ret, size - ret, hists__col_len(hists, se->se_width_idx)); } @@ -1179,6 +1292,37 @@ void hists__filter_by_thread(struct hists *hists) } } +static bool hists__filter_entry_by_symbol(struct hists *hists, + struct hist_entry *he) +{ + if (hists->symbol_filter_str != NULL && + (!he->ms.sym || strstr(he->ms.sym->name, + hists->symbol_filter_str) == NULL)) { + he->filtered |= (1 << HIST_FILTER__SYMBOL); + return true; + } + + return false; +} + +void hists__filter_by_symbol(struct hists *hists) +{ + struct rb_node *nd; + + hists->nr_entries = hists->stats.total_period = 0; + hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0; + hists__reset_col_len(hists); + + for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { + struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); + + if (hists__filter_entry_by_symbol(hists, h)) + continue; + + hists__remove_entry_filter(hists, h, HIST_FILTER__SYMBOL); + } +} + int hist_entry__inc_addr_samples(struct hist_entry *he, int evidx, u64 ip) { return symbol__inc_addr_samples(he->ms.sym, he->ms.map, evidx, ip); diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index f55f0a8d1f816..cfc64e293f90b 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -32,6 +32,7 @@ struct events_stats { u32 nr_unknown_events; u32 nr_invalid_chains; u32 nr_unknown_id; + u32 nr_unprocessable_samples; }; enum hist_column { @@ -41,6 +42,11 @@ enum hist_column { HISTC_COMM, HISTC_PARENT, HISTC_CPU, + HISTC_MISPREDICT, + HISTC_SYMBOL_FROM, + HISTC_SYMBOL_TO, + HISTC_DSO_FROM, + HISTC_DSO_TO, HISTC_NR_COLS, /* Last entry */ }; @@ -55,6 +61,8 @@ struct hists { u64 nr_entries; const struct thread *thread_filter; const struct dso *dso_filter; + const char *uid_filter_str; + const char *symbol_filter_str; pthread_mutex_t lock; struct events_stats stats; u64 event_stream; @@ -72,6 +80,12 @@ int hist_entry__snprintf(struct hist_entry *self, char *bf, size_t size, struct hists *hists); void hist_entry__free(struct hist_entry *); +struct hist_entry *__hists__add_branch_entry(struct hists *self, + struct addr_location *al, + struct symbol *sym_parent, + struct branch_info *bi, + u64 period); + void hists__output_resort(struct hists *self); void hists__output_resort_threaded(struct hists *hists); void hists__collapse_resort(struct hists *self); @@ -94,6 +108,7 @@ int hist_entry__annotate(struct hist_entry *self, size_t privsize); void hists__filter_by_dso(struct hists *hists); void hists__filter_by_thread(struct hists *hists); +void hists__filter_by_symbol(struct hists *hists); u16 hists__col_len(struct hists *self, enum hist_column col); void hists__set_col_len(struct hists *self, enum hist_column col, u16 len); @@ -123,7 +138,7 @@ static inline int hist_entry__tui_annotate(struct hist_entry *self __used, #define K_LEFT -1 #define K_RIGHT -2 #else -#include "ui/keysyms.h" +#include "../ui/keysyms.h" int hist_entry__tui_annotate(struct hist_entry *he, int evidx, void(*timer)(void *arg), void *arg, int delay_secs); @@ -132,6 +147,23 @@ int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help, int refresh); #endif +#ifdef NO_GTK2_SUPPORT +static inline +int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist __used, + const char *help __used, + void(*timer)(void *arg) __used, + void *arg __used, + int refresh __used) +{ + return 0; +} + +#else +int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, const char *help, + void(*timer)(void *arg), void *arg, + int refresh); +#endif + unsigned int hists__sort_list_width(struct hists *self); #endif /* __PERF_HIST_H */ diff --git a/tools/perf/util/include/asm/dwarf2.h b/tools/perf/util/include/asm/dwarf2.h index bb4198e7837a8..afe38199e9224 100644 --- a/tools/perf/util/include/asm/dwarf2.h +++ b/tools/perf/util/include/asm/dwarf2.h @@ -2,10 +2,12 @@ #ifndef PERF_DWARF2_H #define PERF_DWARF2_H -/* dwarf2.h ... dummy header file for including arch/x86/lib/memcpy_64.S */ +/* dwarf2.h ... dummy header file for including arch/x86/lib/mem{cpy,set}_64.S */ #define CFI_STARTPROC #define CFI_ENDPROC +#define CFI_REMEMBER_STATE +#define CFI_RESTORE_STATE #endif /* PERF_DWARF2_H */ diff --git a/tools/perf/util/include/asm/unistd_32.h b/tools/perf/util/include/asm/unistd_32.h new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/tools/perf/util/include/asm/unistd_32.h @@ -0,0 +1 @@ + diff --git a/tools/perf/util/include/asm/unistd_64.h b/tools/perf/util/include/asm/unistd_64.h new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/tools/perf/util/include/asm/unistd_64.h @@ -0,0 +1 @@ + diff --git a/tools/perf/util/include/linux/bitmap.h b/tools/perf/util/include/linux/bitmap.h index eda4416efa0a9..bb162e40c76cf 100644 --- a/tools/perf/util/include/linux/bitmap.h +++ b/tools/perf/util/include/linux/bitmap.h @@ -5,6 +5,8 @@ #include <linux/bitops.h> int __bitmap_weight(const unsigned long *bitmap, int bits); +void __bitmap_or(unsigned long *dst, const unsigned long *bitmap1, + const unsigned long *bitmap2, int bits); #define BITMAP_LAST_WORD_MASK(nbits) \ ( \ @@ -32,4 +34,13 @@ static inline int bitmap_weight(const unsigned long *src, int nbits) return __bitmap_weight(src, nbits); } +static inline void bitmap_or(unsigned long *dst, const unsigned long *src1, + const unsigned long *src2, int nbits) +{ + if (small_const_nbits(nbits)) + *dst = *src1 | *src2; + else + __bitmap_or(dst, src1, src2, nbits); +} + #endif /* _PERF_BITOPS_H */ diff --git a/tools/perf/util/include/linux/bitops.h b/tools/perf/util/include/linux/bitops.h index 62cdee78db7b2..f1584833bd229 100644 --- a/tools/perf/util/include/linux/bitops.h +++ b/tools/perf/util/include/linux/bitops.h @@ -15,7 +15,7 @@ (bit) = find_next_bit((addr), (size), (bit) + 1)) /* same as for_each_set_bit() but use bit as value to start with */ -#define for_each_set_bit_cont(bit, addr, size) \ +#define for_each_set_bit_from(bit, addr, size) \ for ((bit) = find_next_bit((addr), (size), (bit)); \ (bit) < (size); \ (bit) = find_next_bit((addr), (size), (bit) + 1)) diff --git a/tools/perf/util/include/linux/module.h b/tools/perf/util/include/linux/export.h index b43e2dc21e04d..b43e2dc21e04d 100644 --- a/tools/perf/util/include/linux/module.h +++ b/tools/perf/util/include/linux/export.h diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index 316aa0ab71224..35ae56864e4f5 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c @@ -38,6 +38,7 @@ void map__init(struct map *self, enum map_type type, RB_CLEAR_NODE(&self->rb_node); self->groups = NULL; self->referenced = false; + self->erange_warned = false; } struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, @@ -212,6 +213,21 @@ size_t map__fprintf(struct map *self, FILE *fp) self->start, self->end, self->pgoff, self->dso->name); } +size_t map__fprintf_dsoname(struct map *map, FILE *fp) +{ + const char *dsoname; + + if (map && map->dso && (map->dso->name || map->dso->long_name)) { + if (symbol_conf.show_kernel_path && map->dso->long_name) + dsoname = map->dso->long_name; + else if (map->dso->name) + dsoname = map->dso->name; + } else + dsoname = "[unknown]"; + + return fprintf(fp, "%s", dsoname); +} + /* * objdump wants/reports absolute IPs for ET_EXEC, and RIPs for ET_DYN. * map->dso->adjust_symbols==1 for ET_EXEC-like cases. diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index 2b8017f8a9300..81371bad4ef0e 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -33,6 +33,7 @@ struct map { u64 end; u8 /* enum map_type */ type; bool referenced; + bool erange_warned; u32 priv; u64 pgoff; @@ -118,6 +119,7 @@ void map__delete(struct map *self); struct map *map__clone(struct map *self); int map__overlap(struct map *l, struct map *r); size_t map__fprintf(struct map *self, FILE *fp); +size_t map__fprintf_dsoname(struct map *map, FILE *fp); int map__load(struct map *self, symbol_filter_t filter); struct symbol *map__find_symbol(struct map *self, diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index b029296d20d9d..c7fc18a33d545 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -11,6 +11,10 @@ #include "cache.h" #include "header.h" #include "debugfs.h" +#include "parse-events-flex.h" +#include "pmu.h" + +#define MAX_NAME_LEN 100 struct event_symbol { u8 type; @@ -19,11 +23,8 @@ struct event_symbol { const char *alias; }; -enum event_result { - EVT_FAILED, - EVT_HANDLED, - EVT_HANDLED_ALL -}; +int parse_events_parse(struct list_head *list, struct list_head *list_tmp, + int *idx); #define CHW(x) .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_##x #define CSW(x) .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_##x @@ -165,7 +166,7 @@ struct tracepoint_path *tracepoint_id_to_path(u64 config) struct tracepoint_path *path = NULL; DIR *sys_dir, *evt_dir; struct dirent *sys_next, *evt_next, sys_dirent, evt_dirent; - char id_buf[4]; + char id_buf[24]; int fd; u64 id; char evt_path[MAXPATHLEN]; @@ -354,7 +355,24 @@ const char *__event_name(int type, u64 config) return "unknown"; } -static int parse_aliases(const char **str, const char *names[][MAX_ALIASES], int size) +static int add_event(struct list_head *list, int *idx, + struct perf_event_attr *attr, char *name) +{ + struct perf_evsel *evsel; + + event_attr_init(attr); + + evsel = perf_evsel__new(attr, (*idx)++); + if (!evsel) + return -ENOMEM; + + list_add_tail(&evsel->node, list); + + evsel->name = strdup(name); + return 0; +} + +static int parse_aliases(char *str, const char *names[][MAX_ALIASES], int size) { int i, j; int n, longest = -1; @@ -362,58 +380,57 @@ static int parse_aliases(const char **str, const char *names[][MAX_ALIASES], int for (i = 0; i < size; i++) { for (j = 0; j < MAX_ALIASES && names[i][j]; j++) { n = strlen(names[i][j]); - if (n > longest && !strncasecmp(*str, names[i][j], n)) + if (n > longest && !strncasecmp(str, names[i][j], n)) longest = n; } - if (longest > 0) { - *str += longest; + if (longest > 0) return i; - } } return -1; } -static enum event_result -parse_generic_hw_event(const char **str, struct perf_event_attr *attr) +int parse_events_add_cache(struct list_head *list, int *idx, + char *type, char *op_result1, char *op_result2) { - const char *s = *str; + struct perf_event_attr attr; + char name[MAX_NAME_LEN]; int cache_type = -1, cache_op = -1, cache_result = -1; + char *op_result[2] = { op_result1, op_result2 }; + int i, n; - cache_type = parse_aliases(&s, hw_cache, PERF_COUNT_HW_CACHE_MAX); /* * No fallback - if we cannot get a clear cache type * then bail out: */ + cache_type = parse_aliases(type, hw_cache, + PERF_COUNT_HW_CACHE_MAX); if (cache_type == -1) - return EVT_FAILED; + return -EINVAL; - while ((cache_op == -1 || cache_result == -1) && *s == '-') { - ++s; + n = snprintf(name, MAX_NAME_LEN, "%s", type); + + for (i = 0; (i < 2) && (op_result[i]); i++) { + char *str = op_result[i]; + + snprintf(name + n, MAX_NAME_LEN - n, "-%s\n", str); if (cache_op == -1) { - cache_op = parse_aliases(&s, hw_cache_op, - PERF_COUNT_HW_CACHE_OP_MAX); + cache_op = parse_aliases(str, hw_cache_op, + PERF_COUNT_HW_CACHE_OP_MAX); if (cache_op >= 0) { if (!is_cache_op_valid(cache_type, cache_op)) - return EVT_FAILED; + return -EINVAL; continue; } } if (cache_result == -1) { - cache_result = parse_aliases(&s, hw_cache_result, + cache_result = parse_aliases(str, hw_cache_result, PERF_COUNT_HW_CACHE_RESULT_MAX); if (cache_result >= 0) continue; } - - /* - * Can't parse this as a cache op or result, so back up - * to the '-'. - */ - --s; - break; } /* @@ -428,20 +445,17 @@ parse_generic_hw_event(const char **str, struct perf_event_attr *attr) if (cache_result == -1) cache_result = PERF_COUNT_HW_CACHE_RESULT_ACCESS; - attr->config = cache_type | (cache_op << 8) | (cache_result << 16); - attr->type = PERF_TYPE_HW_CACHE; - - *str = s; - return EVT_HANDLED; + memset(&attr, 0, sizeof(attr)); + attr.config = cache_type | (cache_op << 8) | (cache_result << 16); + attr.type = PERF_TYPE_HW_CACHE; + return add_event(list, idx, &attr, name); } -static enum event_result -parse_single_tracepoint_event(char *sys_name, - const char *evt_name, - unsigned int evt_length, - struct perf_event_attr *attr, - const char **strp) +static int add_tracepoint(struct list_head *list, int *idx, + char *sys_name, char *evt_name) { + struct perf_event_attr attr; + char name[MAX_NAME_LEN]; char evt_path[MAXPATHLEN]; char id_buf[4]; u64 id; @@ -452,130 +466,80 @@ parse_single_tracepoint_event(char *sys_name, fd = open(evt_path, O_RDONLY); if (fd < 0) - return EVT_FAILED; + return -1; if (read(fd, id_buf, sizeof(id_buf)) < 0) { close(fd); - return EVT_FAILED; + return -1; } close(fd); id = atoll(id_buf); - attr->config = id; - attr->type = PERF_TYPE_TRACEPOINT; - *strp += strlen(sys_name) + evt_length + 1; /* + 1 for the ':' */ - - attr->sample_type |= PERF_SAMPLE_RAW; - attr->sample_type |= PERF_SAMPLE_TIME; - attr->sample_type |= PERF_SAMPLE_CPU; - - attr->sample_period = 1; + memset(&attr, 0, sizeof(attr)); + attr.config = id; + attr.type = PERF_TYPE_TRACEPOINT; + attr.sample_type |= PERF_SAMPLE_RAW; + attr.sample_type |= PERF_SAMPLE_TIME; + attr.sample_type |= PERF_SAMPLE_CPU; + attr.sample_period = 1; - return EVT_HANDLED; + snprintf(name, MAX_NAME_LEN, "%s:%s", sys_name, evt_name); + return add_event(list, idx, &attr, name); } -/* sys + ':' + event + ':' + flags*/ -#define MAX_EVOPT_LEN (MAX_EVENT_LENGTH * 2 + 2 + 128) -static enum event_result -parse_multiple_tracepoint_event(struct perf_evlist *evlist, char *sys_name, - const char *evt_exp, char *flags) +static int add_tracepoint_multi(struct list_head *list, int *idx, + char *sys_name, char *evt_name) { char evt_path[MAXPATHLEN]; struct dirent *evt_ent; DIR *evt_dir; + int ret = 0; snprintf(evt_path, MAXPATHLEN, "%s/%s", tracing_events_path, sys_name); evt_dir = opendir(evt_path); - if (!evt_dir) { perror("Can't open event dir"); - return EVT_FAILED; + return -1; } - while ((evt_ent = readdir(evt_dir))) { - char event_opt[MAX_EVOPT_LEN + 1]; - int len; - + while (!ret && (evt_ent = readdir(evt_dir))) { if (!strcmp(evt_ent->d_name, ".") || !strcmp(evt_ent->d_name, "..") || !strcmp(evt_ent->d_name, "enable") || !strcmp(evt_ent->d_name, "filter")) continue; - if (!strglobmatch(evt_ent->d_name, evt_exp)) + if (!strglobmatch(evt_ent->d_name, evt_name)) continue; - len = snprintf(event_opt, MAX_EVOPT_LEN, "%s:%s%s%s", sys_name, - evt_ent->d_name, flags ? ":" : "", - flags ?: ""); - if (len < 0) - return EVT_FAILED; - - if (parse_events(evlist, event_opt, 0)) - return EVT_FAILED; + ret = add_tracepoint(list, idx, sys_name, evt_ent->d_name); } - return EVT_HANDLED_ALL; + return ret; } -static enum event_result -parse_tracepoint_event(struct perf_evlist *evlist, const char **strp, - struct perf_event_attr *attr) +int parse_events_add_tracepoint(struct list_head *list, int *idx, + char *sys, char *event) { - const char *evt_name; - char *flags = NULL, *comma_loc; - char sys_name[MAX_EVENT_LENGTH]; - unsigned int sys_length, evt_length; - - if (debugfs_valid_mountpoint(tracing_events_path)) - return 0; - - evt_name = strchr(*strp, ':'); - if (!evt_name) - return EVT_FAILED; + int ret; - sys_length = evt_name - *strp; - if (sys_length >= MAX_EVENT_LENGTH) - return 0; + ret = debugfs_valid_mountpoint(tracing_events_path); + if (ret) + return ret; - strncpy(sys_name, *strp, sys_length); - sys_name[sys_length] = '\0'; - evt_name = evt_name + 1; - - comma_loc = strchr(evt_name, ','); - if (comma_loc) { - /* take the event name up to the comma */ - evt_name = strndup(evt_name, comma_loc - evt_name); - } - flags = strchr(evt_name, ':'); - if (flags) { - /* split it out: */ - evt_name = strndup(evt_name, flags - evt_name); - flags++; - } - - evt_length = strlen(evt_name); - if (evt_length >= MAX_EVENT_LENGTH) - return EVT_FAILED; - if (strpbrk(evt_name, "*?")) { - *strp += strlen(sys_name) + evt_length + 1; /* 1 == the ':' */ - return parse_multiple_tracepoint_event(evlist, sys_name, - evt_name, flags); - } else { - return parse_single_tracepoint_event(sys_name, evt_name, - evt_length, attr, strp); - } + return strpbrk(event, "*?") ? + add_tracepoint_multi(list, idx, sys, event) : + add_tracepoint(list, idx, sys, event); } -static enum event_result -parse_breakpoint_type(const char *type, const char **strp, - struct perf_event_attr *attr) +static int +parse_breakpoint_type(const char *type, struct perf_event_attr *attr) { int i; for (i = 0; i < 3; i++) { - if (!type[i]) + if (!type || !type[i]) break; switch (type[i]) { @@ -589,164 +553,158 @@ parse_breakpoint_type(const char *type, const char **strp, attr->bp_type |= HW_BREAKPOINT_X; break; default: - return EVT_FAILED; + return -EINVAL; } } + if (!attr->bp_type) /* Default */ attr->bp_type = HW_BREAKPOINT_R | HW_BREAKPOINT_W; - *strp = type + i; - - return EVT_HANDLED; + return 0; } -static enum event_result -parse_breakpoint_event(const char **strp, struct perf_event_attr *attr) +int parse_events_add_breakpoint(struct list_head *list, int *idx, + void *ptr, char *type) { - const char *target; - const char *type; - char *endaddr; - u64 addr; - enum event_result err; - - target = strchr(*strp, ':'); - if (!target) - return EVT_FAILED; - - if (strncmp(*strp, "mem", target - *strp) != 0) - return EVT_FAILED; - - target++; - - addr = strtoull(target, &endaddr, 0); - if (target == endaddr) - return EVT_FAILED; - - attr->bp_addr = addr; - *strp = endaddr; + struct perf_event_attr attr; + char name[MAX_NAME_LEN]; - type = strchr(target, ':'); + memset(&attr, 0, sizeof(attr)); + attr.bp_addr = (unsigned long) ptr; - /* If no type is defined, just rw as default */ - if (!type) { - attr->bp_type = HW_BREAKPOINT_R | HW_BREAKPOINT_W; - } else { - err = parse_breakpoint_type(++type, strp, attr); - if (err == EVT_FAILED) - return EVT_FAILED; - } + if (parse_breakpoint_type(type, &attr)) + return -EINVAL; /* * 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); + if (attr.bp_type == HW_BREAKPOINT_X) + attr.bp_len = sizeof(long); else - attr->bp_len = HW_BREAKPOINT_LEN_4; + attr.bp_len = HW_BREAKPOINT_LEN_4; - attr->type = PERF_TYPE_BREAKPOINT; + attr.type = PERF_TYPE_BREAKPOINT; - return EVT_HANDLED; + snprintf(name, MAX_NAME_LEN, "mem:%p:%s", ptr, type ? type : "rw"); + return add_event(list, idx, &attr, name); } -static int check_events(const char *str, unsigned int i) +static int config_term(struct perf_event_attr *attr, + struct parse_events__term *term) { - int n; +#define CHECK_TYPE_VAL(type) \ +do { \ + if (PARSE_EVENTS__TERM_TYPE_ ## type != term->type_val) \ + return -EINVAL; \ +} while (0) + + switch (term->type_term) { + case PARSE_EVENTS__TERM_TYPE_CONFIG: + CHECK_TYPE_VAL(NUM); + attr->config = term->val.num; + break; + case PARSE_EVENTS__TERM_TYPE_CONFIG1: + CHECK_TYPE_VAL(NUM); + attr->config1 = term->val.num; + break; + case PARSE_EVENTS__TERM_TYPE_CONFIG2: + CHECK_TYPE_VAL(NUM); + attr->config2 = term->val.num; + break; + case PARSE_EVENTS__TERM_TYPE_SAMPLE_PERIOD: + CHECK_TYPE_VAL(NUM); + attr->sample_period = term->val.num; + break; + case PARSE_EVENTS__TERM_TYPE_BRANCH_SAMPLE_TYPE: + /* + * TODO uncomment when the field is available + * attr->branch_sample_type = term->val.num; + */ + break; + default: + return -EINVAL; + } + + return 0; +#undef CHECK_TYPE_VAL +} - n = strlen(event_symbols[i].symbol); - if (!strncasecmp(str, event_symbols[i].symbol, n)) - return n; +static int config_attr(struct perf_event_attr *attr, + struct list_head *head, int fail) +{ + struct parse_events__term *term; - n = strlen(event_symbols[i].alias); - if (n) { - if (!strncasecmp(str, event_symbols[i].alias, n)) - return n; - } + list_for_each_entry(term, head, list) + if (config_term(attr, term) && fail) + return -EINVAL; return 0; } -static enum event_result -parse_symbolic_event(const char **strp, struct perf_event_attr *attr) +int parse_events_add_numeric(struct list_head *list, int *idx, + unsigned long type, unsigned long config, + struct list_head *head_config) { - const char *str = *strp; - unsigned int i; - int n; - - for (i = 0; i < ARRAY_SIZE(event_symbols); i++) { - n = check_events(str, i); - if (n > 0) { - attr->type = event_symbols[i].type; - attr->config = event_symbols[i].config; - *strp = str + n; - return EVT_HANDLED; - } - } - return EVT_FAILED; + struct perf_event_attr attr; + + memset(&attr, 0, sizeof(attr)); + attr.type = type; + attr.config = config; + + if (head_config && + config_attr(&attr, head_config, 1)) + return -EINVAL; + + return add_event(list, idx, &attr, + (char *) __event_name(type, config)); } -static enum event_result -parse_raw_event(const char **strp, struct perf_event_attr *attr) +int parse_events_add_pmu(struct list_head *list, int *idx, + char *name, struct list_head *head_config) { - const char *str = *strp; - u64 config; - int n; - - if (*str != 'r') - return EVT_FAILED; - n = hex2u64(str + 1, &config); - if (n > 0) { - const char *end = str + n + 1; - if (*end != '\0' && *end != ',' && *end != ':') - return EVT_FAILED; - - *strp = end; - attr->type = PERF_TYPE_RAW; - attr->config = config; - return EVT_HANDLED; - } - return EVT_FAILED; + struct perf_event_attr attr; + struct perf_pmu *pmu; + + pmu = perf_pmu__find(name); + if (!pmu) + return -EINVAL; + + memset(&attr, 0, sizeof(attr)); + + /* + * Configure hardcoded terms first, no need to check + * return value when called with fail == 0 ;) + */ + config_attr(&attr, head_config, 0); + + if (perf_pmu__config(pmu, &attr, head_config)) + return -EINVAL; + + return add_event(list, idx, &attr, (char *) "pmu"); } -static enum event_result -parse_numeric_event(const char **strp, struct perf_event_attr *attr) +void parse_events_update_lists(struct list_head *list_event, + struct list_head *list_all) { - const char *str = *strp; - char *endp; - unsigned long type; - u64 config; - - type = strtoul(str, &endp, 0); - if (endp > str && type < PERF_TYPE_MAX && *endp == ':') { - str = endp + 1; - config = strtoul(str, &endp, 0); - if (endp > str) { - attr->type = type; - attr->config = config; - *strp = endp; - return EVT_HANDLED; - } - } - return EVT_FAILED; + /* + * Called for single event definition. Update the + * 'all event' list, and reinit the 'signle event' + * list, for next event definition. + */ + list_splice_tail(list_event, list_all); + INIT_LIST_HEAD(list_event); } -static int -parse_event_modifier(const char **strp, struct perf_event_attr *attr) +int parse_events_modifier(struct list_head *list, char *str) { - const char *str = *strp; + struct perf_evsel *evsel; int exclude = 0, exclude_GH = 0; int eu = 0, ek = 0, eh = 0, eH = 0, eG = 0, precise = 0; - if (!*str) - return 0; - - if (*str == ',') + if (str == NULL) return 0; - if (*str++ != ':') - return -1; - while (*str) { if (*str == 'u') { if (!exclude) @@ -775,111 +733,62 @@ parse_event_modifier(const char **strp, struct perf_event_attr *attr) ++str; } - if (str < *strp + 2) - return -1; - *strp = str; + /* + * precise ip: + * + * 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 + * + * See also PERF_RECORD_MISC_EXACT_IP + */ + if (precise > 3) + return -EINVAL; - attr->exclude_user = eu; - attr->exclude_kernel = ek; - attr->exclude_hv = eh; - attr->precise_ip = precise; - attr->exclude_host = eH; - attr->exclude_guest = eG; + list_for_each_entry(evsel, list, node) { + evsel->attr.exclude_user = eu; + evsel->attr.exclude_kernel = ek; + evsel->attr.exclude_hv = eh; + evsel->attr.precise_ip = precise; + evsel->attr.exclude_host = eH; + evsel->attr.exclude_guest = eG; + } return 0; } -/* - * Each event can have multiple symbolic names. - * Symbolic names are (almost) exactly matched. - */ -static enum event_result -parse_event_symbols(struct perf_evlist *evlist, const char **str, - struct perf_event_attr *attr) +int parse_events(struct perf_evlist *evlist, const char *str, int unset __used) { - enum event_result ret; - - ret = parse_tracepoint_event(evlist, str, attr); - if (ret != EVT_FAILED) - goto modifier; + LIST_HEAD(list); + LIST_HEAD(list_tmp); + YY_BUFFER_STATE buffer; + int ret, idx = evlist->nr_entries; - ret = parse_raw_event(str, attr); - if (ret != EVT_FAILED) - goto modifier; + buffer = parse_events__scan_string(str); - ret = parse_numeric_event(str, attr); - if (ret != EVT_FAILED) - goto modifier; + ret = parse_events_parse(&list, &list_tmp, &idx); - ret = parse_symbolic_event(str, attr); - if (ret != EVT_FAILED) - goto modifier; + parse_events__flush_buffer(buffer); + parse_events__delete_buffer(buffer); - ret = parse_generic_hw_event(str, attr); - if (ret != EVT_FAILED) - goto modifier; - - ret = parse_breakpoint_event(str, attr); - if (ret != EVT_FAILED) - goto modifier; - - fprintf(stderr, "invalid or unsupported event: '%s'\n", *str); - fprintf(stderr, "Run 'perf list' for a list of valid events\n"); - return EVT_FAILED; - -modifier: - if (parse_event_modifier(str, attr) < 0) { - fprintf(stderr, "invalid event modifier: '%s'\n", *str); - fprintf(stderr, "Run 'perf list' for a list of valid events and modifiers\n"); - - return EVT_FAILED; + if (!ret) { + int entries = idx - evlist->nr_entries; + perf_evlist__splice_list_tail(evlist, &list, entries); + return 0; } + /* + * There are 2 users - builtin-record and builtin-test objects. + * Both call perf_evlist__delete in case of error, so we dont + * need to bother. + */ + fprintf(stderr, "invalid or unsupported event: '%s'\n", str); + fprintf(stderr, "Run 'perf list' for a list of valid events\n"); return ret; } -int parse_events(struct perf_evlist *evlist , const char *str, int unset __used) -{ - struct perf_event_attr attr; - enum event_result ret; - const char *ostr; - - for (;;) { - ostr = str; - memset(&attr, 0, sizeof(attr)); - event_attr_init(&attr); - ret = parse_event_symbols(evlist, &str, &attr); - if (ret == EVT_FAILED) - return -1; - - if (!(*str == 0 || *str == ',' || isspace(*str))) - return -1; - - if (ret != EVT_HANDLED_ALL) { - struct perf_evsel *evsel; - evsel = perf_evsel__new(&attr, evlist->nr_entries); - if (evsel == NULL) - return -1; - perf_evlist__add(evlist, evsel); - - evsel->name = calloc(str - ostr + 1, 1); - if (!evsel->name) - return -1; - strncpy(evsel->name, ostr, str - ostr); - } - - if (*str == 0) - break; - if (*str == ',') - ++str; - while (isspace(*str)) - ++str; - } - - return 0; -} - int parse_events_option(const struct option *opt, const char *str, int unset __used) { @@ -1052,8 +961,6 @@ int print_hwcache_events(const char *event_glob) return printed; } -#define MAX_NAME_LEN 100 - /* * Print the help text for the event symbols: */ @@ -1102,8 +1009,12 @@ void print_events(const char *event_glob) printf("\n"); printf(" %-50s [%s]\n", - "rNNN (see 'perf list --help' on how to encode it)", + "rNNN", + event_type_descriptors[PERF_TYPE_RAW]); + printf(" %-50s [%s]\n", + "cpu/t1=v1[,t2=v2,t3 ...]/modifier", event_type_descriptors[PERF_TYPE_RAW]); + printf(" (see 'perf list --help' on how to encode it)\n"); printf("\n"); printf(" %-50s [%s]\n", @@ -1113,3 +1024,62 @@ void print_events(const char *event_glob) print_tracepoint_events(NULL, NULL); } + +int parse_events__is_hardcoded_term(struct parse_events__term *term) +{ + return term->type_term != PARSE_EVENTS__TERM_TYPE_USER; +} + +static int new_term(struct parse_events__term **_term, int type_val, + int type_term, char *config, + char *str, long num) +{ + struct parse_events__term *term; + + term = zalloc(sizeof(*term)); + if (!term) + return -ENOMEM; + + INIT_LIST_HEAD(&term->list); + term->type_val = type_val; + term->type_term = type_term; + term->config = config; + + switch (type_val) { + case PARSE_EVENTS__TERM_TYPE_NUM: + term->val.num = num; + break; + case PARSE_EVENTS__TERM_TYPE_STR: + term->val.str = str; + break; + default: + return -EINVAL; + } + + *_term = term; + return 0; +} + +int parse_events__term_num(struct parse_events__term **term, + int type_term, char *config, long num) +{ + return new_term(term, PARSE_EVENTS__TERM_TYPE_NUM, type_term, + config, NULL, num); +} + +int parse_events__term_str(struct parse_events__term **term, + int type_term, char *config, char *str) +{ + return new_term(term, PARSE_EVENTS__TERM_TYPE_STR, type_term, + config, str, 0); +} + +void parse_events__free_terms(struct list_head *terms) +{ + struct parse_events__term *term, *h; + + list_for_each_entry_safe(term, h, terms, list) + free(term); + + free(terms); +} diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h index 7e0cbe75d5f1f..3fddd610d3509 100644 --- a/tools/perf/util/parse-events.h +++ b/tools/perf/util/parse-events.h @@ -4,7 +4,9 @@ * Parse symbolic events/counts passed in as options: */ +#include <stdbool.h> #include "../../../include/linux/perf_event.h" +#include "types.h" struct list_head; struct perf_evsel; @@ -33,6 +35,58 @@ extern int parse_filter(const struct option *opt, const char *str, int unset); #define EVENTS_HELP_MAX (128*1024) +enum { + PARSE_EVENTS__TERM_TYPE_NUM, + PARSE_EVENTS__TERM_TYPE_STR, +}; + +enum { + PARSE_EVENTS__TERM_TYPE_USER, + PARSE_EVENTS__TERM_TYPE_CONFIG, + PARSE_EVENTS__TERM_TYPE_CONFIG1, + PARSE_EVENTS__TERM_TYPE_CONFIG2, + PARSE_EVENTS__TERM_TYPE_SAMPLE_PERIOD, + PARSE_EVENTS__TERM_TYPE_BRANCH_SAMPLE_TYPE, +}; + +struct parse_events__term { + char *config; + union { + char *str; + long num; + } val; + int type_val; + int type_term; + struct list_head list; +}; + +int parse_events__is_hardcoded_term(struct parse_events__term *term); +int parse_events__term_num(struct parse_events__term **_term, + int type_term, char *config, long num); +int parse_events__term_str(struct parse_events__term **_term, + int type_term, char *config, char *str); +void parse_events__free_terms(struct list_head *terms); +int parse_events_modifier(struct list_head *list __used, char *str __used); +int parse_events_add_tracepoint(struct list_head *list, int *idx, + char *sys, char *event); +int parse_events_add_raw(struct perf_evlist *evlist, unsigned long config, + unsigned long config1, unsigned long config2, + char *mod); +int parse_events_add_numeric(struct list_head *list, int *idx, + unsigned long type, unsigned long config, + struct list_head *head_config); +int parse_events_add_cache(struct list_head *list, int *idx, + char *type, char *op_result1, char *op_result2); +int parse_events_add_breakpoint(struct list_head *list, int *idx, + void *ptr, char *type); +int parse_events_add_pmu(struct list_head *list, int *idx, + char *pmu , struct list_head *head_config); +void parse_events_update_lists(struct list_head *list_event, + struct list_head *list_all); +void parse_events_error(struct list_head *list_all, + struct list_head *list_event, + int *idx, char const *msg); + void print_events(const char *event_glob); void print_events_type(u8 type); void print_tracepoint_events(const char *subsys_glob, const char *event_glob); diff --git a/tools/perf/util/parse-events.l b/tools/perf/util/parse-events.l new file mode 100644 index 0000000000000..1fcf1bbc5458e --- /dev/null +++ b/tools/perf/util/parse-events.l @@ -0,0 +1,127 @@ + +%option prefix="parse_events_" + +%{ +#include <errno.h> +#include "../perf.h" +#include "parse-events-bison.h" +#include "parse-events.h" + +static int __value(char *str, int base, int token) +{ + long num; + + errno = 0; + num = strtoul(str, NULL, base); + if (errno) + return PE_ERROR; + + parse_events_lval.num = num; + return token; +} + +static int value(int base) +{ + return __value(parse_events_text, base, PE_VALUE); +} + +static int raw(void) +{ + return __value(parse_events_text + 1, 16, PE_RAW); +} + +static int str(int token) +{ + parse_events_lval.str = strdup(parse_events_text); + return token; +} + +static int sym(int type, int config) +{ + parse_events_lval.num = (type << 16) + config; + return PE_VALUE_SYM; +} + +static int term(int type) +{ + parse_events_lval.num = type; + return PE_TERM; +} + +%} + +num_dec [0-9]+ +num_hex 0x[a-fA-F0-9]+ +num_raw_hex [a-fA-F0-9]+ +name [a-zA-Z_*?][a-zA-Z0-9_*?]* +modifier_event [ukhpGH]{1,8} +modifier_bp [rwx] + +%% +cpu-cycles|cycles { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES); } +stalled-cycles-frontend|idle-cycles-frontend { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_FRONTEND); } +stalled-cycles-backend|idle-cycles-backend { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_BACKEND); } +instructions { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS); } +cache-references { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES); } +cache-misses { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES); } +branch-instructions|branches { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS); } +branch-misses { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES); } +bus-cycles { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES); } +ref-cycles { return sym(PERF_TYPE_HARDWARE, PERF_COUNT_HW_REF_CPU_CYCLES); } +cpu-clock { return sym(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK); } +task-clock { return sym(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK); } +page-faults|faults { return sym(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS); } +minor-faults { return sym(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MIN); } +major-faults { return sym(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MAJ); } +context-switches|cs { return sym(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES); } +cpu-migrations|migrations { return sym(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS); } +alignment-faults { return sym(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS); } +emulation-faults { return sym(PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS); } + +L1-dcache|l1-d|l1d|L1-data | +L1-icache|l1-i|l1i|L1-instruction | +LLC|L2 | +dTLB|d-tlb|Data-TLB | +iTLB|i-tlb|Instruction-TLB | +branch|branches|bpu|btb|bpc | +node { return str(PE_NAME_CACHE_TYPE); } + +load|loads|read | +store|stores|write | +prefetch|prefetches | +speculative-read|speculative-load | +refs|Reference|ops|access | +misses|miss { return str(PE_NAME_CACHE_OP_RESULT); } + + /* + * These are event config hardcoded term names to be specified + * within xxx/.../ syntax. So far we dont clash with other names, + * so we can put them here directly. In case the we have a conflict + * in future, this needs to go into '//' condition block. + */ +config { return term(PARSE_EVENTS__TERM_TYPE_CONFIG); } +config1 { return term(PARSE_EVENTS__TERM_TYPE_CONFIG1); } +config2 { return term(PARSE_EVENTS__TERM_TYPE_CONFIG2); } +period { return term(PARSE_EVENTS__TERM_TYPE_SAMPLE_PERIOD); } +branch_type { return term(PARSE_EVENTS__TERM_TYPE_BRANCH_SAMPLE_TYPE); } + +mem: { return PE_PREFIX_MEM; } +r{num_raw_hex} { return raw(); } +{num_dec} { return value(10); } +{num_hex} { return value(16); } + +{modifier_event} { return str(PE_MODIFIER_EVENT); } +{modifier_bp} { return str(PE_MODIFIER_BP); } +{name} { return str(PE_NAME); } +"/" { return '/'; } +- { return '-'; } +, { return ','; } +: { return ':'; } += { return '='; } + +%% + +int parse_events_wrap(void) +{ + return 1; +} diff --git a/tools/perf/util/parse-events.y b/tools/perf/util/parse-events.y new file mode 100644 index 0000000000000..936913ea0ab6a --- /dev/null +++ b/tools/perf/util/parse-events.y @@ -0,0 +1,229 @@ + +%name-prefix "parse_events_" +%parse-param {struct list_head *list_all} +%parse-param {struct list_head *list_event} +%parse-param {int *idx} + +%{ + +#define YYDEBUG 1 + +#include <linux/compiler.h> +#include <linux/list.h> +#include "types.h" +#include "util.h" +#include "parse-events.h" + +extern int parse_events_lex (void); + +#define ABORT_ON(val) \ +do { \ + if (val) \ + YYABORT; \ +} while (0) + +%} + +%token PE_VALUE PE_VALUE_SYM PE_RAW PE_TERM +%token PE_NAME +%token PE_MODIFIER_EVENT PE_MODIFIER_BP +%token PE_NAME_CACHE_TYPE PE_NAME_CACHE_OP_RESULT +%token PE_PREFIX_MEM PE_PREFIX_RAW +%token PE_ERROR +%type <num> PE_VALUE +%type <num> PE_VALUE_SYM +%type <num> PE_RAW +%type <num> PE_TERM +%type <str> PE_NAME +%type <str> PE_NAME_CACHE_TYPE +%type <str> PE_NAME_CACHE_OP_RESULT +%type <str> PE_MODIFIER_EVENT +%type <str> PE_MODIFIER_BP +%type <head> event_config +%type <term> event_term + +%union +{ + char *str; + unsigned long num; + struct list_head *head; + struct parse_events__term *term; +} +%% + +events: +events ',' event | event + +event: +event_def PE_MODIFIER_EVENT +{ + /* + * Apply modifier on all events added by single event definition + * (there could be more events added for multiple tracepoint + * definitions via '*?'. + */ + ABORT_ON(parse_events_modifier(list_event, $2)); + parse_events_update_lists(list_event, list_all); +} +| +event_def +{ + parse_events_update_lists(list_event, list_all); +} + +event_def: event_pmu | + event_legacy_symbol | + event_legacy_cache sep_dc | + event_legacy_mem | + event_legacy_tracepoint sep_dc | + event_legacy_numeric sep_dc | + event_legacy_raw sep_dc + +event_pmu: +PE_NAME '/' event_config '/' +{ + ABORT_ON(parse_events_add_pmu(list_event, idx, $1, $3)); + parse_events__free_terms($3); +} + +event_legacy_symbol: +PE_VALUE_SYM '/' event_config '/' +{ + int type = $1 >> 16; + int config = $1 & 255; + + ABORT_ON(parse_events_add_numeric(list_event, idx, type, config, $3)); + parse_events__free_terms($3); +} +| +PE_VALUE_SYM sep_slash_dc +{ + int type = $1 >> 16; + int config = $1 & 255; + + ABORT_ON(parse_events_add_numeric(list_event, idx, type, config, NULL)); +} + +event_legacy_cache: +PE_NAME_CACHE_TYPE '-' PE_NAME_CACHE_OP_RESULT '-' PE_NAME_CACHE_OP_RESULT +{ + ABORT_ON(parse_events_add_cache(list_event, idx, $1, $3, $5)); +} +| +PE_NAME_CACHE_TYPE '-' PE_NAME_CACHE_OP_RESULT +{ + ABORT_ON(parse_events_add_cache(list_event, idx, $1, $3, NULL)); +} +| +PE_NAME_CACHE_TYPE +{ + ABORT_ON(parse_events_add_cache(list_event, idx, $1, NULL, NULL)); +} + +event_legacy_mem: +PE_PREFIX_MEM PE_VALUE ':' PE_MODIFIER_BP sep_dc +{ + ABORT_ON(parse_events_add_breakpoint(list_event, idx, (void *) $2, $4)); +} +| +PE_PREFIX_MEM PE_VALUE sep_dc +{ + ABORT_ON(parse_events_add_breakpoint(list_event, idx, (void *) $2, NULL)); +} + +event_legacy_tracepoint: +PE_NAME ':' PE_NAME +{ + ABORT_ON(parse_events_add_tracepoint(list_event, idx, $1, $3)); +} + +event_legacy_numeric: +PE_VALUE ':' PE_VALUE +{ + ABORT_ON(parse_events_add_numeric(list_event, idx, $1, $3, NULL)); +} + +event_legacy_raw: +PE_RAW +{ + ABORT_ON(parse_events_add_numeric(list_event, idx, PERF_TYPE_RAW, $1, NULL)); +} + +event_config: +event_config ',' event_term +{ + struct list_head *head = $1; + struct parse_events__term *term = $3; + + ABORT_ON(!head); + list_add_tail(&term->list, head); + $$ = $1; +} +| +event_term +{ + struct list_head *head = malloc(sizeof(*head)); + struct parse_events__term *term = $1; + + ABORT_ON(!head); + INIT_LIST_HEAD(head); + list_add_tail(&term->list, head); + $$ = head; +} + +event_term: +PE_NAME '=' PE_NAME +{ + struct parse_events__term *term; + + ABORT_ON(parse_events__term_str(&term, PARSE_EVENTS__TERM_TYPE_USER, + $1, $3)); + $$ = term; +} +| +PE_NAME '=' PE_VALUE +{ + struct parse_events__term *term; + + ABORT_ON(parse_events__term_num(&term, PARSE_EVENTS__TERM_TYPE_USER, + $1, $3)); + $$ = term; +} +| +PE_NAME +{ + struct parse_events__term *term; + + ABORT_ON(parse_events__term_num(&term, PARSE_EVENTS__TERM_TYPE_USER, + $1, 1)); + $$ = term; +} +| +PE_TERM '=' PE_VALUE +{ + struct parse_events__term *term; + + ABORT_ON(parse_events__term_num(&term, $1, NULL, $3)); + $$ = term; +} +| +PE_TERM +{ + struct parse_events__term *term; + + ABORT_ON(parse_events__term_num(&term, $1, NULL, 1)); + $$ = term; +} + +sep_dc: ':' | + +sep_slash_dc: '/' | ':' | + +%% + +void parse_events_error(struct list_head *list_all __used, + struct list_head *list_event __used, + int *idx __used, + char const *msg __used) +{ +} diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c new file mode 100644 index 0000000000000..8ee219b7285b9 --- /dev/null +++ b/tools/perf/util/pmu.c @@ -0,0 +1,483 @@ + +#include <linux/list.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdio.h> +#include <dirent.h> +#include "sysfs.h" +#include "util.h" +#include "pmu.h" +#include "parse-events.h" + +int perf_pmu_parse(struct list_head *list, char *name); +extern FILE *perf_pmu_in; + +static LIST_HEAD(pmus); + +/* + * Parse & process all the sysfs attributes located under + * the directory specified in 'dir' parameter. + */ +static int pmu_format_parse(char *dir, struct list_head *head) +{ + struct dirent *evt_ent; + DIR *format_dir; + int ret = 0; + + format_dir = opendir(dir); + if (!format_dir) + return -EINVAL; + + while (!ret && (evt_ent = readdir(format_dir))) { + char path[PATH_MAX]; + char *name = evt_ent->d_name; + FILE *file; + + if (!strcmp(name, ".") || !strcmp(name, "..")) + continue; + + snprintf(path, PATH_MAX, "%s/%s", dir, name); + + ret = -EINVAL; + file = fopen(path, "r"); + if (!file) + break; + + perf_pmu_in = file; + ret = perf_pmu_parse(head, name); + fclose(file); + } + + closedir(format_dir); + return ret; +} + +/* + * Reading/parsing the default pmu format definition, which should be + * located at: + * /sys/bus/event_source/devices/<dev>/format as sysfs group attributes. + */ +static int pmu_format(char *name, struct list_head *format) +{ + struct stat st; + char path[PATH_MAX]; + const char *sysfs; + + sysfs = sysfs_find_mountpoint(); + if (!sysfs) + return -1; + + snprintf(path, PATH_MAX, + "%s/bus/event_source/devices/%s/format", sysfs, name); + + if (stat(path, &st) < 0) + return -1; + + if (pmu_format_parse(path, format)) + return -1; + + return 0; +} + +/* + * Reading/parsing the default pmu type value, which should be + * located at: + * /sys/bus/event_source/devices/<dev>/type as sysfs attribute. + */ +static int pmu_type(char *name, __u32 *type) +{ + struct stat st; + char path[PATH_MAX]; + const char *sysfs; + FILE *file; + int ret = 0; + + sysfs = sysfs_find_mountpoint(); + if (!sysfs) + return -1; + + snprintf(path, PATH_MAX, + "%s/bus/event_source/devices/%s/type", sysfs, name); + + if (stat(path, &st) < 0) + return -1; + + file = fopen(path, "r"); + if (!file) + return -EINVAL; + + if (1 != fscanf(file, "%u", type)) + ret = -1; + + fclose(file); + return ret; +} + +static struct perf_pmu *pmu_lookup(char *name) +{ + struct perf_pmu *pmu; + LIST_HEAD(format); + __u32 type; + + /* + * The pmu data we store & need consists of the pmu + * type value and format definitions. Load both right + * now. + */ + if (pmu_format(name, &format)) + return NULL; + + if (pmu_type(name, &type)) + return NULL; + + pmu = zalloc(sizeof(*pmu)); + if (!pmu) + return NULL; + + INIT_LIST_HEAD(&pmu->format); + list_splice(&format, &pmu->format); + pmu->name = strdup(name); + pmu->type = type; + return pmu; +} + +static struct perf_pmu *pmu_find(char *name) +{ + struct perf_pmu *pmu; + + list_for_each_entry(pmu, &pmus, list) + if (!strcmp(pmu->name, name)) + return pmu; + + return NULL; +} + +struct perf_pmu *perf_pmu__find(char *name) +{ + struct perf_pmu *pmu; + + /* + * Once PMU is loaded it stays in the list, + * so we keep us from multiple reading/parsing + * the pmu format definitions. + */ + pmu = pmu_find(name); + if (pmu) + return pmu; + + return pmu_lookup(name); +} + +static struct perf_pmu__format* +pmu_find_format(struct list_head *formats, char *name) +{ + struct perf_pmu__format *format; + + list_for_each_entry(format, formats, list) + if (!strcmp(format->name, name)) + return format; + + return NULL; +} + +/* + * Returns value based on the format definition (format parameter) + * and unformated value (value parameter). + * + * TODO maybe optimize a little ;) + */ +static __u64 pmu_format_value(unsigned long *format, __u64 value) +{ + unsigned long fbit, vbit; + __u64 v = 0; + + for (fbit = 0, vbit = 0; fbit < PERF_PMU_FORMAT_BITS; fbit++) { + + if (!test_bit(fbit, format)) + continue; + + if (!(value & (1llu << vbit++))) + continue; + + v |= (1llu << fbit); + } + + return v; +} + +/* + * Setup one of config[12] attr members based on the + * user input data - temr parameter. + */ +static int pmu_config_term(struct list_head *formats, + struct perf_event_attr *attr, + struct parse_events__term *term) +{ + struct perf_pmu__format *format; + __u64 *vp; + + /* + * Support only for hardcoded and numnerial terms. + * Hardcoded terms should be already in, so nothing + * to be done for them. + */ + if (parse_events__is_hardcoded_term(term)) + return 0; + + if (term->type_val != PARSE_EVENTS__TERM_TYPE_NUM) + return -EINVAL; + + format = pmu_find_format(formats, term->config); + if (!format) + return -EINVAL; + + switch (format->value) { + case PERF_PMU_FORMAT_VALUE_CONFIG: + vp = &attr->config; + break; + case PERF_PMU_FORMAT_VALUE_CONFIG1: + vp = &attr->config1; + break; + case PERF_PMU_FORMAT_VALUE_CONFIG2: + vp = &attr->config2; + break; + default: + return -EINVAL; + } + + /* + * XXX If we ever decide to go with string values for + * non-hardcoded terms, here's the place to translate + * them into value. + */ + *vp |= pmu_format_value(format->bits, term->val.num); + return 0; +} + +static int pmu_config(struct list_head *formats, struct perf_event_attr *attr, + struct list_head *head_terms) +{ + struct parse_events__term *term, *h; + + list_for_each_entry_safe(term, h, head_terms, list) + if (pmu_config_term(formats, attr, term)) + return -EINVAL; + + return 0; +} + +/* + * Configures event's 'attr' parameter based on the: + * 1) users input - specified in terms parameter + * 2) pmu format definitions - specified by pmu parameter + */ +int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr, + struct list_head *head_terms) +{ + attr->type = pmu->type; + return pmu_config(&pmu->format, attr, head_terms); +} + +int perf_pmu__new_format(struct list_head *list, char *name, + int config, unsigned long *bits) +{ + struct perf_pmu__format *format; + + format = zalloc(sizeof(*format)); + if (!format) + return -ENOMEM; + + format->name = strdup(name); + format->value = config; + memcpy(format->bits, bits, sizeof(format->bits)); + + list_add_tail(&format->list, list); + return 0; +} + +void perf_pmu__set_format(unsigned long *bits, long from, long to) +{ + long b; + + if (!to) + to = from; + + memset(bits, 0, BITS_TO_LONGS(PERF_PMU_FORMAT_BITS)); + for (b = from; b <= to; b++) + set_bit(b, bits); +} + +/* Simulated format definitions. */ +static struct test_format { + const char *name; + const char *value; +} test_formats[] = { + { "krava01", "config:0-1,62-63\n", }, + { "krava02", "config:10-17\n", }, + { "krava03", "config:5\n", }, + { "krava11", "config1:0,2,4,6,8,20-28\n", }, + { "krava12", "config1:63\n", }, + { "krava13", "config1:45-47\n", }, + { "krava21", "config2:0-3,10-13,20-23,30-33,40-43,50-53,60-63\n", }, + { "krava22", "config2:8,18,48,58\n", }, + { "krava23", "config2:28-29,38\n", }, +}; + +#define TEST_FORMATS_CNT (sizeof(test_formats) / sizeof(struct test_format)) + +/* Simulated users input. */ +static struct parse_events__term test_terms[] = { + { + .config = (char *) "krava01", + .val.num = 15, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava02", + .val.num = 170, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava03", + .val.num = 1, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava11", + .val.num = 27, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava12", + .val.num = 1, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava13", + .val.num = 2, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava21", + .val.num = 119, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava22", + .val.num = 11, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava23", + .val.num = 2, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, +}; +#define TERMS_CNT (sizeof(test_terms) / sizeof(struct parse_events__term)) + +/* + * Prepare format directory data, exported by kernel + * at /sys/bus/event_source/devices/<dev>/format. + */ +static char *test_format_dir_get(void) +{ + static char dir[PATH_MAX]; + unsigned int i; + + snprintf(dir, PATH_MAX, "/tmp/perf-pmu-test-format-XXXXXX"); + if (!mkdtemp(dir)) + return NULL; + + for (i = 0; i < TEST_FORMATS_CNT; i++) { + static char name[PATH_MAX]; + struct test_format *format = &test_formats[i]; + FILE *file; + + snprintf(name, PATH_MAX, "%s/%s", dir, format->name); + + file = fopen(name, "w"); + if (!file) + return NULL; + + if (1 != fwrite(format->value, strlen(format->value), 1, file)) + break; + + fclose(file); + } + + return dir; +} + +/* Cleanup format directory. */ +static int test_format_dir_put(char *dir) +{ + char buf[PATH_MAX]; + snprintf(buf, PATH_MAX, "rm -f %s/*\n", dir); + if (system(buf)) + return -1; + + snprintf(buf, PATH_MAX, "rmdir %s\n", dir); + return system(buf); +} + +static struct list_head *test_terms_list(void) +{ + static LIST_HEAD(terms); + unsigned int i; + + for (i = 0; i < TERMS_CNT; i++) + list_add_tail(&test_terms[i].list, &terms); + + return &terms; +} + +#undef TERMS_CNT + +int perf_pmu__test(void) +{ + char *format = test_format_dir_get(); + LIST_HEAD(formats); + struct list_head *terms = test_terms_list(); + int ret; + + if (!format) + return -EINVAL; + + do { + struct perf_event_attr attr; + + memset(&attr, 0, sizeof(attr)); + + ret = pmu_format_parse(format, &formats); + if (ret) + break; + + ret = pmu_config(&formats, &attr, terms); + if (ret) + break; + + ret = -EINVAL; + + if (attr.config != 0xc00000000002a823) + break; + if (attr.config1 != 0x8000400000000145) + break; + if (attr.config2 != 0x0400000020041d07) + break; + + ret = 0; + } while (0); + + test_format_dir_put(format); + return ret; +} diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h new file mode 100644 index 0000000000000..68c0db965e1f5 --- /dev/null +++ b/tools/perf/util/pmu.h @@ -0,0 +1,41 @@ +#ifndef __PMU_H +#define __PMU_H + +#include <linux/bitops.h> +#include "../../../include/linux/perf_event.h" + +enum { + PERF_PMU_FORMAT_VALUE_CONFIG, + PERF_PMU_FORMAT_VALUE_CONFIG1, + PERF_PMU_FORMAT_VALUE_CONFIG2, +}; + +#define PERF_PMU_FORMAT_BITS 64 + +struct perf_pmu__format { + char *name; + int value; + DECLARE_BITMAP(bits, PERF_PMU_FORMAT_BITS); + struct list_head list; +}; + +struct perf_pmu { + char *name; + __u32 type; + struct list_head format; + struct list_head list; +}; + +struct perf_pmu *perf_pmu__find(char *name); +int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr, + struct list_head *head_terms); + +int perf_pmu_wrap(void); +void perf_pmu_error(struct list_head *list, char *name, char const *msg); + +int perf_pmu__new_format(struct list_head *list, char *name, + int config, unsigned long *bits); +void perf_pmu__set_format(unsigned long *bits, long from, long to); + +int perf_pmu__test(void); +#endif /* __PMU_H */ diff --git a/tools/perf/util/pmu.l b/tools/perf/util/pmu.l new file mode 100644 index 0000000000000..a15d9fbd7c0ed --- /dev/null +++ b/tools/perf/util/pmu.l @@ -0,0 +1,43 @@ +%option prefix="perf_pmu_" + +%{ +#include <stdlib.h> +#include <linux/bitops.h> +#include "pmu.h" +#include "pmu-bison.h" + +static int value(int base) +{ + long num; + + errno = 0; + num = strtoul(perf_pmu_text, NULL, base); + if (errno) + return PP_ERROR; + + perf_pmu_lval.num = num; + return PP_VALUE; +} + +%} + +num_dec [0-9]+ + +%% + +{num_dec} { return value(10); } +config { return PP_CONFIG; } +config1 { return PP_CONFIG1; } +config2 { return PP_CONFIG2; } +- { return '-'; } +: { return ':'; } +, { return ','; } +. { ; } +\n { ; } + +%% + +int perf_pmu_wrap(void) +{ + return 1; +} diff --git a/tools/perf/util/pmu.y b/tools/perf/util/pmu.y new file mode 100644 index 0000000000000..20ea77e931690 --- /dev/null +++ b/tools/perf/util/pmu.y @@ -0,0 +1,93 @@ + +%name-prefix "perf_pmu_" +%parse-param {struct list_head *format} +%parse-param {char *name} + +%{ + +#include <linux/compiler.h> +#include <linux/list.h> +#include <linux/bitmap.h> +#include <string.h> +#include "pmu.h" + +extern int perf_pmu_lex (void); + +#define ABORT_ON(val) \ +do { \ + if (val) \ + YYABORT; \ +} while (0) + +%} + +%token PP_CONFIG PP_CONFIG1 PP_CONFIG2 +%token PP_VALUE PP_ERROR +%type <num> PP_VALUE +%type <bits> bit_term +%type <bits> bits + +%union +{ + unsigned long num; + DECLARE_BITMAP(bits, PERF_PMU_FORMAT_BITS); +} + +%% + +format: +format format_term +| +format_term + +format_term: +PP_CONFIG ':' bits +{ + ABORT_ON(perf_pmu__new_format(format, name, + PERF_PMU_FORMAT_VALUE_CONFIG, + $3)); +} +| +PP_CONFIG1 ':' bits +{ + ABORT_ON(perf_pmu__new_format(format, name, + PERF_PMU_FORMAT_VALUE_CONFIG1, + $3)); +} +| +PP_CONFIG2 ':' bits +{ + ABORT_ON(perf_pmu__new_format(format, name, + PERF_PMU_FORMAT_VALUE_CONFIG2, + $3)); +} + +bits: +bits ',' bit_term +{ + bitmap_or($$, $1, $3, 64); +} +| +bit_term +{ + memcpy($$, $1, sizeof($1)); +} + +bit_term: +PP_VALUE '-' PP_VALUE +{ + perf_pmu__set_format($$, $1, $3); +} +| +PP_VALUE +{ + perf_pmu__set_format($$, $1, 0); +} + +%% + +void perf_pmu_error(struct list_head *list __used, + char *name __used, + char const *msg __used) +{ +} diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index e33554a562b36..8a8ee64e72d14 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -34,7 +34,6 @@ #include "util.h" #include "event.h" -#include "string.h" #include "strlist.h" #include "debug.h" #include "cache.h" @@ -273,10 +272,10 @@ static int add_module_to_probe_trace_events(struct probe_trace_event *tevs, /* Try to find perf_probe_event with debuginfo */ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, struct probe_trace_event **tevs, - int max_tevs, const char *module) + int max_tevs, const char *target) { bool need_dwarf = perf_probe_event_need_dwarf(pev); - struct debuginfo *dinfo = open_debuginfo(module); + struct debuginfo *dinfo = open_debuginfo(target); int ntevs, ret = 0; if (!dinfo) { @@ -295,9 +294,9 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, if (ntevs > 0) { /* Succeeded to find trace events */ pr_debug("find %d probe_trace_events.\n", ntevs); - if (module) + if (target) ret = add_module_to_probe_trace_events(*tevs, ntevs, - module); + target); return ret < 0 ? ret : ntevs; } @@ -1729,7 +1728,7 @@ static int __add_probe_trace_events(struct perf_probe_event *pev, } ret = 0; - printf("Add new event%s\n", (ntevs > 1) ? "s:" : ":"); + printf("Added new event%s\n", (ntevs > 1) ? "s:" : ":"); for (i = 0; i < ntevs; i++) { tev = &tevs[i]; if (pev->event) @@ -1784,7 +1783,7 @@ static int __add_probe_trace_events(struct perf_probe_event *pev, if (ret >= 0) { /* Show how to use the event. */ - printf("\nYou can now use it on all perf tools, such as:\n\n"); + printf("\nYou can now use it in all perf tools, such as:\n\n"); printf("\tperf record -e %s:%s -aR sleep 1\n\n", tev->group, tev->event); } @@ -1796,14 +1795,14 @@ static int __add_probe_trace_events(struct perf_probe_event *pev, static int convert_to_probe_trace_events(struct perf_probe_event *pev, struct probe_trace_event **tevs, - int max_tevs, const char *module) + int max_tevs, const char *target) { struct symbol *sym; int ret = 0, i; struct probe_trace_event *tev; /* Convert perf_probe_event with debuginfo */ - ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, module); + ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, target); if (ret != 0) return ret; /* Found in debuginfo or got an error */ @@ -1819,8 +1818,8 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev, goto error; } - if (module) { - tev->point.module = strdup(module); + if (target) { + tev->point.module = strdup(target); if (tev->point.module == NULL) { ret = -ENOMEM; goto error; @@ -1890,7 +1889,7 @@ struct __event_package { }; int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, - int max_tevs, const char *module, bool force_add) + int max_tevs, const char *target, bool force_add) { int i, j, ret; struct __event_package *pkgs; @@ -1913,7 +1912,7 @@ int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, ret = convert_to_probe_trace_events(pkgs[i].pev, &pkgs[i].tevs, max_tevs, - module); + target); if (ret < 0) goto end; pkgs[i].ntevs = ret; @@ -1965,7 +1964,7 @@ static int __del_trace_probe_event(int fd, struct str_node *ent) goto error; } - printf("Remove event: %s\n", ent->s); + printf("Removed event: %s\n", ent->s); return 0; error: pr_warning("Failed to delete event: %s\n", strerror(-ret)); @@ -2069,7 +2068,7 @@ static int filter_available_functions(struct map *map __unused, return 1; } -int show_available_funcs(const char *module, struct strfilter *_filter) +int show_available_funcs(const char *target, struct strfilter *_filter) { struct map *map; int ret; @@ -2080,9 +2079,9 @@ int show_available_funcs(const char *module, struct strfilter *_filter) if (ret < 0) return ret; - map = kernel_get_module_map(module); + map = kernel_get_module_map(target); if (!map) { - pr_err("Failed to find %s map.\n", (module) ? : "kernel"); + pr_err("Failed to find %s map.\n", (target) ? : "kernel"); return -EINVAL; } available_func_filter = _filter; diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 74bd2e63c4b4a..d448984ed789c 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -30,7 +30,6 @@ #include <stdlib.h> #include <string.h> #include <stdarg.h> -#include <ctype.h> #include <dwarf-regs.h> #include <linux/bitops.h> @@ -973,10 +972,12 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) struct dwarf_callback_param *param = data; struct probe_finder *pf = param->data; struct perf_probe_point *pp = &pf->pev->point; + Dwarf_Attribute attr; /* Check tag and diename */ if (dwarf_tag(sp_die) != DW_TAG_subprogram || - !die_compare_name(sp_die, pp->function)) + !die_compare_name(sp_die, pp->function) || + dwarf_attr(sp_die, DW_AT_declaration, &attr)) return DWARF_CB_OK; /* Check declared file */ diff --git a/tools/perf/util/python-ext-sources b/tools/perf/util/python-ext-sources new file mode 100644 index 0000000000000..2884e67ee625d --- /dev/null +++ b/tools/perf/util/python-ext-sources @@ -0,0 +1,19 @@ +# +# List of files needed by perf python extention +# +# Each source file must be placed on its own line so that it can be +# processed by Makefile and util/setup.py accordingly. +# + +util/python.c +util/ctype.c +util/evlist.c +util/evsel.c +util/cpumap.c +util/thread_map.c +util/util.c +util/xyarray.c +util/cgroup.c +util/debugfs.c +util/strlist.c +../../lib/rbtree.c diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index 9dd47a4f2596d..e03b58a484243 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -425,14 +425,14 @@ struct pyrf_thread_map { static int pyrf_thread_map__init(struct pyrf_thread_map *pthreads, PyObject *args, PyObject *kwargs) { - static char *kwlist[] = { "pid", "tid", NULL }; - int pid = -1, tid = -1; + static char *kwlist[] = { "pid", "tid", "uid", NULL }; + int pid = -1, tid = -1, uid = UINT_MAX; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", - kwlist, &pid, &tid)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iii", + kwlist, &pid, &tid, &uid)) return -1; - pthreads->threads = thread_map__new(pid, tid); + pthreads->threads = thread_map__new(pid, tid, uid); if (pthreads->threads == NULL) return -1; return 0; diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c index 0b2a487831724..acb9795286c4b 100644 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ b/tools/perf/util/scripting-engines/trace-event-python.c @@ -24,7 +24,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <ctype.h> #include <errno.h> #include "../../perf.h" @@ -38,7 +37,7 @@ PyMODINIT_FUNC initperf_trace_context(void); #define FTRACE_MAX_EVENT \ ((1 << (sizeof(unsigned short) * 8)) - 1) -struct event *events[FTRACE_MAX_EVENT]; +struct event_format *events[FTRACE_MAX_EVENT]; #define MAX_FIELDS 64 #define N_COMMON_FIELDS 7 @@ -137,7 +136,7 @@ static void define_field(enum print_arg_type field_type, Py_DECREF(t); } -static void define_event_symbols(struct event *event, +static void define_event_symbols(struct event_format *event, const char *ev_name, struct print_arg *args) { @@ -179,6 +178,10 @@ static void define_event_symbols(struct event *event, define_event_symbols(event, ev_name, args->op.right); break; default: + /* gcc warns for these? */ + case PRINT_BSTRING: + case PRINT_DYNAMIC_ARRAY: + case PRINT_FUNC: /* we should warn... */ return; } @@ -187,10 +190,10 @@ static void define_event_symbols(struct event *event, define_event_symbols(event, ev_name, args->next); } -static inline struct event *find_cache_event(int type) +static inline struct event_format *find_cache_event(int type) { static char ev_name[256]; - struct event *event; + struct event_format *event; if (events[type]) return events[type]; @@ -217,7 +220,7 @@ static void python_process_event(union perf_event *pevent __unused, struct format_field *field; unsigned long long val; unsigned long s, ns; - struct event *event; + struct event_format *event; unsigned n = 0; int type; int pid; @@ -437,7 +440,7 @@ out: static int python_generate_script(const char *outfile) { - struct event *event = NULL; + struct event_format *event = NULL; struct format_field *f; char fname[PATH_MAX]; int not_first, count; diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index b5ca2558c7bb3..4dcc8f3190cf2 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -24,7 +24,7 @@ static int perf_session__open(struct perf_session *self, bool force) self->fd = STDIN_FILENO; if (perf_session__read_header(self, self->fd) < 0) - pr_err("incompatible file format"); + pr_err("incompatible file format (rerun with -v to learn more)"); return 0; } @@ -56,7 +56,7 @@ static int perf_session__open(struct perf_session *self, bool force) } if (perf_session__read_header(self, self->fd) < 0) { - pr_err("incompatible file format"); + pr_err("incompatible file format (rerun with -v to learn more)"); goto out_close; } @@ -140,6 +140,7 @@ struct perf_session *perf_session__new(const char *filename, int mode, INIT_LIST_HEAD(&self->ordered_samples.sample_cache); INIT_LIST_HEAD(&self->ordered_samples.to_free); machine__init(&self->host_machine, "", HOST_KERNEL_ID); + hists__init(&self->hists); if (mode == O_RDONLY) { if (perf_session__open(self, force) < 0) @@ -229,6 +230,64 @@ static bool symbol__match_parent_regex(struct symbol *sym) return 0; } +static const u8 cpumodes[] = { + PERF_RECORD_MISC_USER, + PERF_RECORD_MISC_KERNEL, + PERF_RECORD_MISC_GUEST_USER, + PERF_RECORD_MISC_GUEST_KERNEL +}; +#define NCPUMODES (sizeof(cpumodes)/sizeof(u8)) + +static void ip__resolve_ams(struct machine *self, struct thread *thread, + struct addr_map_symbol *ams, + u64 ip) +{ + struct addr_location al; + size_t i; + u8 m; + + memset(&al, 0, sizeof(al)); + + for (i = 0; i < NCPUMODES; i++) { + m = cpumodes[i]; + /* + * We cannot use the header.misc hint to determine whether a + * branch stack address is user, kernel, guest, hypervisor. + * Branches may straddle the kernel/user/hypervisor boundaries. + * Thus, we have to try consecutively until we find a match + * or else, the symbol is unknown + */ + thread__find_addr_location(thread, self, m, MAP__FUNCTION, + ip, &al, NULL); + if (al.sym) + goto found; + } +found: + ams->addr = ip; + ams->al_addr = al.addr; + ams->sym = al.sym; + ams->map = al.map; +} + +struct branch_info *machine__resolve_bstack(struct machine *self, + struct thread *thr, + struct branch_stack *bs) +{ + struct branch_info *bi; + unsigned int i; + + bi = calloc(bs->nr, sizeof(struct branch_info)); + if (!bi) + return NULL; + + for (i = 0; i < bs->nr; i++) { + ip__resolve_ams(self, thr, &bi[i].to, bs->entries[i].to); + ip__resolve_ams(self, thr, &bi[i].from, bs->entries[i].from); + bi[i].flags = bs->entries[i].flags; + } + return bi; +} + int machine__resolve_callchain(struct machine *self, struct perf_evsel *evsel, struct thread *thread, struct ip_callchain *chain, @@ -697,6 +756,18 @@ static void callchain__printf(struct perf_sample *sample) i, sample->callchain->ips[i]); } +static void branch_stack__printf(struct perf_sample *sample) +{ + uint64_t i; + + printf("... branch stack: nr:%" PRIu64 "\n", sample->branch_stack->nr); + + for (i = 0; i < sample->branch_stack->nr; i++) + printf("..... %2"PRIu64": %016" PRIx64 " -> %016" PRIx64 "\n", + i, sample->branch_stack->entries[i].from, + sample->branch_stack->entries[i].to); +} + static void perf_session__print_tstamp(struct perf_session *session, union perf_event *event, struct perf_sample *sample) @@ -744,6 +815,9 @@ static void dump_sample(struct perf_session *session, union perf_event *event, if (session->sample_type & PERF_SAMPLE_CALLCHAIN) callchain__printf(sample); + + if (session->sample_type & PERF_SAMPLE_BRANCH_STACK) + branch_stack__printf(sample); } static struct machine * @@ -752,8 +826,16 @@ static struct machine * { const u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL && perf_guest) - return perf_session__find_machine(session, event->ip.pid); + if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL && perf_guest) { + u32 pid; + + if (event->header.type == PERF_RECORD_MMAP) + pid = event->mmap.pid; + else + pid = event->ip.pid; + + return perf_session__find_machine(session, pid); + } return perf_session__find_host_machine(session); } @@ -794,7 +876,11 @@ static int perf_session_deliver_event(struct perf_session *session, dump_sample(session, event, sample); if (evsel == NULL) { ++session->hists.stats.nr_unknown_id; - return -1; + return 0; + } + if (machine == NULL) { + ++session->hists.stats.nr_unprocessable_samples; + return 0; } return tool->sample(tool, event, sample, evsel, machine); case PERF_RECORD_MMAP: @@ -964,6 +1050,12 @@ static void perf_session__warn_about_errors(const struct perf_session *session, session->hists.stats.nr_invalid_chains, session->hists.stats.nr_events[PERF_RECORD_SAMPLE]); } + + if (session->hists.stats.nr_unprocessable_samples != 0) { + ui__warning("%u unprocessable samples recorded.\n" + "Do you have a KVM guest running and not using 'perf kvm'?\n", + session->hists.stats.nr_unprocessable_samples); + } } #define session_done() (*(volatile int *)(&session_done)) @@ -1016,16 +1108,10 @@ more: } if ((skip = perf_session__process_event(self, &event, tool, head)) < 0) { - dump_printf("%#" PRIx64 " [%#x]: skipping unknown header type: %d\n", - head, event.header.size, event.header.type); - /* - * assume we lost track of the stream, check alignment, and - * increment a single u64 in the hope to catch on again 'soon'. - */ - if (unlikely(head & 7)) - head &= ~7ULL; - - size = 8; + pr_err("%#" PRIx64 " [%#x]: failed to process type: %d\n", + head, event.header.size, event.header.type); + err = -EINVAL; + goto out_err; } head += size; @@ -1134,17 +1220,11 @@ more: if (size == 0 || perf_session__process_event(session, event, tool, file_pos) < 0) { - dump_printf("%#" PRIx64 " [%#x]: skipping unknown header type: %d\n", - file_offset + head, event->header.size, - event->header.type); - /* - * assume we lost track of the stream, check alignment, and - * increment a single u64 in the hope to catch on again 'soon'. - */ - if (unlikely(head & 7)) - head &= ~7ULL; - - size = 8; + pr_err("%#" PRIx64 " [%#x]: failed to process type: %d\n", + file_offset + head, event->header.size, + event->header.type); + err = -EINVAL; + goto out_err; } head += size; @@ -1293,10 +1373,9 @@ struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session, void perf_event__print_ip(union perf_event *event, struct perf_sample *sample, struct machine *machine, struct perf_evsel *evsel, - int print_sym, int print_dso) + int print_sym, int print_dso, int print_symoffset) { struct addr_location al; - const char *symname, *dsoname; struct callchain_cursor *cursor = &evsel->hists.callchain_cursor; struct callchain_cursor_node *node; @@ -1324,20 +1403,13 @@ void perf_event__print_ip(union perf_event *event, struct perf_sample *sample, printf("\t%16" PRIx64, node->ip); if (print_sym) { - if (node->sym && node->sym->name) - symname = node->sym->name; - else - symname = ""; - - printf(" %s", symname); + printf(" "); + symbol__fprintf_symname(node->sym, stdout); } if (print_dso) { - if (node->map && node->map->dso && node->map->dso->name) - dsoname = node->map->dso->name; - else - dsoname = ""; - - printf(" (%s)", dsoname); + printf(" ("); + map__fprintf_dsoname(al.map, stdout); + printf(")"); } printf("\n"); @@ -1347,21 +1419,18 @@ void perf_event__print_ip(union perf_event *event, struct perf_sample *sample, } else { printf("%16" PRIx64, sample->ip); if (print_sym) { - if (al.sym && al.sym->name) - symname = al.sym->name; + printf(" "); + if (print_symoffset) + symbol__fprintf_symname_offs(al.sym, &al, + stdout); else - symname = ""; - - printf(" %s", symname); + symbol__fprintf_symname(al.sym, stdout); } if (print_dso) { - if (al.map && al.map->dso && al.map->dso->name) - dsoname = al.map->dso->name; - else - dsoname = ""; - - printf(" (%s)", dsoname); + printf(" ("); + map__fprintf_dsoname(al.map, stdout); + printf(")"); } } } diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index 37bc38381fb67..7a5434c005653 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h @@ -73,6 +73,10 @@ int perf_session__resolve_callchain(struct perf_session *self, struct perf_evsel struct ip_callchain *chain, struct symbol **parent); +struct branch_info *machine__resolve_bstack(struct machine *self, + struct thread *thread, + struct branch_stack *bs); + bool perf_session__has_traces(struct perf_session *self, const char *msg); void mem_bswap_64(void *src, int byte_size); @@ -147,7 +151,7 @@ struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session, void perf_event__print_ip(union perf_event *event, struct perf_sample *sample, struct machine *machine, struct perf_evsel *evsel, - int print_sym, int print_dso); + int print_sym, int print_dso, int print_symoffset); int perf_session__cpu_bitmap(struct perf_session *session, const char *cpu_list, unsigned long *cpu_bitmap); diff --git a/tools/perf/util/setup.py b/tools/perf/util/setup.py index 36d4c56195750..d0f9f29cf181a 100644 --- a/tools/perf/util/setup.py +++ b/tools/perf/util/setup.py @@ -24,11 +24,11 @@ cflags += getenv('CFLAGS', '').split() build_lib = getenv('PYTHON_EXTBUILD_LIB') build_tmp = getenv('PYTHON_EXTBUILD_TMP') +ext_sources = [f.strip() for f in file('util/python-ext-sources') + if len(f.strip()) > 0 and f[0] != '#'] + perf = Extension('perf', - sources = ['util/python.c', 'util/ctype.c', 'util/evlist.c', - 'util/evsel.c', 'util/cpumap.c', 'util/thread_map.c', - 'util/util.c', 'util/xyarray.c', 'util/cgroup.c', - 'util/debugfs.c'], + sources = ext_sources, include_dirs = ['util/include'], extra_compile_args = cflags, ) diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 16da30d8d765a..a27237430c5f1 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -8,6 +8,7 @@ const char default_sort_order[] = "comm,dso,symbol"; const char *sort_order = default_sort_order; int sort__need_collapse = 0; int sort__has_parent = 0; +int sort__branch_mode = -1; /* -1 = means not set */ enum sort_type sort__first_dimension; @@ -33,6 +34,9 @@ static int repsep_snprintf(char *bf, size_t size, const char *fmt, ...) } } va_end(ap); + + if (n >= (int)size) + return size - 1; return n; } @@ -94,6 +98,26 @@ static int hist_entry__comm_snprintf(struct hist_entry *self, char *bf, return repsep_snprintf(bf, size, "%*s", width, self->thread->comm); } +static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r) +{ + struct dso *dso_l = map_l ? map_l->dso : NULL; + struct dso *dso_r = map_r ? map_r->dso : NULL; + const char *dso_name_l, *dso_name_r; + + if (!dso_l || !dso_r) + return cmp_null(dso_l, dso_r); + + if (verbose) { + dso_name_l = dso_l->long_name; + dso_name_r = dso_r->long_name; + } else { + dso_name_l = dso_l->short_name; + dso_name_r = dso_r->short_name; + } + + return strcmp(dso_name_l, dso_name_r); +} + struct sort_entry sort_comm = { .se_header = "Command", .se_cmp = sort__comm_cmp, @@ -107,36 +131,74 @@ struct sort_entry sort_comm = { static int64_t sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) { - struct dso *dso_l = left->ms.map ? left->ms.map->dso : NULL; - struct dso *dso_r = right->ms.map ? right->ms.map->dso : NULL; - const char *dso_name_l, *dso_name_r; + return _sort__dso_cmp(left->ms.map, right->ms.map); +} - if (!dso_l || !dso_r) - return cmp_null(dso_l, dso_r); - if (verbose) { - dso_name_l = dso_l->long_name; - dso_name_r = dso_r->long_name; - } else { - dso_name_l = dso_l->short_name; - dso_name_r = dso_r->short_name; +static int64_t _sort__sym_cmp(struct symbol *sym_l, struct symbol *sym_r, + u64 ip_l, u64 ip_r) +{ + if (!sym_l || !sym_r) + return cmp_null(sym_l, sym_r); + + if (sym_l == sym_r) + return 0; + + if (sym_l) + ip_l = sym_l->start; + if (sym_r) + ip_r = sym_r->start; + + return (int64_t)(ip_r - ip_l); +} + +static int _hist_entry__dso_snprintf(struct map *map, char *bf, + size_t size, unsigned int width) +{ + if (map && map->dso) { + const char *dso_name = !verbose ? map->dso->short_name : + map->dso->long_name; + return repsep_snprintf(bf, size, "%-*s", width, dso_name); } - return strcmp(dso_name_l, dso_name_r); + return repsep_snprintf(bf, size, "%-*s", width, "[unknown]"); } static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf, size_t size, unsigned int width) { - if (self->ms.map && self->ms.map->dso) { - const char *dso_name = !verbose ? self->ms.map->dso->short_name : - self->ms.map->dso->long_name; - return repsep_snprintf(bf, size, "%-*s", width, dso_name); + return _hist_entry__dso_snprintf(self->ms.map, bf, size, width); +} + +static int _hist_entry__sym_snprintf(struct map *map, struct symbol *sym, + u64 ip, char level, char *bf, size_t size, + unsigned int width __used) +{ + size_t ret = 0; + + if (verbose) { + char o = map ? dso__symtab_origin(map->dso) : '!'; + ret += repsep_snprintf(bf, size, "%-#*llx %c ", + BITS_PER_LONG / 4, ip, o); } - return repsep_snprintf(bf, size, "%-*s", width, "[unknown]"); + ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", level); + if (sym) + ret += repsep_snprintf(bf + ret, size - ret, "%-*s", + width - ret, + sym->name); + else { + size_t len = BITS_PER_LONG / 4; + ret += repsep_snprintf(bf + ret, size - ret, "%-#.*llx", + len, ip); + ret += repsep_snprintf(bf + ret, size - ret, "%-*s", + width - ret, ""); + } + + return ret; } + struct sort_entry sort_dso = { .se_header = "Shared Object", .se_cmp = sort__dso_cmp, @@ -144,8 +206,14 @@ struct sort_entry sort_dso = { .se_width_idx = HISTC_DSO, }; -/* --sort symbol */ +static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, + size_t size, unsigned int width __used) +{ + return _hist_entry__sym_snprintf(self->ms.map, self->ms.sym, self->ip, + self->level, bf, size, width); +} +/* --sort symbol */ static int64_t sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) { @@ -163,31 +231,7 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) ip_l = left->ms.sym->start; ip_r = right->ms.sym->start; - return (int64_t)(ip_r - ip_l); -} - -static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, - size_t size, unsigned int width __used) -{ - size_t ret = 0; - - if (verbose) { - char o = self->ms.map ? dso__symtab_origin(self->ms.map->dso) : '!'; - ret += repsep_snprintf(bf, size, "%-#*llx %c ", - BITS_PER_LONG / 4, self->ip, o); - } - - if (!sort_dso.elide) - ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level); - - if (self->ms.sym) - ret += repsep_snprintf(bf + ret, size - ret, "%s", - self->ms.sym->name); - else - ret += repsep_snprintf(bf + ret, size - ret, "%-#*llx", - BITS_PER_LONG / 4, self->ip); - - return ret; + return _sort__sym_cmp(left->ms.sym, right->ms.sym, ip_l, ip_r); } struct sort_entry sort_sym = { @@ -246,19 +290,155 @@ struct sort_entry sort_cpu = { .se_width_idx = HISTC_CPU, }; +static int64_t +sort__dso_from_cmp(struct hist_entry *left, struct hist_entry *right) +{ + return _sort__dso_cmp(left->branch_info->from.map, + right->branch_info->from.map); +} + +static int hist_entry__dso_from_snprintf(struct hist_entry *self, char *bf, + size_t size, unsigned int width) +{ + return _hist_entry__dso_snprintf(self->branch_info->from.map, + bf, size, width); +} + +struct sort_entry sort_dso_from = { + .se_header = "Source Shared Object", + .se_cmp = sort__dso_from_cmp, + .se_snprintf = hist_entry__dso_from_snprintf, + .se_width_idx = HISTC_DSO_FROM, +}; + +static int64_t +sort__dso_to_cmp(struct hist_entry *left, struct hist_entry *right) +{ + return _sort__dso_cmp(left->branch_info->to.map, + right->branch_info->to.map); +} + +static int hist_entry__dso_to_snprintf(struct hist_entry *self, char *bf, + size_t size, unsigned int width) +{ + return _hist_entry__dso_snprintf(self->branch_info->to.map, + bf, size, width); +} + +static int64_t +sort__sym_from_cmp(struct hist_entry *left, struct hist_entry *right) +{ + struct addr_map_symbol *from_l = &left->branch_info->from; + struct addr_map_symbol *from_r = &right->branch_info->from; + + if (!from_l->sym && !from_r->sym) + return right->level - left->level; + + return _sort__sym_cmp(from_l->sym, from_r->sym, from_l->addr, + from_r->addr); +} + +static int64_t +sort__sym_to_cmp(struct hist_entry *left, struct hist_entry *right) +{ + struct addr_map_symbol *to_l = &left->branch_info->to; + struct addr_map_symbol *to_r = &right->branch_info->to; + + if (!to_l->sym && !to_r->sym) + return right->level - left->level; + + return _sort__sym_cmp(to_l->sym, to_r->sym, to_l->addr, to_r->addr); +} + +static int hist_entry__sym_from_snprintf(struct hist_entry *self, char *bf, + size_t size, unsigned int width __used) +{ + struct addr_map_symbol *from = &self->branch_info->from; + return _hist_entry__sym_snprintf(from->map, from->sym, from->addr, + self->level, bf, size, width); + +} + +static int hist_entry__sym_to_snprintf(struct hist_entry *self, char *bf, + size_t size, unsigned int width __used) +{ + struct addr_map_symbol *to = &self->branch_info->to; + return _hist_entry__sym_snprintf(to->map, to->sym, to->addr, + self->level, bf, size, width); + +} + +struct sort_entry sort_dso_to = { + .se_header = "Target Shared Object", + .se_cmp = sort__dso_to_cmp, + .se_snprintf = hist_entry__dso_to_snprintf, + .se_width_idx = HISTC_DSO_TO, +}; + +struct sort_entry sort_sym_from = { + .se_header = "Source Symbol", + .se_cmp = sort__sym_from_cmp, + .se_snprintf = hist_entry__sym_from_snprintf, + .se_width_idx = HISTC_SYMBOL_FROM, +}; + +struct sort_entry sort_sym_to = { + .se_header = "Target Symbol", + .se_cmp = sort__sym_to_cmp, + .se_snprintf = hist_entry__sym_to_snprintf, + .se_width_idx = HISTC_SYMBOL_TO, +}; + +static int64_t +sort__mispredict_cmp(struct hist_entry *left, struct hist_entry *right) +{ + const unsigned char mp = left->branch_info->flags.mispred != + right->branch_info->flags.mispred; + const unsigned char p = left->branch_info->flags.predicted != + right->branch_info->flags.predicted; + + return mp || p; +} + +static int hist_entry__mispredict_snprintf(struct hist_entry *self, char *bf, + size_t size, unsigned int width){ + static const char *out = "N/A"; + + if (self->branch_info->flags.predicted) + out = "N"; + else if (self->branch_info->flags.mispred) + out = "Y"; + + return repsep_snprintf(bf, size, "%-*s", width, out); +} + +struct sort_entry sort_mispredict = { + .se_header = "Branch Mispredicted", + .se_cmp = sort__mispredict_cmp, + .se_snprintf = hist_entry__mispredict_snprintf, + .se_width_idx = HISTC_MISPREDICT, +}; + struct sort_dimension { const char *name; struct sort_entry *entry; int taken; }; +#define DIM(d, n, func) [d] = { .name = n, .entry = &(func) } + static struct sort_dimension sort_dimensions[] = { - { .name = "pid", .entry = &sort_thread, }, - { .name = "comm", .entry = &sort_comm, }, - { .name = "dso", .entry = &sort_dso, }, - { .name = "symbol", .entry = &sort_sym, }, - { .name = "parent", .entry = &sort_parent, }, - { .name = "cpu", .entry = &sort_cpu, }, + DIM(SORT_PID, "pid", sort_thread), + DIM(SORT_COMM, "comm", sort_comm), + DIM(SORT_DSO, "dso", sort_dso), + DIM(SORT_DSO_FROM, "dso_from", sort_dso_from), + DIM(SORT_DSO_TO, "dso_to", sort_dso_to), + DIM(SORT_SYM, "symbol", sort_sym), + DIM(SORT_SYM_TO, "symbol_from", sort_sym_from), + DIM(SORT_SYM_FROM, "symbol_to", sort_sym_to), + DIM(SORT_PARENT, "parent", sort_parent), + DIM(SORT_CPU, "cpu", sort_cpu), + DIM(SORT_MISPREDICT, "mispredict", sort_mispredict), }; int sort_dimension__add(const char *tok) @@ -270,7 +450,6 @@ int sort_dimension__add(const char *tok) if (strncasecmp(tok, sd->name, strlen(tok))) continue; - if (sd->entry == &sort_parent) { int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED); if (ret) { @@ -302,6 +481,16 @@ int sort_dimension__add(const char *tok) sort__first_dimension = SORT_PARENT; else if (!strcmp(sd->name, "cpu")) sort__first_dimension = SORT_CPU; + else if (!strcmp(sd->name, "symbol_from")) + sort__first_dimension = SORT_SYM_FROM; + else if (!strcmp(sd->name, "symbol_to")) + sort__first_dimension = SORT_SYM_TO; + else if (!strcmp(sd->name, "dso_from")) + sort__first_dimension = SORT_DSO_FROM; + else if (!strcmp(sd->name, "dso_to")) + sort__first_dimension = SORT_DSO_TO; + else if (!strcmp(sd->name, "mispredict")) + sort__first_dimension = SORT_MISPREDICT; } list_add_tail(&sd->entry->list, &hist_entry__sort_list); @@ -309,7 +498,6 @@ int sort_dimension__add(const char *tok) return 0; } - return -ESRCH; } diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 3f67ae3957521..472aa5a63a58f 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -31,11 +31,16 @@ extern const char *parent_pattern; extern const char default_sort_order[]; extern int sort__need_collapse; extern int sort__has_parent; +extern int sort__branch_mode; extern char *field_sep; 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 struct sort_entry sort_dso_from; +extern struct sort_entry sort_dso_to; +extern struct sort_entry sort_sym_from; +extern struct sort_entry sort_sym_to; extern enum sort_type sort__first_dimension; /** @@ -72,6 +77,7 @@ struct hist_entry { struct hist_entry *pair; struct rb_root sorted_chain; }; + struct branch_info *branch_info; struct callchain_root callchain[0]; }; @@ -82,6 +88,11 @@ enum sort_type { SORT_SYM, SORT_PARENT, SORT_CPU, + SORT_DSO_FROM, + SORT_DSO_TO, + SORT_SYM_FROM, + SORT_SYM_TO, + SORT_MISPREDICT, }; /* diff --git a/tools/perf/util/strbuf.c b/tools/perf/util/strbuf.c index 92e068517c1aa..2eeb51baf077f 100644 --- a/tools/perf/util/strbuf.c +++ b/tools/perf/util/strbuf.c @@ -1,4 +1,5 @@ #include "cache.h" +#include <linux/kernel.h> int prefixcmp(const char *str, const char *prefix) { @@ -89,14 +90,14 @@ void strbuf_addf(struct strbuf *sb, const char *fmt, ...) if (!strbuf_avail(sb)) strbuf_grow(sb, 64); va_start(ap, fmt); - len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); + len = vscnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); va_end(ap); if (len < 0) - die("your vsnprintf is broken"); + die("your vscnprintf is broken"); if (len > strbuf_avail(sb)) { strbuf_grow(sb, len); va_start(ap, fmt); - len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); + len = vscnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); va_end(ap); if (len > strbuf_avail(sb)) { die("this should not happen, your snprintf is broken"); diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 0975438c3e728..ab9867b2b433c 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -1,7 +1,5 @@ -#include <ctype.h> #include <dirent.h> #include <errno.h> -#include <libgen.h> #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -12,6 +10,7 @@ #include <unistd.h> #include <inttypes.h> #include "build-id.h" +#include "util.h" #include "debug.h" #include "symbol.h" #include "strlist.h" @@ -51,6 +50,8 @@ struct symbol_conf symbol_conf = { int dso__name_len(const struct dso *dso) { + if (!dso) + return strlen("[unknown]"); if (verbose) return dso->long_name_len; @@ -263,6 +264,28 @@ static size_t symbol__fprintf(struct symbol *sym, FILE *fp) sym->name); } +size_t symbol__fprintf_symname_offs(const struct symbol *sym, + const struct addr_location *al, FILE *fp) +{ + unsigned long offset; + size_t length; + + if (sym && sym->name) { + length = fprintf(fp, "%s", sym->name); + if (al) { + offset = al->addr - sym->start; + length += fprintf(fp, "+0x%lx", offset); + } + return length; + } else + return fprintf(fp, "[unknown]"); +} + +size_t symbol__fprintf_symname(const struct symbol *sym, FILE *fp) +{ + return symbol__fprintf_symname_offs(sym, NULL, fp); +} + void dso__set_long_name(struct dso *dso, char *name) { if (name == NULL) @@ -954,8 +977,9 @@ static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, * And always look at the original dso, not at debuginfo packages, that * have the PLT data stripped out (shdr_rel_plt.sh_type == SHT_NOBITS). */ -static int dso__synthesize_plt_symbols(struct dso *dso, struct map *map, - symbol_filter_t filter) +static int +dso__synthesize_plt_symbols(struct dso *dso, char *name, struct map *map, + symbol_filter_t filter) { uint32_t nr_rel_entries, idx; GElf_Sym sym; @@ -970,10 +994,7 @@ static int dso__synthesize_plt_symbols(struct dso *dso, struct map *map, char sympltname[1024]; Elf *elf; int nr = 0, symidx, fd, err = 0; - char name[PATH_MAX]; - snprintf(name, sizeof(name), "%s%s", - symbol_conf.symfs, dso->long_name); fd = open(name, O_RDONLY); if (fd < 0) goto out; @@ -1680,8 +1701,9 @@ restart: continue; if (ret > 0) { - int nr_plt = dso__synthesize_plt_symbols(dso, map, - filter); + int nr_plt; + + nr_plt = dso__synthesize_plt_symbols(dso, name, map, filter); if (nr_plt > 0) ret += nr_plt; break; diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 123c2e14353e5..1f003884f1abb 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -5,6 +5,7 @@ #include <stdbool.h> #include <stdint.h> #include "map.h" +#include "../perf.h" #include <linux/list.h> #include <linux/rbtree.h> #include <stdio.h> @@ -64,12 +65,18 @@ struct symbol { void symbol__delete(struct symbol *sym); +static inline size_t symbol__size(const struct symbol *sym) +{ + return sym->end - sym->start + 1; +} + struct strlist; struct symbol_conf { unsigned short priv_size; unsigned short nr_events; bool try_vmlinux_path, + show_kernel_path, use_modules, sort_by_name, show_nr_samples, @@ -95,7 +102,11 @@ struct symbol_conf { *col_width_list_str; struct strlist *dso_list, *comm_list, - *sym_list; + *sym_list, + *dso_from_list, + *dso_to_list, + *sym_from_list, + *sym_to_list; const char *symfs; }; @@ -119,6 +130,19 @@ struct map_symbol { bool has_children; }; +struct addr_map_symbol { + struct map *map; + struct symbol *sym; + u64 addr; + u64 al_addr; +}; + +struct branch_info { + struct addr_map_symbol from; + struct addr_map_symbol to; + struct branch_flags flags; +}; + struct addr_location { struct thread *thread; struct map *map; @@ -241,6 +265,9 @@ void machines__destroy_guest_kernel_maps(struct rb_root *machines); int symbol__init(void); void symbol__exit(void); +size_t symbol__fprintf_symname_offs(const struct symbol *sym, + const struct addr_location *al, FILE *fp); +size_t symbol__fprintf_symname(const struct symbol *sym, FILE *fp); bool symbol_type__is_a(char symbol_type, enum map_type map_type); size_t machine__fprintf_vmlinux_path(struct machine *machine, FILE *fp); diff --git a/tools/perf/util/sysfs.c b/tools/perf/util/sysfs.c new file mode 100644 index 0000000000000..48c6902e749f7 --- /dev/null +++ b/tools/perf/util/sysfs.c @@ -0,0 +1,60 @@ + +#include "util.h" +#include "sysfs.h" + +static const char * const sysfs_known_mountpoints[] = { + "/sys", + 0, +}; + +static int sysfs_found; +char sysfs_mountpoint[PATH_MAX]; + +static int sysfs_valid_mountpoint(const char *sysfs) +{ + struct statfs st_fs; + + if (statfs(sysfs, &st_fs) < 0) + return -ENOENT; + else if (st_fs.f_type != (long) SYSFS_MAGIC) + return -ENOENT; + + return 0; +} + +const char *sysfs_find_mountpoint(void) +{ + const char * const *ptr; + char type[100]; + FILE *fp; + + if (sysfs_found) + return (const char *) sysfs_mountpoint; + + ptr = sysfs_known_mountpoints; + while (*ptr) { + if (sysfs_valid_mountpoint(*ptr) == 0) { + sysfs_found = 1; + strcpy(sysfs_mountpoint, *ptr); + return sysfs_mountpoint; + } + ptr++; + } + + /* give up and parse /proc/mounts */ + fp = fopen("/proc/mounts", "r"); + if (fp == NULL) + return NULL; + + while (!sysfs_found && + fscanf(fp, "%*s %" STR(PATH_MAX) "s %99s %*s %*d %*d\n", + sysfs_mountpoint, type) == 2) { + + if (strcmp(type, "sysfs") == 0) + sysfs_found = 1; + } + + fclose(fp); + + return sysfs_found ? sysfs_mountpoint : NULL; +} diff --git a/tools/perf/util/sysfs.h b/tools/perf/util/sysfs.h new file mode 100644 index 0000000000000..a813b72039385 --- /dev/null +++ b/tools/perf/util/sysfs.h @@ -0,0 +1,6 @@ +#ifndef __SYSFS_H__ +#define __SYSFS_H__ + +const char *sysfs_find_mountpoint(void); + +#endif /* __DEBUGFS_H__ */ diff --git a/tools/perf/util/target.c b/tools/perf/util/target.c new file mode 100644 index 0000000000000..1064d5b148ad9 --- /dev/null +++ b/tools/perf/util/target.c @@ -0,0 +1,142 @@ +/* + * Helper functions for handling target threads/cpus + * + * Copyright (C) 2012, LG Electronics, Namhyung Kim <namhyung.kim@lge.com> + * + * Released under the GPL v2. + */ + +#include "target.h" +#include "debug.h" + +#include <pwd.h> +#include <string.h> + + +enum perf_target_errno perf_target__validate(struct perf_target *target) +{ + enum perf_target_errno ret = PERF_ERRNO_TARGET__SUCCESS; + + if (target->pid) + target->tid = target->pid; + + /* CPU and PID are mutually exclusive */ + if (target->tid && target->cpu_list) { + target->cpu_list = NULL; + if (ret == PERF_ERRNO_TARGET__SUCCESS) + ret = PERF_ERRNO_TARGET__PID_OVERRIDE_CPU; + } + + /* UID and PID are mutually exclusive */ + if (target->tid && target->uid_str) { + target->uid_str = NULL; + if (ret == PERF_ERRNO_TARGET__SUCCESS) + ret = PERF_ERRNO_TARGET__PID_OVERRIDE_UID; + } + + /* UID and CPU are mutually exclusive */ + if (target->uid_str && target->cpu_list) { + target->cpu_list = NULL; + if (ret == PERF_ERRNO_TARGET__SUCCESS) + ret = PERF_ERRNO_TARGET__UID_OVERRIDE_CPU; + } + + /* PID and SYSTEM are mutually exclusive */ + if (target->tid && target->system_wide) { + target->system_wide = false; + if (ret == PERF_ERRNO_TARGET__SUCCESS) + ret = PERF_ERRNO_TARGET__PID_OVERRIDE_SYSTEM; + } + + /* UID and SYSTEM are mutually exclusive */ + if (target->uid_str && target->system_wide) { + target->system_wide = false; + if (ret == PERF_ERRNO_TARGET__SUCCESS) + ret = PERF_ERRNO_TARGET__UID_OVERRIDE_SYSTEM; + } + + return ret; +} + +enum perf_target_errno perf_target__parse_uid(struct perf_target *target) +{ + struct passwd pwd, *result; + char buf[1024]; + const char *str = target->uid_str; + + target->uid = UINT_MAX; + if (str == NULL) + return PERF_ERRNO_TARGET__SUCCESS; + + /* Try user name first */ + getpwnam_r(str, &pwd, buf, sizeof(buf), &result); + + if (result == NULL) { + /* + * The user name not found. Maybe it's a UID number. + */ + char *endptr; + int uid = strtol(str, &endptr, 10); + + if (*endptr != '\0') + return PERF_ERRNO_TARGET__INVALID_UID; + + getpwuid_r(uid, &pwd, buf, sizeof(buf), &result); + + if (result == NULL) + return PERF_ERRNO_TARGET__USER_NOT_FOUND; + } + + target->uid = result->pw_uid; + return PERF_ERRNO_TARGET__SUCCESS; +} + +/* + * This must have a same ordering as the enum perf_target_errno. + */ +static const char *perf_target__error_str[] = { + "PID/TID switch overriding CPU", + "PID/TID switch overriding UID", + "UID switch overriding CPU", + "PID/TID switch overriding SYSTEM", + "UID switch overriding SYSTEM", + "Invalid User: %s", + "Problems obtaining information for user %s", +}; + +int perf_target__strerror(struct perf_target *target, int errnum, + char *buf, size_t buflen) +{ + int idx; + const char *msg; + + if (errnum >= 0) { + strerror_r(errnum, buf, buflen); + return 0; + } + + if (errnum < __PERF_ERRNO_TARGET__START || + errnum >= __PERF_ERRNO_TARGET__END) + return -1; + + idx = errnum - __PERF_ERRNO_TARGET__START; + msg = perf_target__error_str[idx]; + + switch (errnum) { + case PERF_ERRNO_TARGET__PID_OVERRIDE_CPU + ... PERF_ERRNO_TARGET__UID_OVERRIDE_SYSTEM: + snprintf(buf, buflen, "%s", msg); + break; + + case PERF_ERRNO_TARGET__INVALID_UID: + case PERF_ERRNO_TARGET__USER_NOT_FOUND: + snprintf(buf, buflen, msg, target->uid_str); + break; + + default: + /* cannot reach here */ + break; + } + + return 0; +} diff --git a/tools/perf/util/target.h b/tools/perf/util/target.h new file mode 100644 index 0000000000000..a4be8575fda56 --- /dev/null +++ b/tools/perf/util/target.h @@ -0,0 +1,65 @@ +#ifndef _PERF_TARGET_H +#define _PERF_TARGET_H + +#include <stdbool.h> +#include <sys/types.h> + +struct perf_target { + const char *pid; + const char *tid; + const char *cpu_list; + const char *uid_str; + uid_t uid; + bool system_wide; + bool uses_mmap; +}; + +enum perf_target_errno { + PERF_ERRNO_TARGET__SUCCESS = 0, + + /* + * Choose an arbitrary negative big number not to clash with standard + * errno since SUS requires the errno has distinct positive values. + * See 'Issue 6' in the link below. + * + * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html + */ + __PERF_ERRNO_TARGET__START = -10000, + + + /* for perf_target__validate() */ + PERF_ERRNO_TARGET__PID_OVERRIDE_CPU = __PERF_ERRNO_TARGET__START, + PERF_ERRNO_TARGET__PID_OVERRIDE_UID, + PERF_ERRNO_TARGET__UID_OVERRIDE_CPU, + PERF_ERRNO_TARGET__PID_OVERRIDE_SYSTEM, + PERF_ERRNO_TARGET__UID_OVERRIDE_SYSTEM, + + /* for perf_target__parse_uid() */ + PERF_ERRNO_TARGET__INVALID_UID, + PERF_ERRNO_TARGET__USER_NOT_FOUND, + + __PERF_ERRNO_TARGET__END, +}; + +enum perf_target_errno perf_target__validate(struct perf_target *target); +enum perf_target_errno perf_target__parse_uid(struct perf_target *target); + +int perf_target__strerror(struct perf_target *target, int errnum, char *buf, + size_t buflen); + +static inline bool perf_target__has_task(struct perf_target *target) +{ + return target->tid || target->pid || target->uid_str; +} + +static inline bool perf_target__has_cpu(struct perf_target *target) +{ + return target->system_wide || target->cpu_list; +} + +static inline bool perf_target__none(struct perf_target *target) +{ + return !perf_target__has_task(target) && !perf_target__has_cpu(target); +} + +#endif /* _PERF_TARGET_H */ diff --git a/tools/perf/util/thread_map.c b/tools/perf/util/thread_map.c index a5df131b77c32..84d9bd7820049 100644 --- a/tools/perf/util/thread_map.c +++ b/tools/perf/util/thread_map.c @@ -1,6 +1,13 @@ #include <dirent.h> +#include <limits.h> +#include <stdbool.h> #include <stdlib.h> #include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include "strlist.h" +#include <string.h> #include "thread_map.h" /* Skip "." and ".." directories */ @@ -23,7 +30,7 @@ struct thread_map *thread_map__new_by_pid(pid_t pid) sprintf(name, "/proc/%d/task", pid); items = scandir(name, &namelist, filter, NULL); if (items <= 0) - return NULL; + return NULL; threads = malloc(sizeof(*threads) + sizeof(pid_t) * items); if (threads != NULL) { @@ -51,14 +58,240 @@ struct thread_map *thread_map__new_by_tid(pid_t tid) return threads; } -struct thread_map *thread_map__new(pid_t pid, pid_t tid) +struct thread_map *thread_map__new_by_uid(uid_t uid) +{ + DIR *proc; + int max_threads = 32, items, i; + char path[256]; + struct dirent dirent, *next, **namelist = NULL; + struct thread_map *threads = malloc(sizeof(*threads) + + max_threads * sizeof(pid_t)); + if (threads == NULL) + goto out; + + proc = opendir("/proc"); + if (proc == NULL) + goto out_free_threads; + + threads->nr = 0; + + while (!readdir_r(proc, &dirent, &next) && next) { + char *end; + bool grow = false; + struct stat st; + pid_t pid = strtol(dirent.d_name, &end, 10); + + if (*end) /* only interested in proper numerical dirents */ + continue; + + snprintf(path, sizeof(path), "/proc/%s", dirent.d_name); + + if (stat(path, &st) != 0) + continue; + + if (st.st_uid != uid) + continue; + + snprintf(path, sizeof(path), "/proc/%d/task", pid); + items = scandir(path, &namelist, filter, NULL); + if (items <= 0) + goto out_free_closedir; + + while (threads->nr + items >= max_threads) { + max_threads *= 2; + grow = true; + } + + if (grow) { + struct thread_map *tmp; + + tmp = realloc(threads, (sizeof(*threads) + + max_threads * sizeof(pid_t))); + if (tmp == NULL) + goto out_free_namelist; + + threads = tmp; + } + + for (i = 0; i < items; i++) + threads->map[threads->nr + i] = atoi(namelist[i]->d_name); + + for (i = 0; i < items; i++) + free(namelist[i]); + free(namelist); + + threads->nr += items; + } + +out_closedir: + closedir(proc); +out: + return threads; + +out_free_threads: + free(threads); + return NULL; + +out_free_namelist: + for (i = 0; i < items; i++) + free(namelist[i]); + free(namelist); + +out_free_closedir: + free(threads); + threads = NULL; + goto out_closedir; +} + +struct thread_map *thread_map__new(pid_t pid, pid_t tid, uid_t uid) { if (pid != -1) return thread_map__new_by_pid(pid); + + if (tid == -1 && uid != UINT_MAX) + return thread_map__new_by_uid(uid); + return thread_map__new_by_tid(tid); } +static struct thread_map *thread_map__new_by_pid_str(const char *pid_str) +{ + struct thread_map *threads = NULL, *nt; + char name[256]; + int items, total_tasks = 0; + struct dirent **namelist = NULL; + int i, j = 0; + pid_t pid, prev_pid = INT_MAX; + char *end_ptr; + struct str_node *pos; + struct strlist *slist = strlist__new(false, pid_str); + + if (!slist) + return NULL; + + strlist__for_each(pos, slist) { + pid = strtol(pos->s, &end_ptr, 10); + + if (pid == INT_MIN || pid == INT_MAX || + (*end_ptr != '\0' && *end_ptr != ',')) + goto out_free_threads; + + if (pid == prev_pid) + continue; + + sprintf(name, "/proc/%d/task", pid); + items = scandir(name, &namelist, filter, NULL); + if (items <= 0) + goto out_free_threads; + + total_tasks += items; + nt = realloc(threads, (sizeof(*threads) + + sizeof(pid_t) * total_tasks)); + if (nt == NULL) + goto out_free_threads; + + threads = nt; + + if (threads) { + for (i = 0; i < items; i++) + threads->map[j++] = atoi(namelist[i]->d_name); + threads->nr = total_tasks; + } + + for (i = 0; i < items; i++) + free(namelist[i]); + free(namelist); + + if (!threads) + break; + } + +out: + strlist__delete(slist); + return threads; + +out_free_threads: + free(threads); + threads = NULL; + goto out; +} + +static struct thread_map *thread_map__new_by_tid_str(const char *tid_str) +{ + struct thread_map *threads = NULL, *nt; + int ntasks = 0; + pid_t tid, prev_tid = INT_MAX; + char *end_ptr; + struct str_node *pos; + struct strlist *slist; + + /* perf-stat expects threads to be generated even if tid not given */ + if (!tid_str) { + threads = malloc(sizeof(*threads) + sizeof(pid_t)); + if (threads != NULL) { + threads->map[0] = -1; + threads->nr = 1; + } + return threads; + } + + slist = strlist__new(false, tid_str); + if (!slist) + return NULL; + + strlist__for_each(pos, slist) { + tid = strtol(pos->s, &end_ptr, 10); + + if (tid == INT_MIN || tid == INT_MAX || + (*end_ptr != '\0' && *end_ptr != ',')) + goto out_free_threads; + + if (tid == prev_tid) + continue; + + ntasks++; + nt = realloc(threads, sizeof(*threads) + sizeof(pid_t) * ntasks); + + if (nt == NULL) + goto out_free_threads; + + threads = nt; + threads->map[ntasks - 1] = tid; + threads->nr = ntasks; + } +out: + return threads; + +out_free_threads: + free(threads); + threads = NULL; + goto out; +} + +struct thread_map *thread_map__new_str(const char *pid, const char *tid, + uid_t uid) +{ + if (pid) + return thread_map__new_by_pid_str(pid); + + if (!tid && uid != UINT_MAX) + return thread_map__new_by_uid(uid); + + return thread_map__new_by_tid_str(tid); +} + void thread_map__delete(struct thread_map *threads) { free(threads); } + +size_t thread_map__fprintf(struct thread_map *threads, FILE *fp) +{ + int i; + size_t printed = fprintf(fp, "%d thread%s: ", + threads->nr, threads->nr > 1 ? "s" : ""); + for (i = 0; i < threads->nr; ++i) + printed += fprintf(fp, "%s%d", i ? ", " : "", threads->map[i]); + + return printed + fprintf(fp, "\n"); +} diff --git a/tools/perf/util/thread_map.h b/tools/perf/util/thread_map.h index 3cb907311409f..f718df8a3c59d 100644 --- a/tools/perf/util/thread_map.h +++ b/tools/perf/util/thread_map.h @@ -2,14 +2,23 @@ #define __PERF_THREAD_MAP_H #include <sys/types.h> +#include <stdio.h> struct thread_map { int nr; - int map[]; + pid_t map[]; }; struct thread_map *thread_map__new_by_pid(pid_t pid); struct thread_map *thread_map__new_by_tid(pid_t tid); -struct thread_map *thread_map__new(pid_t pid, pid_t tid); +struct thread_map *thread_map__new_by_uid(uid_t uid); +struct thread_map *thread_map__new(pid_t pid, pid_t tid, uid_t uid); + +struct thread_map *thread_map__new_str(const char *pid, + const char *tid, uid_t uid); + void thread_map__delete(struct thread_map *threads); + +size_t thread_map__fprintf(struct thread_map *threads, FILE *fp); + #endif /* __PERF_THREAD_MAP_H */ diff --git a/tools/perf/util/top.c b/tools/perf/util/top.c index 500471dffa4fd..abe0e8e950682 100644 --- a/tools/perf/util/top.c +++ b/tools/perf/util/top.c @@ -69,20 +69,24 @@ size_t perf_top__header_snprintf(struct perf_top *top, char *bf, size_t size) ret += SNPRINTF(bf + ret, size - ret, "], "); - if (top->target_pid != -1) - ret += SNPRINTF(bf + ret, size - ret, " (target_pid: %d", - top->target_pid); - else if (top->target_tid != -1) - ret += SNPRINTF(bf + ret, size - ret, " (target_tid: %d", - top->target_tid); + if (top->target.pid) + ret += SNPRINTF(bf + ret, size - ret, " (target_pid: %s", + top->target.pid); + else if (top->target.tid) + ret += SNPRINTF(bf + ret, size - ret, " (target_tid: %s", + top->target.tid); + else if (top->target.uid_str != NULL) + ret += SNPRINTF(bf + ret, size - ret, " (uid: %s", + top->target.uid_str); else ret += SNPRINTF(bf + ret, size - ret, " (all"); - if (top->cpu_list) + if (top->target.cpu_list) ret += SNPRINTF(bf + ret, size - ret, ", CPU%s: %s)", - top->evlist->cpus->nr > 1 ? "s" : "", top->cpu_list); + top->evlist->cpus->nr > 1 ? "s" : "", + top->target.cpu_list); else { - if (top->target_tid != -1) + if (top->target.tid) ret += SNPRINTF(bf + ret, size - ret, ")"); else ret += SNPRINTF(bf + ret, size - ret, ", %d CPU%s)", diff --git a/tools/perf/util/top.h b/tools/perf/util/top.h index a248f3c2c60d9..33347ca89ee40 100644 --- a/tools/perf/util/top.h +++ b/tools/perf/util/top.h @@ -13,6 +13,7 @@ struct perf_session; struct perf_top { struct perf_tool tool; struct perf_evlist *evlist; + struct perf_target target; /* * Symbols will be added here in perf_event__process_sample and will * get out after decayed. @@ -23,9 +24,7 @@ struct perf_top { u64 guest_us_samples, guest_kernel_samples; int print_entries, count_filter, delay_secs; int freq; - pid_t target_pid, target_tid; bool hide_kernel_symbols, hide_user_symbols, zero; - bool system_wide; bool use_tui, use_stdio; bool sort_has_symbols; bool dont_use_callchains; @@ -33,9 +32,9 @@ struct perf_top { bool vmlinux_warned; bool inherit; bool group; - bool sample_id_all_avail; + bool sample_id_all_missing; + bool exclude_guest_missing; bool dump_symtab; - const char *cpu_list; struct hist_entry *sym_filter_entry; struct perf_evsel *sym_evsel; struct perf_session *session; diff --git a/tools/perf/util/trace-event-info.c b/tools/perf/util/trace-event-info.c index fc22cf5c605f3..a8d81c35ef66c 100644 --- a/tools/perf/util/trace-event-info.c +++ b/tools/perf/util/trace-event-info.c @@ -68,7 +68,7 @@ struct events { }; -void *malloc_or_die(unsigned int size) +static void *malloc_or_die(unsigned int size) { void *data; @@ -448,6 +448,8 @@ static void tracing_data_header(void) else buf[0] = 0; + read_trace_init(buf[0], buf[0]); + write_or_die(buf, 1); /* save size of long */ diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c index 1a8d4dc4f386b..df2fddbf0cd2f 100644 --- a/tools/perf/util/trace-event-parse.c +++ b/tools/perf/util/trace-event-parse.c @@ -17,11 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * The parts for function graph printing was taken and modified from the - * Linux Kernel that were written by Frederic Weisbecker. */ - #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -32,2145 +28,294 @@ #include "util.h" #include "trace-event.h" -int header_page_ts_offset; -int header_page_ts_size; -int header_page_size_offset; int header_page_size_size; -int header_page_overwrite_offset; -int header_page_overwrite_size; +int header_page_ts_size; int header_page_data_offset; -int header_page_data_size; - -bool latency_format; - -static char *input_buf; -static unsigned long long input_buf_ptr; -static unsigned long long input_buf_siz; - -static int cpus; -static int long_size; -static int is_flag_field; -static int is_symbolic_field; - -static struct format_field * -find_any_field(struct event *event, const char *name); - -static void init_input_buf(char *buf, unsigned long long size) -{ - input_buf = buf; - input_buf_siz = size; - input_buf_ptr = 0; -} - -struct cmdline { - char *comm; - int pid; -}; - -static struct cmdline *cmdlines; -static int cmdline_count; - -static int cmdline_cmp(const void *a, const void *b) -{ - const struct cmdline *ca = a; - const struct cmdline *cb = b; - - if (ca->pid < cb->pid) - return -1; - if (ca->pid > cb->pid) - return 1; - - return 0; -} -void parse_cmdlines(char *file, int size __unused) -{ - struct cmdline_list { - struct cmdline_list *next; - char *comm; - int pid; - } *list = NULL, *item; - char *line; - char *next = NULL; - int i; +struct pevent *perf_pevent; +static struct pevent *pevent; - line = strtok_r(file, "\n", &next); - while (line) { - item = malloc_or_die(sizeof(*item)); - sscanf(line, "%d %as", &item->pid, - (float *)(void *)&item->comm); /* workaround gcc warning */ - item->next = list; - list = item; - line = strtok_r(NULL, "\n", &next); - cmdline_count++; - } - - cmdlines = malloc_or_die(sizeof(*cmdlines) * cmdline_count); - - i = 0; - while (list) { - cmdlines[i].pid = list->pid; - cmdlines[i].comm = list->comm; - i++; - item = list; - list = list->next; - free(item); - } - - qsort(cmdlines, cmdline_count, sizeof(*cmdlines), cmdline_cmp); -} - -static struct func_map { - unsigned long long addr; - char *func; - char *mod; -} *func_list; -static unsigned int func_count; - -static int func_cmp(const void *a, const void *b) -{ - const struct func_map *fa = a; - const struct func_map *fb = b; - - if (fa->addr < fb->addr) - return -1; - if (fa->addr > fb->addr) - return 1; - - return 0; -} - -void parse_proc_kallsyms(char *file, unsigned int size __unused) -{ - struct func_list { - struct func_list *next; - unsigned long long addr; - char *func; - char *mod; - } *list = NULL, *item; - char *line; - char *next = NULL; - char *addr_str; - char ch; - int ret __used; - int i; - - line = strtok_r(file, "\n", &next); - while (line) { - item = malloc_or_die(sizeof(*item)); - item->mod = NULL; - ret = sscanf(line, "%as %c %as\t[%as", - (float *)(void *)&addr_str, /* workaround gcc warning */ - &ch, - (float *)(void *)&item->func, - (float *)(void *)&item->mod); - item->addr = strtoull(addr_str, NULL, 16); - free(addr_str); - - /* truncate the extra ']' */ - if (item->mod) - item->mod[strlen(item->mod) - 1] = 0; - - - item->next = list; - list = item; - line = strtok_r(NULL, "\n", &next); - func_count++; - } - - func_list = malloc_or_die(sizeof(*func_list) * (func_count + 1)); - - i = 0; - while (list) { - func_list[i].func = list->func; - func_list[i].addr = list->addr; - func_list[i].mod = list->mod; - i++; - item = list; - list = list->next; - free(item); - } - - qsort(func_list, func_count, sizeof(*func_list), func_cmp); - - /* - * Add a special record at the end. - */ - func_list[func_count].func = NULL; - func_list[func_count].addr = 0; - func_list[func_count].mod = NULL; -} +bool latency_format; -/* - * We are searching for a record in between, not an exact - * match. - */ -static int func_bcmp(const void *a, const void *b) +int read_trace_init(int file_bigendian, int host_bigendian) { - const struct func_map *fa = a; - const struct func_map *fb = b; - - if ((fa->addr == fb->addr) || - - (fa->addr > fb->addr && - fa->addr < (fb+1)->addr)) + if (pevent) return 0; - if (fa->addr < fb->addr) - return -1; - - return 1; -} - -static struct func_map *find_func(unsigned long long addr) -{ - struct func_map *func; - struct func_map key; - - key.addr = addr; - - func = bsearch(&key, func_list, func_count, sizeof(*func_list), - func_bcmp); - - return func; -} - -void print_funcs(void) -{ - int i; - - for (i = 0; i < (int)func_count; i++) { - printf("%016llx %s", - func_list[i].addr, - func_list[i].func); - if (func_list[i].mod) - printf(" [%s]\n", func_list[i].mod); - else - printf("\n"); - } -} + perf_pevent = pevent_alloc(); + pevent = perf_pevent; -static struct printk_map { - unsigned long long addr; - char *printk; -} *printk_list; -static unsigned int printk_count; - -static int printk_cmp(const void *a, const void *b) -{ - const struct func_map *fa = a; - const struct func_map *fb = b; - - if (fa->addr < fb->addr) - return -1; - if (fa->addr > fb->addr) - return 1; + pevent_set_flag(pevent, PEVENT_NSEC_OUTPUT); + pevent_set_file_bigendian(pevent, file_bigendian); + pevent_set_host_bigendian(pevent, host_bigendian); return 0; } -static struct printk_map *find_printk(unsigned long long addr) +static int get_common_field(struct scripting_context *context, + int *offset, int *size, const char *type) { - struct printk_map *printk; - struct printk_map key; - - key.addr = addr; - - printk = bsearch(&key, printk_list, printk_count, sizeof(*printk_list), - printk_cmp); - - return printk; -} - -void parse_ftrace_printk(char *file, unsigned int size __unused) -{ - struct printk_list { - struct printk_list *next; - unsigned long long addr; - char *printk; - } *list = NULL, *item; - char *line; - char *next = NULL; - char *addr_str; - int i; - - line = strtok_r(file, "\n", &next); - while (line) { - addr_str = strsep(&line, ":"); - if (!line) { - warning("error parsing print strings"); - break; - } - item = malloc_or_die(sizeof(*item)); - item->addr = strtoull(addr_str, NULL, 16); - /* fmt still has a space, skip it */ - item->printk = strdup(line+1); - item->next = list; - list = item; - line = strtok_r(NULL, "\n", &next); - printk_count++; - } - - printk_list = malloc_or_die(sizeof(*printk_list) * printk_count + 1); - - i = 0; - while (list) { - printk_list[i].printk = list->printk; - printk_list[i].addr = list->addr; - i++; - item = list; - list = list->next; - free(item); - } - - qsort(printk_list, printk_count, sizeof(*printk_list), printk_cmp); -} - -void print_printk(void) -{ - int i; - - for (i = 0; i < (int)printk_count; i++) { - printf("%016llx %s\n", - printk_list[i].addr, - printk_list[i].printk); - } -} - -static struct event *alloc_event(void) -{ - struct event *event; - - event = malloc_or_die(sizeof(*event)); - memset(event, 0, sizeof(*event)); - - return event; -} - -enum event_type { - EVENT_ERROR, - EVENT_NONE, - EVENT_SPACE, - EVENT_NEWLINE, - EVENT_OP, - EVENT_DELIM, - EVENT_ITEM, - EVENT_DQUOTE, - EVENT_SQUOTE, -}; - -static struct event *event_list; - -static void add_event(struct event *event) -{ - event->next = event_list; - event_list = event; -} - -static int event_item_type(enum event_type type) -{ - switch (type) { - case EVENT_ITEM ... EVENT_SQUOTE: - return 1; - case EVENT_ERROR ... EVENT_DELIM: - default: - return 0; - } -} - -static void free_arg(struct print_arg *arg) -{ - if (!arg) - return; - - switch (arg->type) { - case PRINT_ATOM: - if (arg->atom.atom) - free(arg->atom.atom); - break; - case PRINT_NULL: - case PRINT_FIELD ... PRINT_OP: - default: - /* todo */ - break; - } - - free(arg); -} - -static enum event_type get_type(int ch) -{ - if (ch == '\n') - return EVENT_NEWLINE; - if (isspace(ch)) - return EVENT_SPACE; - if (isalnum(ch) || ch == '_') - return EVENT_ITEM; - if (ch == '\'') - return EVENT_SQUOTE; - if (ch == '"') - return EVENT_DQUOTE; - if (!isprint(ch)) - return EVENT_NONE; - if (ch == '(' || ch == ')' || ch == ',') - return EVENT_DELIM; - - return EVENT_OP; -} - -static int __read_char(void) -{ - if (input_buf_ptr >= input_buf_siz) - return -1; - - return input_buf[input_buf_ptr++]; -} - -static int __peek_char(void) -{ - if (input_buf_ptr >= input_buf_siz) - return -1; - - return input_buf[input_buf_ptr]; -} - -static enum event_type __read_token(char **tok) -{ - char buf[BUFSIZ]; - int ch, last_ch, quote_ch, next_ch; - int i = 0; - int tok_size = 0; - enum event_type type; - - *tok = NULL; - - - ch = __read_char(); - if (ch < 0) - return EVENT_NONE; - - type = get_type(ch); - if (type == EVENT_NONE) - return type; - - buf[i++] = ch; - - switch (type) { - case EVENT_NEWLINE: - case EVENT_DELIM: - *tok = malloc_or_die(2); - (*tok)[0] = ch; - (*tok)[1] = 0; - return type; - - case EVENT_OP: - switch (ch) { - case '-': - next_ch = __peek_char(); - if (next_ch == '>') { - buf[i++] = __read_char(); - break; - } - /* fall through */ - case '+': - case '|': - case '&': - case '>': - case '<': - last_ch = ch; - ch = __peek_char(); - if (ch != last_ch) - goto test_equal; - buf[i++] = __read_char(); - switch (last_ch) { - case '>': - case '<': - goto test_equal; - default: - break; - } - break; - case '!': - case '=': - goto test_equal; - default: /* what should we do instead? */ - break; - } - buf[i] = 0; - *tok = strdup(buf); - return type; - - test_equal: - ch = __peek_char(); - if (ch == '=') - buf[i++] = __read_char(); - break; - - case EVENT_DQUOTE: - case EVENT_SQUOTE: - /* don't keep quotes */ - i--; - quote_ch = ch; - last_ch = 0; - do { - if (i == (BUFSIZ - 1)) { - buf[i] = 0; - if (*tok) { - *tok = realloc(*tok, tok_size + BUFSIZ); - if (!*tok) - return EVENT_NONE; - strcat(*tok, buf); - } else - *tok = strdup(buf); - - if (!*tok) - return EVENT_NONE; - tok_size += BUFSIZ; - i = 0; - } - last_ch = ch; - ch = __read_char(); - buf[i++] = ch; - /* the '\' '\' will cancel itself */ - if (ch == '\\' && last_ch == '\\') - last_ch = 0; - } while (ch != quote_ch || last_ch == '\\'); - /* remove the last quote */ - i--; - goto out; - - case EVENT_ERROR ... EVENT_SPACE: - case EVENT_ITEM: - default: - break; - } - - while (get_type(__peek_char()) == type) { - if (i == (BUFSIZ - 1)) { - buf[i] = 0; - if (*tok) { - *tok = realloc(*tok, tok_size + BUFSIZ); - if (!*tok) - return EVENT_NONE; - strcat(*tok, buf); - } else - *tok = strdup(buf); - - if (!*tok) - return EVENT_NONE; - tok_size += BUFSIZ; - i = 0; - } - ch = __read_char(); - buf[i++] = ch; - } - - out: - buf[i] = 0; - if (*tok) { - *tok = realloc(*tok, tok_size + i); - if (!*tok) - return EVENT_NONE; - strcat(*tok, buf); - } else - *tok = strdup(buf); - if (!*tok) - return EVENT_NONE; - - return type; -} - -static void free_token(char *tok) -{ - if (tok) - free(tok); -} - -static enum event_type read_token(char **tok) -{ - enum event_type type; - - for (;;) { - type = __read_token(tok); - if (type != EVENT_SPACE) - return type; - - free_token(*tok); - } - - /* not reached */ - return EVENT_NONE; -} - -/* no newline */ -static enum event_type read_token_item(char **tok) -{ - enum event_type type; - - for (;;) { - type = __read_token(tok); - if (type != EVENT_SPACE && type != EVENT_NEWLINE) - return type; - - free_token(*tok); - } - - /* not reached */ - return EVENT_NONE; -} - -static int test_type(enum event_type type, enum event_type expect) -{ - if (type != expect) { - warning("Error: expected type %d but read %d", - expect, type); - return -1; - } - return 0; -} + struct event_format *event; + struct format_field *field; -static int __test_type_token(enum event_type type, char *token, - enum event_type expect, const char *expect_tok, - bool warn) -{ - if (type != expect) { - if (warn) - warning("Error: expected type %d but read %d", - expect, type); - return -1; - } + if (!*size) { + if (!pevent->events) + return 0; - if (strcmp(token, expect_tok) != 0) { - if (warn) - warning("Error: expected '%s' but read '%s'", - expect_tok, token); - return -1; + event = pevent->events[0]; + field = pevent_find_common_field(event, type); + if (!field) + return 0; + *offset = field->offset; + *size = field->size; } - return 0; -} -static int test_type_token(enum event_type type, char *token, - enum event_type expect, const char *expect_tok) -{ - return __test_type_token(type, token, expect, expect_tok, true); + return pevent_read_number(pevent, context->event_data + *offset, *size); } -static int __read_expect_type(enum event_type expect, char **tok, int newline_ok) -{ - enum event_type type; - - if (newline_ok) - type = read_token(tok); - else - type = read_token_item(tok); - return test_type(type, expect); -} - -static int read_expect_type(enum event_type expect, char **tok) -{ - return __read_expect_type(expect, tok, 1); -} - -static int __read_expected(enum event_type expect, const char *str, - int newline_ok, bool warn) +int common_lock_depth(struct scripting_context *context) { - enum event_type type; - char *token; + static int offset; + static int size; int ret; - if (newline_ok) - type = read_token(&token); - else - type = read_token_item(&token); - - ret = __test_type_token(type, token, expect, str, warn); - - free_token(token); + ret = get_common_field(context, &size, &offset, + "common_lock_depth"); + if (ret < 0) + return -1; return ret; } -static int read_expected(enum event_type expect, const char *str) -{ - return __read_expected(expect, str, 1, true); -} - -static int read_expected_item(enum event_type expect, const char *str) -{ - return __read_expected(expect, str, 0, true); -} - -static char *event_read_name(void) -{ - char *token; - - if (read_expected(EVENT_ITEM, "name") < 0) - return NULL; - - if (read_expected(EVENT_OP, ":") < 0) - return NULL; - - if (read_expect_type(EVENT_ITEM, &token) < 0) - goto fail; - - return token; - - fail: - free_token(token); - return NULL; -} - -static int event_read_id(void) +int common_flags(struct scripting_context *context) { - char *token; - int id; - - if (read_expected_item(EVENT_ITEM, "ID") < 0) - return -1; + static int offset; + static int size; + int ret; - if (read_expected(EVENT_OP, ":") < 0) + ret = get_common_field(context, &size, &offset, + "common_flags"); + if (ret < 0) return -1; - if (read_expect_type(EVENT_ITEM, &token) < 0) - goto fail; - - id = strtoul(token, NULL, 0); - free_token(token); - return id; - - fail: - free_token(token); - return -1; -} - -static int field_is_string(struct format_field *field) -{ - if ((field->flags & FIELD_IS_ARRAY) && - (!strstr(field->type, "char") || !strstr(field->type, "u8") || - !strstr(field->type, "s8"))) - return 1; - - return 0; -} - -static int field_is_dynamic(struct format_field *field) -{ - if (!strncmp(field->type, "__data_loc", 10)) - return 1; - - return 0; -} - -static int event_read_fields(struct event *event, struct format_field **fields) -{ - struct format_field *field = NULL; - enum event_type type; - char *token; - char *last_token; - int count = 0; - - do { - type = read_token(&token); - if (type == EVENT_NEWLINE) { - free_token(token); - return count; - } - - count++; - - if (test_type_token(type, token, EVENT_ITEM, "field")) - goto fail; - free_token(token); - - type = read_token(&token); - /* - * The ftrace fields may still use the "special" name. - * Just ignore it. - */ - if (event->flags & EVENT_FL_ISFTRACE && - type == EVENT_ITEM && strcmp(token, "special") == 0) { - free_token(token); - type = read_token(&token); - } - - if (test_type_token(type, token, EVENT_OP, ":") < 0) - return -1; - - if (read_expect_type(EVENT_ITEM, &token) < 0) - goto fail; - - last_token = token; - - field = malloc_or_die(sizeof(*field)); - memset(field, 0, sizeof(*field)); - - /* read the rest of the type */ - for (;;) { - type = read_token(&token); - if (type == EVENT_ITEM || - (type == EVENT_OP && strcmp(token, "*") == 0) || - /* - * Some of the ftrace fields are broken and have - * an illegal "." in them. - */ - (event->flags & EVENT_FL_ISFTRACE && - type == EVENT_OP && strcmp(token, ".") == 0)) { - - if (strcmp(token, "*") == 0) - field->flags |= FIELD_IS_POINTER; - - if (field->type) { - field->type = realloc(field->type, - strlen(field->type) + - strlen(last_token) + 2); - strcat(field->type, " "); - strcat(field->type, last_token); - } else - field->type = last_token; - last_token = token; - continue; - } - - break; - } - - if (!field->type) { - die("no type found"); - goto fail; - } - field->name = last_token; - - if (test_type(type, EVENT_OP)) - goto fail; - - if (strcmp(token, "[") == 0) { - enum event_type last_type = type; - char *brackets = token; - int len; - - field->flags |= FIELD_IS_ARRAY; - - type = read_token(&token); - while (strcmp(token, "]") != 0) { - if (last_type == EVENT_ITEM && - type == EVENT_ITEM) - len = 2; - else - len = 1; - last_type = type; - - brackets = realloc(brackets, - strlen(brackets) + - strlen(token) + len); - if (len == 2) - strcat(brackets, " "); - strcat(brackets, token); - free_token(token); - type = read_token(&token); - if (type == EVENT_NONE) { - die("failed to find token"); - goto fail; - } - } - - free_token(token); - - brackets = realloc(brackets, strlen(brackets) + 2); - strcat(brackets, "]"); - - /* add brackets to type */ - - type = read_token(&token); - /* - * If the next token is not an OP, then it is of - * the format: type [] item; - */ - if (type == EVENT_ITEM) { - field->type = realloc(field->type, - strlen(field->type) + - strlen(field->name) + - strlen(brackets) + 2); - strcat(field->type, " "); - strcat(field->type, field->name); - free_token(field->name); - strcat(field->type, brackets); - field->name = token; - type = read_token(&token); - } else { - field->type = realloc(field->type, - strlen(field->type) + - strlen(brackets) + 1); - strcat(field->type, brackets); - } - free(brackets); - } - - if (field_is_string(field)) { - field->flags |= FIELD_IS_STRING; - if (field_is_dynamic(field)) - field->flags |= FIELD_IS_DYNAMIC; - } - - if (test_type_token(type, token, EVENT_OP, ";")) - goto fail; - free_token(token); - - if (read_expected(EVENT_ITEM, "offset") < 0) - goto fail_expect; - - if (read_expected(EVENT_OP, ":") < 0) - goto fail_expect; - - if (read_expect_type(EVENT_ITEM, &token)) - goto fail; - field->offset = strtoul(token, NULL, 0); - free_token(token); - - if (read_expected(EVENT_OP, ";") < 0) - goto fail_expect; - - if (read_expected(EVENT_ITEM, "size") < 0) - goto fail_expect; - - if (read_expected(EVENT_OP, ":") < 0) - goto fail_expect; - - if (read_expect_type(EVENT_ITEM, &token)) - goto fail; - field->size = strtoul(token, NULL, 0); - free_token(token); - - if (read_expected(EVENT_OP, ";") < 0) - goto fail_expect; - - type = read_token(&token); - if (type != EVENT_NEWLINE) { - /* newer versions of the kernel have a "signed" type */ - if (test_type_token(type, token, EVENT_ITEM, "signed")) - goto fail; - - free_token(token); - - if (read_expected(EVENT_OP, ":") < 0) - goto fail_expect; - - if (read_expect_type(EVENT_ITEM, &token)) - goto fail; - - if (strtoul(token, NULL, 0)) - field->flags |= FIELD_IS_SIGNED; - - free_token(token); - if (read_expected(EVENT_OP, ";") < 0) - goto fail_expect; - - if (read_expect_type(EVENT_NEWLINE, &token)) - goto fail; - } - - free_token(token); - - *fields = field; - fields = &field->next; - - } while (1); - - return 0; - -fail: - free_token(token); -fail_expect: - if (field) - free(field); - return -1; + return ret; } -static int event_read_format(struct event *event) +int common_pc(struct scripting_context *context) { - char *token; + static int offset; + static int size; int ret; - if (read_expected_item(EVENT_ITEM, "format") < 0) - return -1; - - if (read_expected(EVENT_OP, ":") < 0) - return -1; - - if (read_expect_type(EVENT_NEWLINE, &token)) - goto fail; - free_token(token); - - ret = event_read_fields(event, &event->format.common_fields); - if (ret < 0) - return ret; - event->format.nr_common = ret; - - ret = event_read_fields(event, &event->format.fields); + ret = get_common_field(context, &size, &offset, + "common_preempt_count"); if (ret < 0) - return ret; - event->format.nr_fields = ret; - - return 0; - - fail: - free_token(token); - return -1; -} - -enum event_type -process_arg_token(struct event *event, struct print_arg *arg, - char **tok, enum event_type type); - -static enum event_type -process_arg(struct event *event, struct print_arg *arg, char **tok) -{ - enum event_type type; - char *token; - - type = read_token(&token); - *tok = token; + return -1; - return process_arg_token(event, arg, tok, type); + return ret; } -static enum event_type -process_cond(struct event *event, struct print_arg *top, char **tok) +unsigned long long +raw_field_value(struct event_format *event, const char *name, void *data) { - struct print_arg *arg, *left, *right; - enum event_type type; - char *token = NULL; - - arg = malloc_or_die(sizeof(*arg)); - memset(arg, 0, sizeof(*arg)); - - left = malloc_or_die(sizeof(*left)); - - right = malloc_or_die(sizeof(*right)); - - arg->type = PRINT_OP; - arg->op.left = left; - arg->op.right = right; - - *tok = NULL; - type = process_arg(event, left, &token); - if (test_type_token(type, token, EVENT_OP, ":")) - goto out_free; - - arg->op.op = token; - - type = process_arg(event, right, &token); + struct format_field *field; + unsigned long long val; - top->op.right = arg; + field = pevent_find_any_field(event, name); + if (!field) + return 0ULL; - *tok = token; - return type; + pevent_read_number_field(field, data, &val); -out_free: - free_token(*tok); - free(right); - free(left); - free_arg(arg); - return EVENT_ERROR; + return val; } -static enum event_type -process_array(struct event *event, struct print_arg *top, char **tok) +void *raw_field_ptr(struct event_format *event, const char *name, void *data) { - struct print_arg *arg; - enum event_type type; - char *token = NULL; - - arg = malloc_or_die(sizeof(*arg)); - memset(arg, 0, sizeof(*arg)); - - *tok = NULL; - type = process_arg(event, arg, &token); - if (test_type_token(type, token, EVENT_OP, "]")) - goto out_free; - - top->op.right = arg; - - free_token(token); - type = read_token_item(&token); - *tok = token; - - return type; + struct format_field *field; -out_free: - free_token(*tok); - free_arg(arg); - return EVENT_ERROR; -} + field = pevent_find_any_field(event, name); + if (!field) + return NULL; -static int get_op_prio(char *op) -{ - if (!op[1]) { - switch (op[0]) { - case '*': - case '/': - case '%': - return 6; - case '+': - case '-': - return 7; - /* '>>' and '<<' are 8 */ - case '<': - case '>': - return 9; - /* '==' and '!=' are 10 */ - case '&': - return 11; - case '^': - return 12; - case '|': - return 13; - case '?': - return 16; - default: - die("unknown op '%c'", op[0]); - return -1; - } - } else { - if (strcmp(op, "++") == 0 || - strcmp(op, "--") == 0) { - return 3; - } else if (strcmp(op, ">>") == 0 || - strcmp(op, "<<") == 0) { - return 8; - } else if (strcmp(op, ">=") == 0 || - strcmp(op, "<=") == 0) { - return 9; - } else if (strcmp(op, "==") == 0 || - strcmp(op, "!=") == 0) { - return 10; - } else if (strcmp(op, "&&") == 0) { - return 14; - } else if (strcmp(op, "||") == 0) { - return 15; - } else { - die("unknown op '%s'", op); - return -1; - } - } -} + if (field->flags & FIELD_IS_DYNAMIC) { + int offset; -static void set_op_prio(struct print_arg *arg) -{ + offset = *(int *)(data + field->offset); + offset &= 0xffff; - /* single ops are the greatest */ - if (!arg->op.left || arg->op.left->type == PRINT_NULL) { - arg->op.prio = 0; - return; + return data + offset; } - arg->op.prio = get_op_prio(arg->op.op); + return data + field->offset; } -static enum event_type -process_op(struct event *event, struct print_arg *arg, char **tok) +int trace_parse_common_type(void *data) { - struct print_arg *left, *right = NULL; - enum event_type type; - char *token; - - /* the op is passed in via tok */ - token = *tok; - - if (arg->type == PRINT_OP && !arg->op.left) { - /* handle single op */ - if (token[1]) { - die("bad op token %s", token); - return EVENT_ERROR; - } - switch (token[0]) { - case '!': - case '+': - case '-': - break; - default: - die("bad op token %s", token); - return EVENT_ERROR; - } - - /* make an empty left */ - left = malloc_or_die(sizeof(*left)); - left->type = PRINT_NULL; - arg->op.left = left; - - right = malloc_or_die(sizeof(*right)); - arg->op.right = right; - - type = process_arg(event, right, tok); - - } else if (strcmp(token, "?") == 0) { - - left = malloc_or_die(sizeof(*left)); - /* copy the top arg to the left */ - *left = *arg; - - arg->type = PRINT_OP; - arg->op.op = token; - arg->op.left = left; - arg->op.prio = 0; - - type = process_cond(event, arg, tok); - - } else if (strcmp(token, ">>") == 0 || - strcmp(token, "<<") == 0 || - strcmp(token, "&") == 0 || - strcmp(token, "|") == 0 || - strcmp(token, "&&") == 0 || - strcmp(token, "||") == 0 || - strcmp(token, "-") == 0 || - strcmp(token, "+") == 0 || - strcmp(token, "*") == 0 || - strcmp(token, "^") == 0 || - strcmp(token, "/") == 0 || - strcmp(token, "<") == 0 || - strcmp(token, ">") == 0 || - strcmp(token, "==") == 0 || - strcmp(token, "!=") == 0) { - - left = malloc_or_die(sizeof(*left)); - - /* copy the top arg to the left */ - *left = *arg; - - arg->type = PRINT_OP; - arg->op.op = token; - arg->op.left = left; - - set_op_prio(arg); - - right = malloc_or_die(sizeof(*right)); - - type = read_token_item(&token); - *tok = token; - - /* could just be a type pointer */ - if ((strcmp(arg->op.op, "*") == 0) && - type == EVENT_DELIM && (strcmp(token, ")") == 0)) { - if (left->type != PRINT_ATOM) - die("bad pointer type"); - left->atom.atom = realloc(left->atom.atom, - sizeof(left->atom.atom) + 3); - strcat(left->atom.atom, " *"); - *arg = *left; - free(arg); - - return type; - } - - type = process_arg_token(event, right, tok, type); - - arg->op.right = right; - - } else if (strcmp(token, "[") == 0) { - - left = malloc_or_die(sizeof(*left)); - *left = *arg; - - arg->type = PRINT_OP; - arg->op.op = token; - arg->op.left = left; + struct pevent_record record; - arg->op.prio = 0; - type = process_array(event, arg, tok); - - } else { - warning("unknown op '%s'", token); - event->flags |= EVENT_FL_FAILED; - /* the arg is now the left side */ - return EVENT_NONE; - } - - if (type == EVENT_OP) { - int prio; - - /* higher prios need to be closer to the root */ - prio = get_op_prio(*tok); - - if (prio > arg->op.prio) - return process_op(event, arg, tok); - - return process_op(event, right, tok); - } - - return type; + record.data = data; + return pevent_data_type(pevent, &record); } -static enum event_type -process_entry(struct event *event __unused, struct print_arg *arg, - char **tok) +int trace_parse_common_pid(void *data) { - enum event_type type; - char *field; - char *token; - - if (read_expected(EVENT_OP, "->") < 0) - return EVENT_ERROR; - - if (read_expect_type(EVENT_ITEM, &token) < 0) - goto fail; - field = token; - - arg->type = PRINT_FIELD; - arg->field.name = field; - - if (is_flag_field) { - arg->field.field = find_any_field(event, arg->field.name); - arg->field.field->flags |= FIELD_IS_FLAG; - is_flag_field = 0; - } else if (is_symbolic_field) { - arg->field.field = find_any_field(event, arg->field.name); - arg->field.field->flags |= FIELD_IS_SYMBOLIC; - is_symbolic_field = 0; - } - - type = read_token(&token); - *tok = token; + struct pevent_record record; - return type; - -fail: - free_token(token); - return EVENT_ERROR; + record.data = data; + return pevent_data_pid(pevent, &record); } -static char *arg_eval (struct print_arg *arg); - -static long long arg_num_eval(struct print_arg *arg) +unsigned long long read_size(void *ptr, int size) { - long long left, right; - long long val = 0; - - switch (arg->type) { - case PRINT_ATOM: - val = strtoll(arg->atom.atom, NULL, 0); - break; - case PRINT_TYPE: - val = arg_num_eval(arg->typecast.item); - break; - case PRINT_OP: - switch (arg->op.op[0]) { - case '|': - left = arg_num_eval(arg->op.left); - right = arg_num_eval(arg->op.right); - if (arg->op.op[1]) - val = left || right; - else - val = left | right; - break; - case '&': - left = arg_num_eval(arg->op.left); - right = arg_num_eval(arg->op.right); - if (arg->op.op[1]) - val = left && right; - else - val = left & right; - break; - case '<': - left = arg_num_eval(arg->op.left); - right = arg_num_eval(arg->op.right); - switch (arg->op.op[1]) { - case 0: - val = left < right; - break; - case '<': - val = left << right; - break; - case '=': - val = left <= right; - break; - default: - die("unknown op '%s'", arg->op.op); - } - break; - case '>': - left = arg_num_eval(arg->op.left); - right = arg_num_eval(arg->op.right); - switch (arg->op.op[1]) { - case 0: - val = left > right; - break; - case '>': - val = left >> right; - break; - case '=': - val = left >= right; - break; - default: - die("unknown op '%s'", arg->op.op); - } - break; - case '=': - left = arg_num_eval(arg->op.left); - right = arg_num_eval(arg->op.right); - - if (arg->op.op[1] != '=') - die("unknown op '%s'", arg->op.op); - - val = left == right; - break; - case '!': - left = arg_num_eval(arg->op.left); - right = arg_num_eval(arg->op.right); - - switch (arg->op.op[1]) { - case '=': - val = left != right; - break; - default: - die("unknown op '%s'", arg->op.op); - } - break; - default: - die("unknown op '%s'", arg->op.op); - } - break; - - case PRINT_NULL: - case PRINT_FIELD ... PRINT_SYMBOL: - case PRINT_STRING: - default: - die("invalid eval type %d", arg->type); - - } - return val; + return pevent_read_number(pevent, ptr, size); } -static char *arg_eval (struct print_arg *arg) +struct event_format *trace_find_event(int type) { - long long val; - static char buf[20]; - - switch (arg->type) { - case PRINT_ATOM: - return arg->atom.atom; - case PRINT_TYPE: - return arg_eval(arg->typecast.item); - case PRINT_OP: - val = arg_num_eval(arg); - sprintf(buf, "%lld", val); - return buf; - - case PRINT_NULL: - case PRINT_FIELD ... PRINT_SYMBOL: - case PRINT_STRING: - default: - die("invalid eval type %d", arg->type); - break; - } - - return NULL; + return pevent_find_event(pevent, type); } -static enum event_type -process_fields(struct event *event, struct print_flag_sym **list, char **tok) -{ - enum event_type type; - struct print_arg *arg = NULL; - struct print_flag_sym *field; - char *token = NULL; - char *value; - - do { - free_token(token); - type = read_token_item(&token); - if (test_type_token(type, token, EVENT_OP, "{")) - break; - - arg = malloc_or_die(sizeof(*arg)); - - free_token(token); - type = process_arg(event, arg, &token); - if (test_type_token(type, token, EVENT_DELIM, ",")) - goto out_free; - - field = malloc_or_die(sizeof(*field)); - memset(field, 0, sizeof(*field)); - - value = arg_eval(arg); - field->value = strdup(value); - - free_token(token); - type = process_arg(event, arg, &token); - if (test_type_token(type, token, EVENT_OP, "}")) - goto out_free; - - value = arg_eval(arg); - field->str = strdup(value); - free_arg(arg); - arg = NULL; - - *list = field; - list = &field->next; - - free_token(token); - type = read_token_item(&token); - } while (type == EVENT_DELIM && strcmp(token, ",") == 0); - - *tok = token; - return type; -out_free: - free_arg(arg); - free_token(token); - - return EVENT_ERROR; -} - -static enum event_type -process_flags(struct event *event, struct print_arg *arg, char **tok) +void print_trace_event(int cpu, void *data, int size) { - struct print_arg *field; - enum event_type type; - char *token; - - memset(arg, 0, sizeof(*arg)); - arg->type = PRINT_FLAGS; - - if (read_expected_item(EVENT_DELIM, "(") < 0) - return EVENT_ERROR; - - field = malloc_or_die(sizeof(*field)); - - type = process_arg(event, field, &token); - while (type == EVENT_OP) - type = process_op(event, field, &token); - if (test_type_token(type, token, EVENT_DELIM, ",")) - goto out_free; + struct event_format *event; + struct pevent_record record; + struct trace_seq s; + int type; - arg->flags.field = field; + type = trace_parse_common_type(data); - type = read_token_item(&token); - if (event_item_type(type)) { - arg->flags.delim = token; - type = read_token_item(&token); + event = trace_find_event(type); + if (!event) { + warning("ug! no event found for type %d", type); + return; } - if (test_type_token(type, token, EVENT_DELIM, ",")) - goto out_free; - - type = process_fields(event, &arg->flags.flags, &token); - if (test_type_token(type, token, EVENT_DELIM, ")")) - goto out_free; + memset(&record, 0, sizeof(record)); + record.cpu = cpu; + record.size = size; + record.data = data; - free_token(token); - type = read_token_item(tok); - return type; - -out_free: - free_token(token); - return EVENT_ERROR; + trace_seq_init(&s); + pevent_print_event(pevent, &s, &record); + trace_seq_do_printf(&s); + printf("\n"); } -static enum event_type -process_symbols(struct event *event, struct print_arg *arg, char **tok) +void print_event(int cpu, void *data, int size, unsigned long long nsecs, + char *comm) { - struct print_arg *field; - enum event_type type; - char *token; - - memset(arg, 0, sizeof(*arg)); - arg->type = PRINT_SYMBOL; - - if (read_expected_item(EVENT_DELIM, "(") < 0) - return EVENT_ERROR; - - field = malloc_or_die(sizeof(*field)); - - type = process_arg(event, field, &token); - if (test_type_token(type, token, EVENT_DELIM, ",")) - goto out_free; + struct pevent_record record; + struct trace_seq s; + int pid; - arg->symbol.field = field; + pevent->latency_format = latency_format; - type = process_fields(event, &arg->symbol.symbols, &token); - if (test_type_token(type, token, EVENT_DELIM, ")")) - goto out_free; + record.ts = nsecs; + record.cpu = cpu; + record.size = size; + record.data = data; + pid = pevent_data_pid(pevent, &record); - free_token(token); - type = read_token_item(tok); - return type; + if (!pevent_pid_is_registered(pevent, pid)) + pevent_register_comm(pevent, comm, pid); -out_free: - free_token(token); - return EVENT_ERROR; + trace_seq_init(&s); + pevent_print_event(pevent, &s, &record); + trace_seq_do_printf(&s); + printf("\n"); } -static enum event_type -process_paren(struct event *event, struct print_arg *arg, char **tok) +void parse_proc_kallsyms(char *file, unsigned int size __unused) { - struct print_arg *item_arg; - enum event_type type; - char *token; - - type = process_arg(event, arg, &token); - - if (type == EVENT_ERROR) - return EVENT_ERROR; - - if (type == EVENT_OP) - type = process_op(event, arg, &token); - - if (type == EVENT_ERROR) - return EVENT_ERROR; - - if (test_type_token(type, token, EVENT_DELIM, ")")) { - free_token(token); - return EVENT_ERROR; - } - - free_token(token); - type = read_token_item(&token); - - /* - * If the next token is an item or another open paren, then - * this was a typecast. - */ - if (event_item_type(type) || - (type == EVENT_DELIM && strcmp(token, "(") == 0)) { - - /* make this a typecast and contine */ + unsigned long long addr; + char *func; + char *line; + char *next = NULL; + char *addr_str; + char *mod; + char ch; - /* prevous must be an atom */ - if (arg->type != PRINT_ATOM) - die("previous needed to be PRINT_ATOM"); + line = strtok_r(file, "\n", &next); + while (line) { + mod = NULL; + sscanf(line, "%as %c %as\t[%as", + (float *)(void *)&addr_str, /* workaround gcc warning */ + &ch, (float *)(void *)&func, (float *)(void *)&mod); + addr = strtoull(addr_str, NULL, 16); + free(addr_str); - item_arg = malloc_or_die(sizeof(*item_arg)); + /* truncate the extra ']' */ + if (mod) + mod[strlen(mod) - 1] = 0; - arg->type = PRINT_TYPE; - arg->typecast.type = arg->atom.atom; - arg->typecast.item = item_arg; - type = process_arg_token(event, item_arg, &token, type); + pevent_register_function(pevent, func, addr, mod); + free(func); + free(mod); + line = strtok_r(NULL, "\n", &next); } - - *tok = token; - return type; } - -static enum event_type -process_str(struct event *event __unused, struct print_arg *arg, char **tok) +void parse_ftrace_printk(char *file, unsigned int size __unused) { - enum event_type type; - char *token; - - if (read_expected(EVENT_DELIM, "(") < 0) - return EVENT_ERROR; - - if (read_expect_type(EVENT_ITEM, &token) < 0) - goto fail; - - arg->type = PRINT_STRING; - arg->string.string = token; - arg->string.offset = -1; - - if (read_expected(EVENT_DELIM, ")") < 0) - return EVENT_ERROR; - - type = read_token(&token); - *tok = token; - - return type; -fail: - free_token(token); - return EVENT_ERROR; -} + unsigned long long addr; + char *printk; + char *line; + char *next = NULL; + char *addr_str; + char *fmt; -enum event_type -process_arg_token(struct event *event, struct print_arg *arg, - char **tok, enum event_type type) -{ - char *token; - char *atom; - - token = *tok; - - switch (type) { - case EVENT_ITEM: - if (strcmp(token, "REC") == 0) { - free_token(token); - type = process_entry(event, arg, &token); - } else if (strcmp(token, "__print_flags") == 0) { - free_token(token); - is_flag_field = 1; - type = process_flags(event, arg, &token); - } else if (strcmp(token, "__print_symbolic") == 0) { - free_token(token); - is_symbolic_field = 1; - type = process_symbols(event, arg, &token); - } else if (strcmp(token, "__get_str") == 0) { - free_token(token); - type = process_str(event, arg, &token); - } else { - atom = token; - /* test the next token */ - type = read_token_item(&token); - - /* atoms can be more than one token long */ - while (type == EVENT_ITEM) { - atom = realloc(atom, strlen(atom) + strlen(token) + 2); - strcat(atom, " "); - strcat(atom, token); - free_token(token); - type = read_token_item(&token); - } - - /* todo, test for function */ - - arg->type = PRINT_ATOM; - arg->atom.atom = atom; - } - break; - case EVENT_DQUOTE: - case EVENT_SQUOTE: - arg->type = PRINT_ATOM; - arg->atom.atom = token; - type = read_token_item(&token); - break; - case EVENT_DELIM: - if (strcmp(token, "(") == 0) { - free_token(token); - type = process_paren(event, arg, &token); + line = strtok_r(file, "\n", &next); + while (line) { + addr_str = strtok_r(line, ":", &fmt); + if (!addr_str) { + warning("printk format with empty entry"); break; } - case EVENT_OP: - /* handle single ops */ - arg->type = PRINT_OP; - arg->op.op = token; - arg->op.left = NULL; - type = process_op(event, arg, &token); - - break; - - case EVENT_ERROR ... EVENT_NEWLINE: - default: - die("unexpected type %d", type); - } - *tok = token; - - return type; -} - -static int event_read_print_args(struct event *event, struct print_arg **list) -{ - enum event_type type = EVENT_ERROR; - struct print_arg *arg; - char *token; - int args = 0; - - do { - if (type == EVENT_NEWLINE) { - free_token(token); - type = read_token_item(&token); - continue; - } - - arg = malloc_or_die(sizeof(*arg)); - memset(arg, 0, sizeof(*arg)); - - type = process_arg(event, arg, &token); - - if (type == EVENT_ERROR) { - free_arg(arg); - return -1; - } - - *list = arg; - args++; - - if (type == EVENT_OP) { - type = process_op(event, arg, &token); - list = &arg->next; - continue; - } - - if (type == EVENT_DELIM && strcmp(token, ",") == 0) { - free_token(token); - *list = arg; - list = &arg->next; - continue; - } - break; - } while (type != EVENT_NONE); - - if (type != EVENT_NONE) - free_token(token); - - return args; -} - -static int event_read_print(struct event *event) -{ - enum event_type type; - char *token; - int ret; - - if (read_expected_item(EVENT_ITEM, "print") < 0) - return -1; - - if (read_expected(EVENT_ITEM, "fmt") < 0) - return -1; - - if (read_expected(EVENT_OP, ":") < 0) - return -1; - - if (read_expect_type(EVENT_DQUOTE, &token) < 0) - goto fail; - - concat: - event->print_fmt.format = token; - event->print_fmt.args = NULL; - - /* ok to have no arg */ - type = read_token_item(&token); - - if (type == EVENT_NONE) - return 0; - - /* Handle concatination of print lines */ - if (type == EVENT_DQUOTE) { - char *cat; - - cat = malloc_or_die(strlen(event->print_fmt.format) + - strlen(token) + 1); - strcpy(cat, event->print_fmt.format); - strcat(cat, token); - free_token(token); - free_token(event->print_fmt.format); - event->print_fmt.format = NULL; - token = cat; - goto concat; - } - - if (test_type_token(type, token, EVENT_DELIM, ",")) - goto fail; - - free_token(token); - - ret = event_read_print_args(event, &event->print_fmt.args); - if (ret < 0) - return -1; - - return ret; - - fail: - free_token(token); - return -1; -} - -static struct format_field * -find_common_field(struct event *event, const char *name) -{ - struct format_field *format; - - for (format = event->format.common_fields; - format; format = format->next) { - if (strcmp(format->name, name) == 0) - break; - } - - return format; -} - -static struct format_field * -find_field(struct event *event, const char *name) -{ - struct format_field *format; - - for (format = event->format.fields; - format; format = format->next) { - if (strcmp(format->name, name) == 0) - break; + addr = strtoull(addr_str, NULL, 16); + /* fmt still has a space, skip it */ + printk = strdup(fmt+1); + line = strtok_r(NULL, "\n", &next); + pevent_register_print_string(pevent, printk, addr); } - - return format; -} - -static struct format_field * -find_any_field(struct event *event, const char *name) -{ - struct format_field *format; - - format = find_common_field(event, name); - if (format) - return format; - return find_field(event, name); } -unsigned long long read_size(void *ptr, int size) +int parse_ftrace_file(char *buf, unsigned long size) { - switch (size) { - case 1: - return *(unsigned char *)ptr; - case 2: - return data2host2(ptr); - case 4: - return data2host4(ptr); - case 8: - return data2host8(ptr); - default: - /* BUG! */ - return 0; - } + return pevent_parse_event(pevent, buf, size, "ftrace"); } -unsigned long long -raw_field_value(struct event *event, const char *name, void *data) +int parse_event_file(char *buf, unsigned long size, char *sys) { - struct format_field *field; - - field = find_any_field(event, name); - if (!field) - return 0ULL; - - return read_size(data + field->offset, field->size); + return pevent_parse_event(pevent, buf, size, sys); } -void *raw_field_ptr(struct event *event, const char *name, void *data) +struct event_format *trace_find_next_event(struct event_format *event) { - struct format_field *field; + static int idx; - field = find_any_field(event, name); - if (!field) + if (!pevent->events) return NULL; - if (field->flags & FIELD_IS_DYNAMIC) { - int offset; - - offset = *(int *)(data + field->offset); - offset &= 0xffff; - - return data + offset; - } - - return data + field->offset; -} - -static int get_common_info(const char *type, int *offset, int *size) -{ - struct event *event; - struct format_field *field; - - /* - * All events should have the same common elements. - * Pick any event to find where the type is; - */ - if (!event_list) - die("no event_list!"); - - event = event_list; - field = find_common_field(event, type); - if (!field) - die("field '%s' not found", type); - - *offset = field->offset; - *size = field->size; - - return 0; -} - -static int __parse_common(void *data, int *size, int *offset, - const char *name) -{ - int ret; - - if (!*size) { - ret = get_common_info(name, offset, size); - if (ret < 0) - return ret; + if (!event) { + idx = 0; + return pevent->events[0]; } - return read_size(data + *offset, *size); -} - -int trace_parse_common_type(void *data) -{ - static int type_offset; - static int type_size; - - return __parse_common(data, &type_size, &type_offset, - "common_type"); -} - -int trace_parse_common_pid(void *data) -{ - static int pid_offset; - static int pid_size; - return __parse_common(data, &pid_size, &pid_offset, - "common_pid"); -} - -int parse_common_pc(void *data) -{ - static int pc_offset; - static int pc_size; - - return __parse_common(data, &pc_size, &pc_offset, - "common_preempt_count"); -} - -int parse_common_flags(void *data) -{ - static int flags_offset; - static int flags_size; - - return __parse_common(data, &flags_size, &flags_offset, - "common_flags"); -} - -int parse_common_lock_depth(void *data) -{ - static int ld_offset; - static int ld_size; - int ret; - - ret = __parse_common(data, &ld_size, &ld_offset, - "common_lock_depth"); - if (ret < 0) - return -1; - - return ret; -} - -struct event *trace_find_event(int id) -{ - struct event *event; - - for (event = event_list; event; event = event->next) { - if (event->id == id) - break; + if (idx < pevent->nr_events && event == pevent->events[idx]) { + idx++; + if (idx == pevent->nr_events) + return NULL; + return pevent->events[idx]; } - return event; -} - -struct event *trace_find_next_event(struct event *event) -{ - if (!event) - return event_list; - - return event->next; -} -static unsigned long long eval_num_arg(void *data, int size, - struct event *event, struct print_arg *arg) -{ - unsigned long long val = 0; - unsigned long long left, right; - struct print_arg *larg; - - switch (arg->type) { - case PRINT_NULL: - /* ?? */ - return 0; - case PRINT_ATOM: - return strtoull(arg->atom.atom, NULL, 0); - case PRINT_FIELD: - if (!arg->field.field) { - arg->field.field = find_any_field(event, arg->field.name); - if (!arg->field.field) - die("field %s not found", arg->field.name); - } - /* must be a number */ - val = read_size(data + arg->field.field->offset, - arg->field.field->size); - break; - case PRINT_FLAGS: - case PRINT_SYMBOL: - break; - case PRINT_TYPE: - return eval_num_arg(data, size, event, arg->typecast.item); - case PRINT_STRING: - return 0; - break; - case PRINT_OP: - if (strcmp(arg->op.op, "[") == 0) { - /* - * Arrays are special, since we don't want - * to read the arg as is. - */ - if (arg->op.left->type != PRINT_FIELD) - goto default_op; /* oops, all bets off */ - larg = arg->op.left; - if (!larg->field.field) { - larg->field.field = - find_any_field(event, larg->field.name); - if (!larg->field.field) - die("field %s not found", larg->field.name); - } - right = eval_num_arg(data, size, event, arg->op.right); - val = read_size(data + larg->field.field->offset + - right * long_size, long_size); - break; - } - default_op: - left = eval_num_arg(data, size, event, arg->op.left); - right = eval_num_arg(data, size, event, arg->op.right); - switch (arg->op.op[0]) { - case '|': - if (arg->op.op[1]) - val = left || right; - else - val = left | right; - break; - case '&': - if (arg->op.op[1]) - val = left && right; - else - val = left & right; - break; - case '<': - switch (arg->op.op[1]) { - case 0: - val = left < right; - break; - case '<': - val = left << right; - break; - case '=': - val = left <= right; - break; - default: - die("unknown op '%s'", arg->op.op); - } - break; - case '>': - switch (arg->op.op[1]) { - case 0: - val = left > right; - break; - case '>': - val = left >> right; - break; - case '=': - val = left >= right; - break; - default: - die("unknown op '%s'", arg->op.op); - } - break; - case '=': - if (arg->op.op[1] != '=') - die("unknown op '%s'", arg->op.op); - val = left == right; - break; - case '-': - val = left - right; - break; - case '+': - val = left + right; - break; - default: - die("unknown op '%s'", arg->op.op); - } - break; - default: /* not sure what to do there */ - return 0; + for (idx = 1; idx < pevent->nr_events; idx++) { + if (event == pevent->events[idx - 1]) + return pevent->events[idx]; } - return val; + return NULL; } struct flag { @@ -2212,933 +357,3 @@ unsigned long long eval_flag(const char *flag) return 0; } - -static void print_str_arg(void *data, int size, - struct event *event, struct print_arg *arg) -{ - struct print_flag_sym *flag; - unsigned long long val, fval; - char *str; - int print; - - switch (arg->type) { - case PRINT_NULL: - /* ?? */ - return; - case PRINT_ATOM: - printf("%s", arg->atom.atom); - return; - case PRINT_FIELD: - if (!arg->field.field) { - arg->field.field = find_any_field(event, arg->field.name); - if (!arg->field.field) - die("field %s not found", arg->field.name); - } - str = malloc_or_die(arg->field.field->size + 1); - memcpy(str, data + arg->field.field->offset, - arg->field.field->size); - str[arg->field.field->size] = 0; - printf("%s", str); - free(str); - break; - case PRINT_FLAGS: - val = eval_num_arg(data, size, event, arg->flags.field); - print = 0; - for (flag = arg->flags.flags; flag; flag = flag->next) { - fval = eval_flag(flag->value); - if (!val && !fval) { - printf("%s", flag->str); - break; - } - if (fval && (val & fval) == fval) { - if (print && arg->flags.delim) - printf("%s", arg->flags.delim); - printf("%s", flag->str); - print = 1; - val &= ~fval; - } - } - break; - case PRINT_SYMBOL: - val = eval_num_arg(data, size, event, arg->symbol.field); - for (flag = arg->symbol.symbols; flag; flag = flag->next) { - fval = eval_flag(flag->value); - if (val == fval) { - printf("%s", flag->str); - break; - } - } - break; - - case PRINT_TYPE: - break; - case PRINT_STRING: { - int str_offset; - - if (arg->string.offset == -1) { - struct format_field *f; - - f = find_any_field(event, arg->string.string); - arg->string.offset = f->offset; - } - str_offset = *(int *)(data + arg->string.offset); - str_offset &= 0xffff; - printf("%s", ((char *)data) + str_offset); - break; - } - case PRINT_OP: - /* - * The only op for string should be ? : - */ - if (arg->op.op[0] != '?') - return; - val = eval_num_arg(data, size, event, arg->op.left); - if (val) - print_str_arg(data, size, event, arg->op.right->op.left); - else - print_str_arg(data, size, event, arg->op.right->op.right); - break; - default: - /* well... */ - break; - } -} - -static struct print_arg *make_bprint_args(char *fmt, void *data, int size, struct event *event) -{ - static struct format_field *field, *ip_field; - struct print_arg *args, *arg, **next; - unsigned long long ip, val; - char *ptr; - void *bptr; - - if (!field) { - field = find_field(event, "buf"); - if (!field) - die("can't find buffer field for binary printk"); - ip_field = find_field(event, "ip"); - if (!ip_field) - die("can't find ip field for binary printk"); - } - - ip = read_size(data + ip_field->offset, ip_field->size); - - /* - * The first arg is the IP pointer. - */ - args = malloc_or_die(sizeof(*args)); - arg = args; - arg->next = NULL; - next = &arg->next; - - arg->type = PRINT_ATOM; - arg->atom.atom = malloc_or_die(32); - sprintf(arg->atom.atom, "%lld", ip); - - /* skip the first "%pf : " */ - for (ptr = fmt + 6, bptr = data + field->offset; - bptr < data + size && *ptr; ptr++) { - int ls = 0; - - if (*ptr == '%') { - process_again: - ptr++; - switch (*ptr) { - case '%': - break; - case 'l': - ls++; - goto process_again; - case 'L': - ls = 2; - goto process_again; - case '0' ... '9': - goto process_again; - case 'p': - ls = 1; - /* fall through */ - case 'd': - case 'u': - case 'x': - case 'i': - /* the pointers are always 4 bytes aligned */ - bptr = (void *)(((unsigned long)bptr + 3) & - ~3); - switch (ls) { - case 0: - case 1: - ls = long_size; - break; - case 2: - ls = 8; - default: - break; - } - val = read_size(bptr, ls); - bptr += ls; - arg = malloc_or_die(sizeof(*arg)); - arg->next = NULL; - arg->type = PRINT_ATOM; - arg->atom.atom = malloc_or_die(32); - sprintf(arg->atom.atom, "%lld", val); - *next = arg; - next = &arg->next; - break; - case 's': - arg = malloc_or_die(sizeof(*arg)); - arg->next = NULL; - arg->type = PRINT_STRING; - arg->string.string = strdup(bptr); - bptr += strlen(bptr) + 1; - *next = arg; - next = &arg->next; - default: - break; - } - } - } - - return args; -} - -static void free_args(struct print_arg *args) -{ - struct print_arg *next; - - while (args) { - next = args->next; - - if (args->type == PRINT_ATOM) - free(args->atom.atom); - else - free(args->string.string); - free(args); - args = next; - } -} - -static char *get_bprint_format(void *data, int size __unused, struct event *event) -{ - unsigned long long addr; - static struct format_field *field; - struct printk_map *printk; - char *format; - char *p; - - if (!field) { - field = find_field(event, "fmt"); - if (!field) - die("can't find format field for binary printk"); - printf("field->offset = %d size=%d\n", field->offset, field->size); - } - - addr = read_size(data + field->offset, field->size); - - printk = find_printk(addr); - if (!printk) { - format = malloc_or_die(45); - sprintf(format, "%%pf : (NO FORMAT FOUND at %llx)\n", - addr); - return format; - } - - p = printk->printk; - /* Remove any quotes. */ - if (*p == '"') - p++; - format = malloc_or_die(strlen(p) + 10); - sprintf(format, "%s : %s", "%pf", p); - /* remove ending quotes and new line since we will add one too */ - p = format + strlen(format) - 1; - if (*p == '"') - *p = 0; - - p -= 2; - if (strcmp(p, "\\n") == 0) - *p = 0; - - return format; -} - -static void pretty_print(void *data, int size, struct event *event) -{ - struct print_fmt *print_fmt = &event->print_fmt; - struct print_arg *arg = print_fmt->args; - struct print_arg *args = NULL; - const char *ptr = print_fmt->format; - unsigned long long val; - struct func_map *func; - const char *saveptr; - char *bprint_fmt = NULL; - char format[32]; - int show_func; - int len; - int ls; - - if (event->flags & EVENT_FL_ISFUNC) - ptr = " %pF <-- %pF"; - - if (event->flags & EVENT_FL_ISBPRINT) { - bprint_fmt = get_bprint_format(data, size, event); - args = make_bprint_args(bprint_fmt, data, size, event); - arg = args; - ptr = bprint_fmt; - } - - for (; *ptr; ptr++) { - ls = 0; - if (*ptr == '\\') { - ptr++; - switch (*ptr) { - case 'n': - printf("\n"); - break; - case 't': - printf("\t"); - break; - case 'r': - printf("\r"); - break; - case '\\': - printf("\\"); - break; - default: - printf("%c", *ptr); - break; - } - - } else if (*ptr == '%') { - saveptr = ptr; - show_func = 0; - cont_process: - ptr++; - switch (*ptr) { - case '%': - printf("%%"); - break; - case 'l': - ls++; - goto cont_process; - case 'L': - ls = 2; - goto cont_process; - case 'z': - case 'Z': - case '0' ... '9': - goto cont_process; - case 'p': - if (long_size == 4) - ls = 1; - else - ls = 2; - - if (*(ptr+1) == 'F' || - *(ptr+1) == 'f') { - ptr++; - show_func = *ptr; - } - - /* fall through */ - case 'd': - case 'i': - case 'x': - case 'X': - case 'u': - if (!arg) - die("no argument match"); - - len = ((unsigned long)ptr + 1) - - (unsigned long)saveptr; - - /* should never happen */ - if (len > 32) - die("bad format!"); - - memcpy(format, saveptr, len); - format[len] = 0; - - val = eval_num_arg(data, size, event, arg); - arg = arg->next; - - if (show_func) { - func = find_func(val); - if (func) { - printf("%s", func->func); - if (show_func == 'F') - printf("+0x%llx", - val - func->addr); - break; - } - } - switch (ls) { - case 0: - printf(format, (int)val); - break; - case 1: - printf(format, (long)val); - break; - case 2: - printf(format, (long long)val); - break; - default: - die("bad count (%d)", ls); - } - break; - case 's': - if (!arg) - die("no matching argument"); - - print_str_arg(data, size, event, arg); - arg = arg->next; - break; - default: - printf(">%c<", *ptr); - - } - } else - printf("%c", *ptr); - } - - if (args) { - free_args(args); - free(bprint_fmt); - } -} - -static inline int log10_cpu(int nb) -{ - if (nb / 100) - return 3; - if (nb / 10) - return 2; - return 1; -} - -static void print_lat_fmt(void *data, int size __unused) -{ - unsigned int lat_flags; - unsigned int pc; - int lock_depth; - int hardirq; - int softirq; - - lat_flags = parse_common_flags(data); - pc = parse_common_pc(data); - lock_depth = parse_common_lock_depth(data); - - hardirq = lat_flags & TRACE_FLAG_HARDIRQ; - softirq = lat_flags & TRACE_FLAG_SOFTIRQ; - - printf("%c%c%c", - (lat_flags & TRACE_FLAG_IRQS_OFF) ? 'd' : - (lat_flags & TRACE_FLAG_IRQS_NOSUPPORT) ? - 'X' : '.', - (lat_flags & TRACE_FLAG_NEED_RESCHED) ? - 'N' : '.', - (hardirq && softirq) ? 'H' : - hardirq ? 'h' : softirq ? 's' : '.'); - - if (pc) - printf("%x", pc); - else - printf("."); - - if (lock_depth < 0) - printf(". "); - else - printf("%d ", lock_depth); -} - -#define TRACE_GRAPH_INDENT 2 - -static struct record * -get_return_for_leaf(int cpu, int cur_pid, unsigned long long cur_func, - struct record *next) -{ - struct format_field *field; - struct event *event; - unsigned long val; - int type; - int pid; - - type = trace_parse_common_type(next->data); - event = trace_find_event(type); - if (!event) - return NULL; - - if (!(event->flags & EVENT_FL_ISFUNCRET)) - return NULL; - - pid = trace_parse_common_pid(next->data); - field = find_field(event, "func"); - if (!field) - die("function return does not have field func"); - - val = read_size(next->data + field->offset, field->size); - - if (cur_pid != pid || cur_func != val) - return NULL; - - /* this is a leaf, now advance the iterator */ - return trace_read_data(cpu); -} - -/* Signal a overhead of time execution to the output */ -static void print_graph_overhead(unsigned long long duration) -{ - /* Non nested entry or return */ - if (duration == ~0ULL) - return (void)printf(" "); - - /* Duration exceeded 100 msecs */ - if (duration > 100000ULL) - return (void)printf("! "); - - /* Duration exceeded 10 msecs */ - if (duration > 10000ULL) - return (void)printf("+ "); - - printf(" "); -} - -static void print_graph_duration(unsigned long long duration) -{ - unsigned long usecs = duration / 1000; - unsigned long nsecs_rem = duration % 1000; - /* log10(ULONG_MAX) + '\0' */ - char msecs_str[21]; - char nsecs_str[5]; - int len; - int i; - - sprintf(msecs_str, "%lu", usecs); - - /* Print msecs */ - len = printf("%lu", usecs); - - /* Print nsecs (we don't want to exceed 7 numbers) */ - if (len < 7) { - snprintf(nsecs_str, 8 - len, "%03lu", nsecs_rem); - len += printf(".%s", nsecs_str); - } - - printf(" us "); - - /* Print remaining spaces to fit the row's width */ - for (i = len; i < 7; i++) - printf(" "); - - printf("| "); -} - -static void -print_graph_entry_leaf(struct event *event, void *data, struct record *ret_rec) -{ - unsigned long long rettime, calltime; - unsigned long long duration, depth; - unsigned long long val; - struct format_field *field; - struct func_map *func; - struct event *ret_event; - int type; - int i; - - type = trace_parse_common_type(ret_rec->data); - ret_event = trace_find_event(type); - - field = find_field(ret_event, "rettime"); - if (!field) - die("can't find rettime in return graph"); - rettime = read_size(ret_rec->data + field->offset, field->size); - - field = find_field(ret_event, "calltime"); - if (!field) - die("can't find rettime in return graph"); - calltime = read_size(ret_rec->data + field->offset, field->size); - - duration = rettime - calltime; - - /* Overhead */ - print_graph_overhead(duration); - - /* Duration */ - print_graph_duration(duration); - - field = find_field(event, "depth"); - if (!field) - die("can't find depth in entry graph"); - depth = read_size(data + field->offset, field->size); - - /* Function */ - for (i = 0; i < (int)(depth * TRACE_GRAPH_INDENT); i++) - printf(" "); - - field = find_field(event, "func"); - if (!field) - die("can't find func in entry graph"); - val = read_size(data + field->offset, field->size); - func = find_func(val); - - if (func) - printf("%s();", func->func); - else - printf("%llx();", val); -} - -static void print_graph_nested(struct event *event, void *data) -{ - struct format_field *field; - unsigned long long depth; - unsigned long long val; - struct func_map *func; - int i; - - /* No overhead */ - print_graph_overhead(-1); - - /* No time */ - printf(" | "); - - field = find_field(event, "depth"); - if (!field) - die("can't find depth in entry graph"); - depth = read_size(data + field->offset, field->size); - - /* Function */ - for (i = 0; i < (int)(depth * TRACE_GRAPH_INDENT); i++) - printf(" "); - - field = find_field(event, "func"); - if (!field) - die("can't find func in entry graph"); - val = read_size(data + field->offset, field->size); - func = find_func(val); - - if (func) - printf("%s() {", func->func); - else - printf("%llx() {", val); -} - -static void -pretty_print_func_ent(void *data, int size, struct event *event, - int cpu, int pid) -{ - struct format_field *field; - struct record *rec; - void *copy_data; - unsigned long val; - - if (latency_format) { - print_lat_fmt(data, size); - printf(" | "); - } - - field = find_field(event, "func"); - if (!field) - die("function entry does not have func field"); - - val = read_size(data + field->offset, field->size); - - /* - * peek_data may unmap the data pointer. Copy it first. - */ - copy_data = malloc_or_die(size); - memcpy(copy_data, data, size); - data = copy_data; - - rec = trace_peek_data(cpu); - if (rec) { - rec = get_return_for_leaf(cpu, pid, val, rec); - if (rec) { - print_graph_entry_leaf(event, data, rec); - goto out_free; - } - } - print_graph_nested(event, data); -out_free: - free(data); -} - -static void -pretty_print_func_ret(void *data, int size __unused, struct event *event) -{ - unsigned long long rettime, calltime; - unsigned long long duration, depth; - struct format_field *field; - int i; - - if (latency_format) { - print_lat_fmt(data, size); - printf(" | "); - } - - field = find_field(event, "rettime"); - if (!field) - die("can't find rettime in return graph"); - rettime = read_size(data + field->offset, field->size); - - field = find_field(event, "calltime"); - if (!field) - die("can't find calltime in return graph"); - calltime = read_size(data + field->offset, field->size); - - duration = rettime - calltime; - - /* Overhead */ - print_graph_overhead(duration); - - /* Duration */ - print_graph_duration(duration); - - field = find_field(event, "depth"); - if (!field) - die("can't find depth in entry graph"); - depth = read_size(data + field->offset, field->size); - - /* Function */ - for (i = 0; i < (int)(depth * TRACE_GRAPH_INDENT); i++) - printf(" "); - - printf("}"); -} - -static void -pretty_print_func_graph(void *data, int size, struct event *event, - int cpu, int pid) -{ - if (event->flags & EVENT_FL_ISFUNCENT) - pretty_print_func_ent(data, size, event, cpu, pid); - else if (event->flags & EVENT_FL_ISFUNCRET) - pretty_print_func_ret(data, size, event); - printf("\n"); -} - -void print_trace_event(int cpu, void *data, int size) -{ - struct event *event; - int type; - int pid; - - type = trace_parse_common_type(data); - - event = trace_find_event(type); - if (!event) { - warning("ug! no event found for type %d", type); - return; - } - - pid = trace_parse_common_pid(data); - - if (event->flags & (EVENT_FL_ISFUNCENT | EVENT_FL_ISFUNCRET)) - return pretty_print_func_graph(data, size, event, cpu, pid); - - if (latency_format) - print_lat_fmt(data, size); - - if (event->flags & EVENT_FL_FAILED) { - printf("EVENT '%s' FAILED TO PARSE\n", - event->name); - return; - } - - pretty_print(data, size, event); -} - -static void print_fields(struct print_flag_sym *field) -{ - printf("{ %s, %s }", field->value, field->str); - if (field->next) { - printf(", "); - print_fields(field->next); - } -} - -static void print_args(struct print_arg *args) -{ - int print_paren = 1; - - switch (args->type) { - case PRINT_NULL: - printf("null"); - break; - case PRINT_ATOM: - printf("%s", args->atom.atom); - break; - case PRINT_FIELD: - printf("REC->%s", args->field.name); - break; - case PRINT_FLAGS: - printf("__print_flags("); - print_args(args->flags.field); - printf(", %s, ", args->flags.delim); - print_fields(args->flags.flags); - printf(")"); - break; - case PRINT_SYMBOL: - printf("__print_symbolic("); - print_args(args->symbol.field); - printf(", "); - print_fields(args->symbol.symbols); - printf(")"); - break; - case PRINT_STRING: - printf("__get_str(%s)", args->string.string); - break; - case PRINT_TYPE: - printf("(%s)", args->typecast.type); - print_args(args->typecast.item); - break; - case PRINT_OP: - if (strcmp(args->op.op, ":") == 0) - print_paren = 0; - if (print_paren) - printf("("); - print_args(args->op.left); - printf(" %s ", args->op.op); - print_args(args->op.right); - if (print_paren) - printf(")"); - break; - default: - /* we should warn... */ - return; - } - if (args->next) { - printf("\n"); - print_args(args->next); - } -} - -int parse_ftrace_file(char *buf, unsigned long size) -{ - struct format_field *field; - struct print_arg *arg, **list; - struct event *event; - int ret; - - init_input_buf(buf, size); - - event = alloc_event(); - if (!event) - return -ENOMEM; - - event->flags |= EVENT_FL_ISFTRACE; - - event->name = event_read_name(); - if (!event->name) - die("failed to read ftrace event name"); - - if (strcmp(event->name, "function") == 0) - event->flags |= EVENT_FL_ISFUNC; - - else if (strcmp(event->name, "funcgraph_entry") == 0) - event->flags |= EVENT_FL_ISFUNCENT; - - else if (strcmp(event->name, "funcgraph_exit") == 0) - event->flags |= EVENT_FL_ISFUNCRET; - - else if (strcmp(event->name, "bprint") == 0) - event->flags |= EVENT_FL_ISBPRINT; - - event->id = event_read_id(); - if (event->id < 0) - die("failed to read ftrace event id"); - - add_event(event); - - ret = event_read_format(event); - if (ret < 0) - die("failed to read ftrace event format"); - - ret = event_read_print(event); - if (ret < 0) - die("failed to read ftrace event print fmt"); - - /* New ftrace handles args */ - if (ret > 0) - return 0; - /* - * The arguments for ftrace files are parsed by the fields. - * Set up the fields as their arguments. - */ - list = &event->print_fmt.args; - for (field = event->format.fields; field; field = field->next) { - arg = malloc_or_die(sizeof(*arg)); - memset(arg, 0, sizeof(*arg)); - *list = arg; - list = &arg->next; - arg->type = PRINT_FIELD; - arg->field.name = field->name; - arg->field.field = field; - } - return 0; -} - -int parse_event_file(char *buf, unsigned long size, char *sys) -{ - struct event *event; - int ret; - - init_input_buf(buf, size); - - event = alloc_event(); - if (!event) - return -ENOMEM; - - event->name = event_read_name(); - if (!event->name) - die("failed to read event name"); - - event->id = event_read_id(); - if (event->id < 0) - die("failed to read event id"); - - ret = event_read_format(event); - if (ret < 0) { - warning("failed to read event format for %s", event->name); - goto event_failed; - } - - ret = event_read_print(event); - if (ret < 0) { - warning("failed to read event print fmt for %s", event->name); - goto event_failed; - } - - event->system = strdup(sys); - -#define PRINT_ARGS 0 - if (PRINT_ARGS && event->print_fmt.args) - print_args(event->print_fmt.args); - - add_event(event); - return 0; - - event_failed: - event->flags |= EVENT_FL_FAILED; - /* still add it even if it failed */ - add_event(event); - return -1; -} - -void parse_set_info(int nr_cpus, int long_sz) -{ - cpus = nr_cpus; - long_size = long_sz; -} - -int common_pc(struct scripting_context *context) -{ - return parse_common_pc(context->event_data); -} - -int common_flags(struct scripting_context *context) -{ - return parse_common_flags(context->event_data); -} - -int common_lock_depth(struct scripting_context *context) -{ - return parse_common_lock_depth(context->event_data); -} diff --git a/tools/perf/util/trace-event-read.c b/tools/perf/util/trace-event-read.c index f55cc3a765a12..f097e0dd6c5cb 100644 --- a/tools/perf/util/trace-event-read.c +++ b/tools/perf/util/trace-event-read.c @@ -33,7 +33,6 @@ #include <pthread.h> #include <fcntl.h> #include <unistd.h> -#include <ctype.h> #include <errno.h> #include "../perf.h" @@ -53,6 +52,16 @@ static unsigned long page_size; static ssize_t calc_data_size; static bool repipe; +static void *malloc_or_die(int size) +{ + void *ret; + + ret = malloc(size); + if (!ret) + die("malloc"); + return ret; +} + static int do_read(int fd, void *buf, int size) { int rsize = size; @@ -110,7 +119,7 @@ static unsigned int read4(void) unsigned int data; read_or_die(&data, 4); - return __data2host4(data); + return __data2host4(perf_pevent, data); } static unsigned long long read8(void) @@ -118,7 +127,7 @@ static unsigned long long read8(void) unsigned long long data; read_or_die(&data, 8); - return __data2host8(data); + return __data2host8(perf_pevent, data); } static char *read_string(void) @@ -283,7 +292,7 @@ struct cpu_data { unsigned long long offset; unsigned long long size; unsigned long long timestamp; - struct record *next; + struct pevent_record *next; char *page; int cpu; int index; @@ -368,9 +377,9 @@ static int calc_index(void *ptr, int cpu) return (unsigned long)ptr - (unsigned long)cpu_data[cpu].page; } -struct record *trace_peek_data(int cpu) +struct pevent_record *trace_peek_data(int cpu) { - struct record *data; + struct pevent_record *data; void *page = cpu_data[cpu].page; int idx = cpu_data[cpu].index; void *ptr = page + idx; @@ -390,15 +399,15 @@ struct record *trace_peek_data(int cpu) /* FIXME: handle header page */ if (header_page_ts_size != 8) die("expected a long long type for timestamp"); - cpu_data[cpu].timestamp = data2host8(ptr); + cpu_data[cpu].timestamp = data2host8(perf_pevent, ptr); ptr += 8; switch (header_page_size_size) { case 4: - cpu_data[cpu].page_size = data2host4(ptr); + cpu_data[cpu].page_size = data2host4(perf_pevent, ptr); ptr += 4; break; case 8: - cpu_data[cpu].page_size = data2host8(ptr); + cpu_data[cpu].page_size = data2host8(perf_pevent, ptr); ptr += 8; break; default: @@ -415,7 +424,7 @@ read_again: return trace_peek_data(cpu); } - type_len_ts = data2host4(ptr); + type_len_ts = data2host4(perf_pevent, ptr); ptr += 4; type_len = type_len4host(type_len_ts); @@ -425,14 +434,14 @@ read_again: case RINGBUF_TYPE_PADDING: if (!delta) die("error, hit unexpected end of page"); - length = data2host4(ptr); + length = data2host4(perf_pevent, ptr); ptr += 4; length *= 4; ptr += length; goto read_again; case RINGBUF_TYPE_TIME_EXTEND: - extend = data2host4(ptr); + extend = data2host4(perf_pevent, ptr); ptr += 4; extend <<= TS_SHIFT; extend += delta; @@ -443,7 +452,7 @@ read_again: ptr += 12; break; case 0: - length = data2host4(ptr); + length = data2host4(perf_pevent, ptr); ptr += 4; die("here! length=%d", length); break; @@ -468,9 +477,9 @@ read_again: return data; } -struct record *trace_read_data(int cpu) +struct pevent_record *trace_read_data(int cpu) { - struct record *data; + struct pevent_record *data; data = trace_peek_data(cpu); cpu_data[cpu].next = NULL; @@ -510,6 +519,8 @@ ssize_t trace_report(int fd, bool __repipe) file_bigendian = buf[0]; host_bigendian = bigendian(); + read_trace_init(file_bigendian, host_bigendian); + read_or_die(buf, 1); long_size = buf[0]; @@ -527,11 +538,11 @@ ssize_t trace_report(int fd, bool __repipe) repipe = false; if (show_funcs) { - print_funcs(); + pevent_print_funcs(perf_pevent); return size; } if (show_printk) { - print_printk(); + pevent_print_printk(perf_pevent); return size; } diff --git a/tools/perf/util/trace-event-scripting.c b/tools/perf/util/trace-event-scripting.c index a3fdf55f317bb..18ae6c1831d38 100644 --- a/tools/perf/util/trace-event-scripting.c +++ b/tools/perf/util/trace-event-scripting.c @@ -22,7 +22,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <ctype.h> #include <errno.h> #include "../perf.h" diff --git a/tools/perf/util/trace-event.h b/tools/perf/util/trace-event.h index 58ae14c5baacb..639852ac11171 100644 --- a/tools/perf/util/trace-event.h +++ b/tools/perf/util/trace-event.h @@ -1,20 +1,21 @@ -#ifndef __PERF_TRACE_EVENTS_H -#define __PERF_TRACE_EVENTS_H +#ifndef _PERF_UTIL_TRACE_EVENT_H +#define _PERF_UTIL_TRACE_EVENT_H -#include <stdbool.h> #include "parse-events.h" +#include "event-parse.h" +#include "session.h" struct machine; struct perf_sample; union perf_event; struct thread; -#define __unused __attribute__((unused)) - +extern int header_page_size_size; +extern int header_page_ts_size; +extern int header_page_data_offset; -#ifndef PAGE_MASK -#define PAGE_MASK (page_size - 1) -#endif +extern bool latency_format; +extern struct pevent *perf_pevent; enum { RINGBUF_TYPE_PADDING = 29, @@ -26,246 +27,37 @@ enum { #define TS_SHIFT 27 #endif -#define NSECS_PER_SEC 1000000000ULL -#define NSECS_PER_USEC 1000ULL - -enum format_flags { - FIELD_IS_ARRAY = 1, - FIELD_IS_POINTER = 2, - FIELD_IS_SIGNED = 4, - FIELD_IS_STRING = 8, - FIELD_IS_DYNAMIC = 16, - FIELD_IS_FLAG = 32, - FIELD_IS_SYMBOLIC = 64, -}; - -struct format_field { - struct format_field *next; - char *type; - char *name; - int offset; - int size; - unsigned long flags; -}; - -struct format { - int nr_common; - int nr_fields; - struct format_field *common_fields; - struct format_field *fields; -}; - -struct print_arg_atom { - char *atom; -}; - -struct print_arg_string { - char *string; - int offset; -}; - -struct print_arg_field { - char *name; - struct format_field *field; -}; - -struct print_flag_sym { - struct print_flag_sym *next; - char *value; - char *str; -}; - -struct print_arg_typecast { - char *type; - struct print_arg *item; -}; - -struct print_arg_flags { - struct print_arg *field; - char *delim; - struct print_flag_sym *flags; -}; - -struct print_arg_symbol { - struct print_arg *field; - struct print_flag_sym *symbols; -}; - -struct print_arg; - -struct print_arg_op { - char *op; - int prio; - struct print_arg *left; - struct print_arg *right; -}; - -struct print_arg_func { - char *name; - struct print_arg *args; -}; - -enum print_arg_type { - PRINT_NULL, - PRINT_ATOM, - PRINT_FIELD, - PRINT_FLAGS, - PRINT_SYMBOL, - PRINT_TYPE, - PRINT_STRING, - PRINT_OP, -}; - -struct print_arg { - struct print_arg *next; - enum print_arg_type type; - union { - struct print_arg_atom atom; - struct print_arg_field field; - struct print_arg_typecast typecast; - struct print_arg_flags flags; - struct print_arg_symbol symbol; - struct print_arg_func func; - struct print_arg_string string; - struct print_arg_op op; - }; -}; - -struct print_fmt { - char *format; - struct print_arg *args; -}; - -struct event { - struct event *next; - char *name; - int id; - int flags; - struct format format; - struct print_fmt print_fmt; - char *system; -}; - -enum { - EVENT_FL_ISFTRACE = 0x01, - EVENT_FL_ISPRINT = 0x02, - EVENT_FL_ISBPRINT = 0x04, - EVENT_FL_ISFUNC = 0x08, - EVENT_FL_ISFUNCENT = 0x10, - EVENT_FL_ISFUNCRET = 0x20, - - EVENT_FL_FAILED = 0x80000000 -}; - -struct record { - unsigned long long ts; - int size; - void *data; -}; - -struct record *trace_peek_data(int cpu); -struct record *trace_read_data(int cpu); - -void parse_set_info(int nr_cpus, int long_sz); - -ssize_t trace_report(int fd, bool repipe); - -void *malloc_or_die(unsigned int size); +int bigendian(void); -void parse_cmdlines(char *file, int size); -void parse_proc_kallsyms(char *file, unsigned int size); -void parse_ftrace_printk(char *file, unsigned int size); +int read_trace_init(int file_bigendian, int host_bigendian); +void print_trace_event(int cpu, void *data, int size); -void print_funcs(void); -void print_printk(void); +void print_event(int cpu, void *data, int size, unsigned long long nsecs, + char *comm); int parse_ftrace_file(char *buf, unsigned long size); int parse_event_file(char *buf, unsigned long size, char *sys); -void print_trace_event(int cpu, void *data, int size); - -extern int file_bigendian; -extern int host_bigendian; - -int bigendian(void); - -static inline unsigned short __data2host2(unsigned short data) -{ - unsigned short swap; - - if (host_bigendian == file_bigendian) - return data; - swap = ((data & 0xffULL) << 8) | - ((data & (0xffULL << 8)) >> 8); +struct pevent_record *trace_peek_data(int cpu); +struct event_format *trace_find_event(int type); - return swap; -} - -static inline unsigned int __data2host4(unsigned int data) -{ - unsigned int swap; - - if (host_bigendian == file_bigendian) - return data; - - swap = ((data & 0xffULL) << 24) | - ((data & (0xffULL << 8)) << 8) | - ((data & (0xffULL << 16)) >> 8) | - ((data & (0xffULL << 24)) >> 24); - - return swap; -} - -static inline unsigned long long __data2host8(unsigned long long data) -{ - unsigned long long swap; - - if (host_bigendian == file_bigendian) - return data; - - swap = ((data & 0xffULL) << 56) | - ((data & (0xffULL << 8)) << 40) | - ((data & (0xffULL << 16)) << 24) | - ((data & (0xffULL << 24)) << 8) | - ((data & (0xffULL << 32)) >> 8) | - ((data & (0xffULL << 40)) >> 24) | - ((data & (0xffULL << 48)) >> 40) | - ((data & (0xffULL << 56)) >> 56); - - return swap; -} +unsigned long long +raw_field_value(struct event_format *event, const char *name, void *data); +void *raw_field_ptr(struct event_format *event, const char *name, void *data); -#define data2host2(ptr) __data2host2(*(unsigned short *)ptr) -#define data2host4(ptr) __data2host4(*(unsigned int *)ptr) -#define data2host8(ptr) ({ \ - unsigned long long __val; \ - \ - memcpy(&__val, (ptr), sizeof(unsigned long long)); \ - __data2host8(__val); \ -}) +void parse_proc_kallsyms(char *file, unsigned int size __unused); +void parse_ftrace_printk(char *file, unsigned int size __unused); -extern int header_page_ts_offset; -extern int header_page_ts_size; -extern int header_page_size_offset; -extern int header_page_size_size; -extern int header_page_data_offset; -extern int header_page_data_size; - -extern bool latency_format; +ssize_t trace_report(int fd, bool repipe); int trace_parse_common_type(void *data); int trace_parse_common_pid(void *data); -int parse_common_pc(void *data); -int parse_common_flags(void *data); -int parse_common_lock_depth(void *data); -struct event *trace_find_event(int id); -struct event *trace_find_next_event(struct event *event); + +struct event_format *trace_find_next_event(struct event_format *event); unsigned long long read_size(void *ptr, int size); -unsigned long long -raw_field_value(struct event *event, const char *name, void *data); -void *raw_field_ptr(struct event *event, const char *name, void *data); unsigned long long eval_flag(const char *flag); +struct pevent_record *trace_read_data(int cpu); int read_tracing_data(int fd, struct list_head *pattrs); struct tracing_data { @@ -280,15 +72,6 @@ struct tracing_data *tracing_data_get(struct list_head *pattrs, void tracing_data_put(struct tracing_data *tdata); -/* taken from kernel/trace/trace.h */ -enum trace_flag_type { - TRACE_FLAG_IRQS_OFF = 0x01, - TRACE_FLAG_IRQS_NOSUPPORT = 0x02, - TRACE_FLAG_NEED_RESCHED = 0x04, - TRACE_FLAG_HARDIRQ = 0x08, - TRACE_FLAG_SOFTIRQ = 0x10, -}; - struct scripting_ops { const char *name; int (*start_script) (const char *script, int argc, const char **argv); @@ -314,4 +97,4 @@ int common_pc(struct scripting_context *context); int common_flags(struct scripting_context *context); int common_lock_depth(struct scripting_context *context); -#endif /* __PERF_TRACE_EVENTS_H */ +#endif /* _PERF_UTIL_TRACE_EVENT_H */ diff --git a/tools/perf/util/ui/browsers/annotate.c b/tools/perf/util/ui/browsers/annotate.c deleted file mode 100644 index 295a9c93f9459..0000000000000 --- a/tools/perf/util/ui/browsers/annotate.c +++ /dev/null @@ -1,427 +0,0 @@ -#include "../../util.h" -#include "../browser.h" -#include "../helpline.h" -#include "../libslang.h" -#include "../ui.h" -#include "../util.h" -#include "../../annotate.h" -#include "../../hist.h" -#include "../../sort.h" -#include "../../symbol.h" -#include <pthread.h> -#include <newt.h> - -struct annotate_browser { - struct ui_browser b; - struct rb_root entries; - struct rb_node *curr_hot; - struct objdump_line *selection; - int nr_asm_entries; - int nr_entries; - bool hide_src_code; -}; - -struct objdump_line_rb_node { - struct rb_node rb_node; - double percent; - u32 idx; - int idx_asm; -}; - -static inline -struct objdump_line_rb_node *objdump_line__rb(struct objdump_line *self) -{ - return (struct objdump_line_rb_node *)(self + 1); -} - -static bool objdump_line__filter(struct ui_browser *browser, void *entry) -{ - struct annotate_browser *ab = container_of(browser, struct annotate_browser, b); - - if (ab->hide_src_code) { - struct objdump_line *ol = list_entry(entry, struct objdump_line, node); - return ol->offset == -1; - } - - return false; -} - -static void annotate_browser__write(struct ui_browser *self, void *entry, int row) -{ - struct annotate_browser *ab = container_of(self, struct annotate_browser, b); - struct objdump_line *ol = list_entry(entry, struct objdump_line, node); - bool current_entry = ui_browser__is_current_entry(self, row); - int width = self->width; - - 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); - } else { - ui_browser__set_percent_color(self, 0, current_entry); - slsmg_write_nstring(" ", 9); - } - - SLsmg_write_char(':'); - slsmg_write_nstring(" ", 8); - - /* The scroll bar isn't being used */ - if (!self->navkeypressed) - width += 1; - - if (!*ol->line) - slsmg_write_nstring(" ", width - 18); - else - slsmg_write_nstring(ol->line, width - 18); - - if (!current_entry) - ui_browser__set_color(self, HE_COLORSET_CODE); - else - ab->selection = ol; -} - -static double objdump_line__calc_percent(struct objdump_line *self, - struct symbol *sym, int evidx) -{ - double percent = 0.0; - - if (self->offset != -1) { - int len = sym->end - sym->start; - unsigned int hits = 0; - struct annotation *notes = symbol__annotation(sym); - struct source_line *src_line = notes->src->lines; - struct sym_hist *h = annotation__histogram(notes, evidx); - s64 offset = self->offset; - struct objdump_line *next; - - next = objdump__get_next_ip_line(¬es->src->source, self); - while (offset < (s64)len && - (next == NULL || offset < next->offset)) { - if (src_line) { - percent += src_line[offset].percent; - } else - hits += h->addr[offset]; - - ++offset; - } - /* - * If the percentage wasn't already calculated in - * symbol__get_source_line, do it now: - */ - if (src_line == 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 void annotate_browser__calc_percent(struct annotate_browser *browser, - int evidx) -{ - struct map_symbol *ms = browser->b.priv; - struct symbol *sym = ms->sym; - struct annotation *notes = symbol__annotation(sym); - struct objdump_line *pos; - - browser->entries = RB_ROOT; - - pthread_mutex_lock(¬es->lock); - - list_for_each_entry(pos, ¬es->src->source, node) { - struct objdump_line_rb_node *rbpos = objdump_line__rb(pos); - rbpos->percent = objdump_line__calc_percent(pos, sym, evidx); - if (rbpos->percent < 0.01) { - RB_CLEAR_NODE(&rbpos->rb_node); - continue; - } - objdump__insert_line(&browser->entries, rbpos); - } - pthread_mutex_unlock(¬es->lock); - - browser->curr_hot = rb_last(&browser->entries); -} - -static bool annotate_browser__toggle_source(struct annotate_browser *browser) -{ - struct objdump_line *ol; - struct objdump_line_rb_node *olrb; - off_t offset = browser->b.index - browser->b.top_idx; - - browser->b.seek(&browser->b, offset, SEEK_CUR); - ol = list_entry(browser->b.top, struct objdump_line, node); - olrb = objdump_line__rb(ol); - - if (browser->hide_src_code) { - if (olrb->idx_asm < offset) - offset = olrb->idx; - - browser->b.nr_entries = browser->nr_entries; - browser->hide_src_code = false; - browser->b.seek(&browser->b, -offset, SEEK_CUR); - browser->b.top_idx = olrb->idx - offset; - browser->b.index = olrb->idx; - } else { - if (olrb->idx_asm < 0) { - ui_helpline__puts("Only available for assembly lines."); - browser->b.seek(&browser->b, -offset, SEEK_CUR); - return false; - } - - if (olrb->idx_asm < offset) - offset = olrb->idx_asm; - - browser->b.nr_entries = browser->nr_asm_entries; - browser->hide_src_code = true; - browser->b.seek(&browser->b, -offset, SEEK_CUR); - browser->b.top_idx = olrb->idx_asm - offset; - browser->b.index = olrb->idx_asm; - } - - return true; -} - -static int annotate_browser__run(struct annotate_browser *self, int evidx, - void(*timer)(void *arg), - void *arg, int delay_secs) -{ - struct rb_node *nd = NULL; - struct map_symbol *ms = self->b.priv; - struct symbol *sym = ms->sym; - const char *help = "<-, ESC: exit, TAB/shift+TAB: cycle hottest lines, " - "H: Hottest, -> Line action, S -> Toggle source " - "code view"; - int key; - - if (ui_browser__show(&self->b, sym->name, help) < 0) - return -1; - - annotate_browser__calc_percent(self, evidx); - - if (self->curr_hot) - annotate_browser__set_top(self, self->curr_hot); - - nd = self->curr_hot; - - while (1) { - key = ui_browser__run(&self->b, delay_secs); - - if (delay_secs != 0) { - annotate_browser__calc_percent(self, evidx); - /* - * Current line focus got out of the list of most active - * lines, NULL it so that if TAB|UNTAB is pressed, we - * move to curr_hot (current hottest line). - */ - if (nd != NULL && RB_EMPTY_NODE(nd)) - nd = NULL; - } - - switch (key) { - case K_TIMER: - if (timer != NULL) - timer(arg); - - if (delay_secs != 0) - symbol__annotate_decay_histogram(sym, evidx); - continue; - case K_TAB: - if (nd != NULL) { - nd = rb_prev(nd); - if (nd == NULL) - nd = rb_last(&self->entries); - } else - nd = self->curr_hot; - break; - case K_UNTAB: - if (nd != NULL) - nd = rb_next(nd); - if (nd == NULL) - nd = rb_first(&self->entries); - else - nd = self->curr_hot; - break; - case 'H': - nd = self->curr_hot; - break; - case 'S': - if (annotate_browser__toggle_source(self)) - ui_helpline__puts(help); - continue; - case K_ENTER: - case K_RIGHT: - if (self->selection == NULL) { - ui_helpline__puts("Huh? No selection. Report to linux-kernel@vger.kernel.org"); - continue; - } - - if (self->selection->offset == -1) { - ui_helpline__puts("Actions are only available for assembly lines."); - continue; - } else { - char *s = strstr(self->selection->line, "callq "); - struct annotation *notes; - struct symbol *target; - u64 ip; - - if (s == NULL) { - ui_helpline__puts("Actions are only available for the 'callq' instruction."); - continue; - } - - s = strchr(s, ' '); - if (s++ == NULL) { - ui_helpline__puts("Invallid callq instruction."); - continue; - } - - ip = strtoull(s, NULL, 16); - ip = ms->map->map_ip(ms->map, ip); - target = map__find_symbol(ms->map, ip, NULL); - if (target == NULL) { - ui_helpline__puts("The called function was not found."); - continue; - } - - notes = symbol__annotation(target); - pthread_mutex_lock(¬es->lock); - - if (notes->src == NULL && symbol__alloc_hist(target) < 0) { - pthread_mutex_unlock(¬es->lock); - ui__warning("Not enough memory for annotating '%s' symbol!\n", - target->name); - continue; - } - - pthread_mutex_unlock(¬es->lock); - symbol__tui_annotate(target, ms->map, evidx, - timer, arg, delay_secs); - } - continue; - case K_LEFT: - case K_ESC: - case 'q': - case CTRL('c'): - goto out; - default: - continue; - } - - if (nd != NULL) - annotate_browser__set_top(self, nd); - } -out: - ui_browser__hide(&self->b); - return key; -} - -int hist_entry__tui_annotate(struct hist_entry *he, int evidx, - void(*timer)(void *arg), void *arg, int delay_secs) -{ - return symbol__tui_annotate(he->ms.sym, he->ms.map, evidx, - timer, arg, delay_secs); -} - -int symbol__tui_annotate(struct symbol *sym, struct map *map, int evidx, - void(*timer)(void *arg), void *arg, - int delay_secs) -{ - struct objdump_line *pos, *n; - struct annotation *notes; - struct map_symbol ms = { - .map = map, - .sym = sym, - }; - struct annotate_browser browser = { - .b = { - .refresh = ui_browser__list_head_refresh, - .seek = ui_browser__list_head_seek, - .write = annotate_browser__write, - .filter = objdump_line__filter, - .priv = &ms, - .use_navkeypressed = true, - }, - }; - int ret; - - if (sym == NULL) - return -1; - - if (map->dso->annotate_warned) - return -1; - - if (symbol__annotate(sym, map, sizeof(struct objdump_line_rb_node)) < 0) { - ui__error("%s", ui_helpline__last_msg); - return -1; - } - - ui_helpline__push("Press <- or ESC to exit"); - - notes = symbol__annotation(sym); - - list_for_each_entry(pos, ¬es->src->source, node) { - struct objdump_line_rb_node *rbpos; - 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.nr_entries++; - if (pos->offset != -1) - rbpos->idx_asm = browser.nr_asm_entries++; - else - rbpos->idx_asm = -1; - } - - browser.b.nr_entries = browser.nr_entries; - browser.b.entries = ¬es->src->source, - browser.b.width += 18; /* Percentage */ - ret = annotate_browser__run(&browser, evidx, timer, arg, delay_secs); - list_for_each_entry_safe(pos, n, ¬es->src->source, node) { - list_del(&pos->node); - objdump_line__free(pos); - } - return ret; -} diff --git a/tools/perf/util/usage.c b/tools/perf/util/usage.c index d76d1c0ff98fc..4007aca8e0caa 100644 --- a/tools/perf/util/usage.c +++ b/tools/perf/util/usage.c @@ -7,6 +7,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "util.h" +#include "debug.h" static void report(const char *prefix, const char *err, va_list params) { diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 813141047fc22..d03599fbe78ba 100644 --- a/tools/perf/util/util.c +++ b/tools/perf/util/util.c @@ -6,7 +6,7 @@ * XXX We need to find a better place for these things... */ bool perf_host = true; -bool perf_guest = true; +bool perf_guest = false; void event_attr_init(struct perf_event_attr *attr) { @@ -14,6 +14,8 @@ void event_attr_init(struct perf_event_attr *attr) attr->exclude_host = 1; if (!perf_guest) attr->exclude_guest = 1; + /* to capture ABI version */ + attr->size = sizeof(*attr); } int mkdir_p(char *path, mode_t mode) @@ -146,3 +148,13 @@ int readn(int fd, void *buf, size_t n) return buf - buf_start; } + +size_t hex_width(u64 v) +{ + size_t n = 1; + + while ((v >>= 4)) + ++n; + + return n; +} diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index ecf9898169c86..2daaedb83d842 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -74,7 +74,6 @@ #include <netinet/tcp.h> #include <arpa/inet.h> #include <netdb.h> -#include <pwd.h> #include <inttypes.h> #include "../../../include/linux/magic.h" #include "types.h" @@ -199,6 +198,8 @@ static inline int has_extension(const char *filename, const char *ext) #undef isalpha #undef isprint #undef isalnum +#undef islower +#undef isupper #undef tolower #undef toupper @@ -219,6 +220,8 @@ extern unsigned char sane_ctype[256]; #define isalpha(x) sane_istest(x,GIT_ALPHA) #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT) #define isprint(x) sane_istest(x,GIT_PRINT) +#define islower(x) (sane_istest(x,GIT_ALPHA) && sane_istest(x,0x20)) +#define isupper(x) (sane_istest(x,GIT_ALPHA) && !sane_istest(x,0x20)) #define tolower(x) sane_case((unsigned char)(x), 0x20) #define toupper(x) sane_case((unsigned char)(x), 0) @@ -259,4 +262,6 @@ bool is_power_of_2(unsigned long n) return (n != 0 && ((n & (n - 1)) == 0)); } +size_t hex_width(u64 v); + #endif |