diff options
Diffstat (limited to 'tools/perf')
197 files changed, 10146 insertions, 2645 deletions
diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt index 4464ad770d51..f6480cbf309b 100644 --- a/tools/perf/Documentation/perf-bench.txt +++ b/tools/perf/Documentation/perf-bench.txt @@ -16,6 +16,10 @@ This 'perf bench' command is a general framework for benchmark suites. COMMON OPTIONS -------------- +-r:: +--repeat=:: +Specify amount of times to repeat the run (default 10). + -f:: --format=:: Specify format style. diff --git a/tools/perf/Documentation/perf-diff.txt b/tools/perf/Documentation/perf-diff.txt index fdfceee0ffd0..b3b8abae62b8 100644 --- a/tools/perf/Documentation/perf-diff.txt +++ b/tools/perf/Documentation/perf-diff.txt @@ -33,21 +33,25 @@ OPTIONS -d:: --dsos=:: Only consider symbols in these dsos. CSV that understands - file://filename entries. + file://filename entries. This option will affect the percentage + of the Baseline/Delta column. See --percentage for more info. -C:: --comms=:: Only consider symbols in these comms. CSV that understands - file://filename entries. + file://filename entries. This option will affect the percentage + of the Baseline/Delta column. See --percentage for more info. -S:: --symbols=:: Only consider these symbols. CSV that understands - file://filename entries. + file://filename entries. This option will affect the percentage + of the Baseline/Delta column. See --percentage for more info. -s:: --sort=:: - Sort by key(s): pid, comm, dso, symbol. + Sort by key(s): pid, comm, dso, symbol, cpu, parent, srcline. + Please see description of --sort in the perf-report man page. -t:: --field-separator=:: @@ -89,6 +93,14 @@ OPTIONS --order:: Specify compute sorting column number. +--percentage:: + Determine how to display the overhead percentage of filtered entries. + Filters can be applied by --comms, --dsos and/or --symbols options. + + "relative" means it's relative to filtered entries only so that the + sum of shown entries will be always 100%. "absolute" means it retains + the original value before and after the filter is applied. + COMPARISON ---------- The comparison is governed by the baseline file. The baseline perf.data @@ -157,6 +169,10 @@ with: - period_percent being the % of the hist entry period value within single data file + - with filtering by -C, -d and/or -S, period_percent might be changed + relative to how entries are filtered. Use --percentage=absolute to + prevent such fluctuation. + ratio ~~~~~ If specified the 'Ratio' column is displayed with value 'r' computed as: @@ -187,4 +203,4 @@ If specified the 'Weighted diff' column is displayed with value 'd' computed as: SEE ALSO -------- -linkperf:perf-record[1] +linkperf:perf-record[1], linkperf:perf-report[1] diff --git a/tools/perf/Documentation/perf-inject.txt b/tools/perf/Documentation/perf-inject.txt index a00a34276c54..dc7442cf3d7f 100644 --- a/tools/perf/Documentation/perf-inject.txt +++ b/tools/perf/Documentation/perf-inject.txt @@ -41,6 +41,9 @@ OPTIONS tasks slept. sched_switch contains a callchain where a task slept and sched_stat contains a timeslice how long a task slept. +--kallsyms=<file>:: + kallsyms pathname + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-report[1], linkperf:perf-archive[1] diff --git a/tools/perf/Documentation/perf-kvm.txt b/tools/perf/Documentation/perf-kvm.txt index 52276a6d2b75..6e689dc89a2f 100644 --- a/tools/perf/Documentation/perf-kvm.txt +++ b/tools/perf/Documentation/perf-kvm.txt @@ -51,9 +51,9 @@ There are a couple of variants of perf kvm: 'perf kvm stat <command>' to run a command and gather performance counter statistics. Especially, perf 'kvm stat record/report' generates a statistical analysis - of KVM events. Currently, vmexit, mmio and ioport events are supported. - 'perf kvm stat record <command>' records kvm events and the events between - start and end <command>. + of KVM events. Currently, vmexit, mmio (x86 only) and ioport (x86 only) + events are supported. 'perf kvm stat record <command>' records kvm events + and the events between start and end <command>. And this command produces a file which contains tracing results of kvm events. @@ -103,8 +103,8 @@ STAT REPORT OPTIONS analyze events which occures on this vcpu. (default: all vcpus) --event=<value>:: - event to be analyzed. Possible values: vmexit, mmio, ioport. - (default: vmexit) + event to be analyzed. Possible values: vmexit, mmio (x86 only), + ioport (x86 only). (default: vmexit) -k:: --key=<value>:: Sorting key. Possible values: sample (default, sort by samples @@ -138,7 +138,8 @@ STAT LIVE OPTIONS --event=<value>:: - event to be analyzed. Possible values: vmexit, mmio, ioport. + event to be analyzed. Possible values: vmexit, + mmio (x86 only), ioport (x86 only). (default: vmexit) -k:: @@ -147,7 +148,8 @@ STAT LIVE OPTIONS number), time (sort by average time). --duration=<value>:: - Show events other than HLT that take longer than duration usecs. + Show events other than HLT (x86 only) or Wait state (s390 only) + that take longer than duration usecs. SEE ALSO -------- diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt index c71b0f36d9e8..d460049cae8e 100644 --- a/tools/perf/Documentation/perf-record.txt +++ b/tools/perf/Documentation/perf-record.txt @@ -184,9 +184,10 @@ following filters are defined: - in_tx: only when the target is in a hardware transaction - no_tx: only when the target is not in a hardware transaction - abort_tx: only when the target is a hardware transaction abort + - cond: conditional branches + -The option requires at least one branch type among any, any_call, any_ret, ind_call. +The option requires at least one branch type among any, any_call, any_ret, ind_call, cond. The privilege levels may be omitted, 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 diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 8eab8a4bdeb8..d2b59af62bc0 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -25,10 +25,6 @@ OPTIONS --verbose:: Be more verbose. (show symbol address, etc) --d:: ---dsos=:: - Only consider symbols in these dsos. CSV that understands - file://filename entries. -n:: --show-nr-samples:: Show the number of samples for each symbol @@ -42,11 +38,18 @@ OPTIONS -c:: --comms=:: Only consider symbols in these comms. CSV that understands - file://filename entries. + file://filename entries. This option will affect the percentage of + the overhead column. See --percentage for more info. +-d:: +--dsos=:: + Only consider symbols in these dsos. CSV that understands + file://filename entries. This option will affect the percentage of + the overhead column. See --percentage for more info. -S:: --symbols=:: Only consider these symbols. CSV that understands - file://filename entries. + file://filename entries. This option will affect the percentage of + the overhead column. See --percentage for more info. --symbol-filter=:: Only show symbols that match (partially) with this filter. @@ -76,6 +79,15 @@ OPTIONS abort cost. This is the global weight. - local_weight: Local weight version of the weight above. - transaction: Transaction abort flags. + - overhead: Overhead percentage of sample + - overhead_sys: Overhead percentage of sample running in system mode + - overhead_us: Overhead percentage of sample running in user mode + - overhead_guest_sys: Overhead percentage of sample running in system mode + on guest machine + - overhead_guest_us: Overhead percentage of sample running in user mode on + guest machine + - sample: Number of sample + - period: Raw number of event count of sample By default, comm, dso and symbol keys are used. (i.e. --sort comm,dso,symbol) @@ -95,6 +107,32 @@ OPTIONS And default sort keys are changed to comm, dso_from, symbol_from, dso_to and symbol_to, see '--branch-stack'. +-F:: +--fields=:: + Specify output field - multiple keys can be specified in CSV format. + Following fields are available: + overhead, overhead_sys, overhead_us, overhead_children, sample and period. + Also it can contain any sort key(s). + + By default, every sort keys not specified in -F will be appended + automatically. + + If --mem-mode option is used, following sort keys are also available + (incompatible with --branch-stack): + symbol_daddr, dso_daddr, locked, tlb, mem, snoop, dcacheline. + + - symbol_daddr: name of data symbol being executed on at the time of sample + - dso_daddr: name of library or module containing the data being executed + on at the time of sample + - locked: whether the bus was locked at the time of sample + - tlb: type of tlb access for the data at the time of sample + - mem: type of memory access for the data at the time of sample + - snoop: type of snoop (if any) for the data at the time of sample + - dcacheline: the cacheline the data address is on at the time of sample + + And default sort keys are changed to local_weight, mem, sym, dso, + symbol_daddr, dso_daddr, snoop, tlb, locked, see '--mem-mode'. + -p:: --parent=<regex>:: A regex filter to identify parent. The parent is a caller of this @@ -141,6 +179,11 @@ OPTIONS Default: fractal,0.5,callee,function. +--children:: + Accumulate callchain of children to parent entry so that then can + show up in the output. The output will have a new "Children" column + and will be sorted on the data. It requires callchains are recorded. + --max-stack:: Set the stack depth limit when parsing the callchain, anything beyond the specified depth will be ignored. This is a trade-off @@ -233,10 +276,26 @@ OPTIONS Demangle symbol names to human readable form. It's enabled by default, disable with --no-demangle. +--mem-mode:: + Use the data addresses of samples in addition to instruction addresses + to build the histograms. To generate meaningful output, the perf.data + file must have been obtained using perf record -d -W and using a + special event -e cpu/mem-loads/ or -e cpu/mem-stores/. See + 'perf mem' for simpler access. + --percent-limit:: Do not show entries which have an overhead under that percent. (Default: 0). +--percentage:: + Determine how to display the overhead percentage of filtered entries. + Filters can be applied by --comms, --dsos and/or --symbols options and + Zoom operations on the TUI (thread, dso, etc). + + "relative" means it's relative to filtered entries only so that the + sum of shown entries will be always 100%. "absolute" means it retains + the original value before and after the filter is applied. + --header:: Show header information in the perf.data file. This includes various information like hostname, OS and perf version, cpu/mem diff --git a/tools/perf/Documentation/perf-timechart.txt b/tools/perf/Documentation/perf-timechart.txt index bc5990c33dc0..df98d1c82688 100644 --- a/tools/perf/Documentation/perf-timechart.txt +++ b/tools/perf/Documentation/perf-timechart.txt @@ -15,10 +15,20 @@ DESCRIPTION There are two variants of perf timechart: 'perf timechart record <command>' to record the system level events - of an arbitrary workload. + of an arbitrary workload. By default timechart records only scheduler + and CPU events (task switches, running times, CPU power states, etc), + but it's possible to record IO (disk, network) activity using -I argument. 'perf timechart' to turn a trace into a Scalable Vector Graphics file, - that can be viewed with popular SVG viewers such as 'Inkscape'. + that can be viewed with popular SVG viewers such as 'Inkscape'. Depending + on the events in the perf.data file, timechart will contain scheduler/cpu + events or IO events. + + In IO mode, every bar has two charts: upper and lower. + Upper bar shows incoming events (disk reads, ingress network packets). + Lower bar shows outgoing events (disk writes, egress network packets). + There are also poll bars which show how much time application spent + in poll/epoll/select syscalls. TIMECHART OPTIONS ----------------- @@ -43,27 +53,6 @@ TIMECHART OPTIONS --symfs=<directory>:: Look for files with symbols relative to this directory. - -EXAMPLES --------- - -$ perf timechart record git pull - - [ perf record: Woken up 13 times to write data ] - [ perf record: Captured and wrote 4.253 MB perf.data (~185801 samples) ] - -$ perf timechart - - Written 10.2 seconds of trace to output.svg. - -Record system-wide timechart: - - $ perf timechart record - - then generate timechart and highlight 'gcc' tasks: - - $ perf timechart --highlight gcc - -n:: --proc-num:: Print task info for at least given number of tasks. @@ -75,6 +64,19 @@ Record system-wide timechart: duration or tasks with given name. If number is given it's interpreted as number of nanoseconds. If non-numeric string is given it's interpreted as task name. +--io-skip-eagain:: + Don't draw EAGAIN IO events. +--io-min-time=<nsecs>:: + Draw small events as if they lasted min-time. Useful when you need + to see very small and fast IO. It's possible to specify ms or us + suffix to specify time in milliseconds or microseconds. + Default value is 1ms. +--io-merge-dist=<nsecs>:: + Merge events that are merge-dist nanoseconds apart. + Reduces number of figures on the SVG and makes it more render-friendly. + It's possible to specify ms or us suffix to specify time in + milliseconds or microseconds. + Default value is 1us. RECORD OPTIONS -------------- @@ -84,10 +86,41 @@ RECORD OPTIONS -T:: --tasks-only:: Record only tasks-related events +-I:: +--io-only:: + Record only io-related events -g:: --callchain:: Do call-graph (stack chain/backtrace) recording +EXAMPLES +-------- + +$ perf timechart record git pull + + [ perf record: Woken up 13 times to write data ] + [ perf record: Captured and wrote 4.253 MB perf.data (~185801 samples) ] + +$ perf timechart + + Written 10.2 seconds of trace to output.svg. + +Record system-wide timechart: + + $ perf timechart record + + then generate timechart and highlight 'gcc' tasks: + + $ perf timechart --highlight gcc + +Record system-wide IO events: + + $ perf timechart record -I + + then generate timechart: + + $ perf timechart + SEE ALSO -------- linkperf:perf-record[1] diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt index 976b00c6cdb1..180ae02137a5 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -113,7 +113,17 @@ Default is to monitor all CPUS. -s:: --sort:: Sort by key(s): pid, comm, dso, symbol, parent, srcline, weight, - local_weight, abort, in_tx, transaction + local_weight, abort, in_tx, transaction, overhead, sample, period. + Please see description of --sort in the perf-report man page. + +--fields=:: + Specify output field - multiple keys can be specified in CSV format. + Following fields are available: + overhead, overhead_sys, overhead_us, overhead_children, sample and period. + Also it can contain any sort key(s). + + By default, every sort keys not specified in --field will be appended + automatically. -n:: --show-nr-samples:: @@ -123,13 +133,16 @@ Default is to monitor all CPUS. Show a column with the sum of periods. --dsos:: - Only consider symbols in these dsos. + Only consider symbols in these dsos. This option will affect the + percentage of the overhead column. See --percentage for more info. --comms:: - Only consider symbols in these comms. + Only consider symbols in these comms. This option will affect the + percentage of the overhead column. See --percentage for more info. --symbols:: - Only consider these symbols. + Only consider these symbols. This option will affect the + percentage of the overhead column. See --percentage for more info. -M:: --disassembler-style=:: Set disassembler style for objdump. @@ -148,6 +161,12 @@ Default is to monitor all CPUS. Setup and enable call-graph (stack chain/backtrace) recording, implies -g. +--children:: + Accumulate callchain of children to parent entry so that then can + show up in the output. The output will have a new "Children" column + and will be sorted on the data. It requires -g/--call-graph option + enabled. + --max-stack:: Set the stack depth limit when parsing the callchain, anything beyond the specified depth will be ignored. This is a trade-off @@ -165,6 +184,15 @@ Default is to monitor all CPUS. Do not show entries which have an overhead under that percent. (Default: 0). +--percentage:: + Determine how to display the overhead percentage of filtered entries. + Filters can be applied by --comms, --dsos and/or --symbols options and + Zoom operations on the TUI (thread, dso, etc). + + "relative" means it's relative to filtered entries only so that the + sum of shown entries will be always 100%. "absolute" means it retains + the original value before and after the filter is applied. + INTERACTIVE PROMPTING KEYS -------------------------- @@ -200,4 +228,4 @@ Pressing any unmapped key displays a menu, and prompts for input. SEE ALSO -------- -linkperf:perf-stat[1], linkperf:perf-list[1] +linkperf:perf-stat[1], linkperf:perf-list[1], linkperf:perf-report[1] diff --git a/tools/perf/Documentation/perf-trace.txt b/tools/perf/Documentation/perf-trace.txt index fae38d9a44a4..02aac831bdd9 100644 --- a/tools/perf/Documentation/perf-trace.txt +++ b/tools/perf/Documentation/perf-trace.txt @@ -107,6 +107,52 @@ the thread executes on the designated CPUs. Default is to monitor all CPUs. Show tool stats such as number of times fd->pathname was discovered thru hooking the open syscall return + vfs_getname or via reading /proc/pid/fd, etc. +-F=[all|min|maj]:: +--pf=[all|min|maj]:: + Trace pagefaults. Optionally, you can specify whether you want minor, + major or all pagefaults. Default value is maj. + +--syscalls:: + Trace system calls. This options is enabled by default. + +PAGEFAULTS +---------- + +When tracing pagefaults, the format of the trace is as follows: + +<min|maj>fault [<ip.symbol>+<ip.offset>] => <addr.dso@addr.offset> (<map type><addr level>). + +- min/maj indicates whether fault event is minor or major; +- ip.symbol shows symbol for instruction pointer (the code that generated the + fault); if no debug symbols available, perf trace will print raw IP; +- addr.dso shows DSO for the faulted address; +- map type is either 'd' for non-executable maps or 'x' for executable maps; +- addr level is either 'k' for kernel dso or '.' for user dso. + +For symbols resolution you may need to install debugging symbols. + +Please be aware that duration is currently always 0 and doesn't reflect actual +time it took for fault to be handled! + +When --verbose specified, perf trace tries to print all available information +for both IP and fault address in the form of dso@symbol+offset. + +EXAMPLES +-------- + +Trace only major pagefaults: + + $ perf trace --no-syscalls -F + +Trace syscalls, major and minor pagefaults: + + $ perf trace -F all + + 1416.547 ( 0.000 ms): python/20235 majfault [CRYPTO_push_info_+0x0] => /lib/x86_64-linux-gnu/libcrypto.so.1.0.0@0x61be0 (x.) + + As you can see, there was major pagefault in python process, from + CRYPTO_push_info_ routine which faulted somewhere in libcrypto.so. + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-script[1] diff --git a/tools/perf/Documentation/perf.txt b/tools/perf/Documentation/perf.txt index 0eeb247dc7d2..d240bb2e5b22 100644 --- a/tools/perf/Documentation/perf.txt +++ b/tools/perf/Documentation/perf.txt @@ -8,7 +8,15 @@ perf - Performance analysis tools for Linux SYNOPSIS -------- [verse] -'perf' [--version] [--help] COMMAND [ARGS] +'perf' [--version] [--help] [OPTIONS] COMMAND [ARGS] + +OPTIONS +------- +--debug:: + Setup debug variable (just verbose for now) in value + range (0, 10). Use like: + --debug verbose # sets verbose = 1 + --debug verbose=2 # sets verbose = 2 DESCRIPTION ----------- diff --git a/tools/perf/MANIFEST b/tools/perf/MANIFEST index c0c87c87b60f..344c4d3d0a4a 100644 --- a/tools/perf/MANIFEST +++ b/tools/perf/MANIFEST @@ -7,6 +7,8 @@ tools/lib/symbol/kallsyms.h tools/include/asm/bug.h tools/include/linux/compiler.h tools/include/linux/hash.h +tools/include/linux/export.h +tools/include/linux/types.h include/linux/const.h include/linux/perf_event.h include/linux/rbtree.h @@ -35,3 +37,6 @@ arch/x86/include/asm/kvm_host.h arch/x86/include/uapi/asm/svm.h arch/x86/include/uapi/asm/vmx.h arch/x86/include/uapi/asm/kvm.h +arch/x86/include/uapi/asm/kvm_perf.h +arch/s390/include/uapi/asm/sie.h +arch/s390/include/uapi/asm/kvm_perf.h diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf index 895edd32930c..2240974b7745 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -222,12 +222,12 @@ 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/export.h +LIB_H += ../include/linux/export.h LIB_H += util/include/linux/poison.h LIB_H += util/include/linux/rbtree.h LIB_H += util/include/linux/rbtree_augmented.h LIB_H += util/include/linux/string.h -LIB_H += util/include/linux/types.h +LIB_H += ../include/linux/types.h LIB_H += util/include/linux/linkage.h LIB_H += util/include/asm/asm-offsets.h LIB_H += ../include/asm/bug.h @@ -252,7 +252,6 @@ LIB_H += util/event.h LIB_H += util/evsel.h LIB_H += util/evlist.h LIB_H += util/exec_cmd.h -LIB_H += util/types.h LIB_H += util/levenshtein.h LIB_H += util/machine.h LIB_H += util/map.h @@ -296,11 +295,13 @@ LIB_H += util/intlist.h LIB_H += util/perf_regs.h LIB_H += util/unwind.h LIB_H += util/vdso.h +LIB_H += util/tsc.h LIB_H += ui/helpline.h LIB_H += ui/progress.h LIB_H += ui/util.h LIB_H += ui/ui.h LIB_H += util/data.h +LIB_H += util/kvm-stat.h LIB_OBJS += $(OUTPUT)util/abspath.o LIB_OBJS += $(OUTPUT)util/alias.o @@ -374,6 +375,8 @@ LIB_OBJS += $(OUTPUT)util/stat.o LIB_OBJS += $(OUTPUT)util/record.o LIB_OBJS += $(OUTPUT)util/srcline.o LIB_OBJS += $(OUTPUT)util/data.o +LIB_OBJS += $(OUTPUT)util/tsc.o +LIB_OBJS += $(OUTPUT)util/cloexec.o LIB_OBJS += $(OUTPUT)ui/setup.o LIB_OBJS += $(OUTPUT)ui/helpline.o @@ -397,7 +400,11 @@ LIB_OBJS += $(OUTPUT)tests/rdpmc.o LIB_OBJS += $(OUTPUT)tests/evsel-roundtrip-name.o LIB_OBJS += $(OUTPUT)tests/evsel-tp-sched.o LIB_OBJS += $(OUTPUT)tests/pmu.o +LIB_OBJS += $(OUTPUT)tests/hists_common.o LIB_OBJS += $(OUTPUT)tests/hists_link.o +LIB_OBJS += $(OUTPUT)tests/hists_filter.o +LIB_OBJS += $(OUTPUT)tests/hists_output.o +LIB_OBJS += $(OUTPUT)tests/hists_cumulate.o LIB_OBJS += $(OUTPUT)tests/python-use.o LIB_OBJS += $(OUTPUT)tests/bp_signal.o LIB_OBJS += $(OUTPUT)tests/bp_signal_overflow.o @@ -410,10 +417,12 @@ LIB_OBJS += $(OUTPUT)tests/code-reading.o LIB_OBJS += $(OUTPUT)tests/sample-parsing.o LIB_OBJS += $(OUTPUT)tests/parse-no-sample-id-all.o ifndef NO_DWARF_UNWIND -ifeq ($(ARCH),x86) +ifeq ($(ARCH),$(filter $(ARCH),x86 arm)) LIB_OBJS += $(OUTPUT)tests/dwarf-unwind.o endif endif +LIB_OBJS += $(OUTPUT)tests/mmap-thread-lookup.o +LIB_OBJS += $(OUTPUT)tests/thread-mg-share.o BUILTIN_OBJS += $(OUTPUT)builtin-annotate.o BUILTIN_OBJS += $(OUTPUT)builtin-bench.o @@ -784,8 +793,8 @@ help: @echo '' @echo 'Perf install targets:' @echo ' NOTE: documentation build requires asciidoc, xmlto packages to be installed' - @echo ' HINT: use "make prefix=<path> <install target>" to install to a particular' - @echo ' path like make prefix=/usr/local install install-doc' + @echo ' HINT: use "prefix" or "DESTDIR" to install to a particular' + @echo ' path like "make prefix=/usr/local install install-doc"' @echo ' install - install compiled binaries' @echo ' install-doc - install *all* documentation' @echo ' install-man - install manpage documentation' @@ -810,17 +819,20 @@ INSTALL_DOC_TARGETS += quick-install-doc quick-install-man quick-install-html $(DOC_TARGETS): $(QUIET_SUBDIR0)Documentation $(QUIET_SUBDIR1) $(@:doc=all) +TAG_FOLDERS= . ../lib/traceevent ../lib/api ../lib/symbol +TAG_FILES= ../../include/uapi/linux/perf_event.h + TAGS: - $(RM) TAGS - $(FIND) . -name '*.[hcS]' -print | xargs etags -a + $(QUIET_GEN)$(RM) TAGS; \ + $(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs etags -a $(TAG_FILES) tags: - $(RM) tags - $(FIND) . -name '*.[hcS]' -print | xargs ctags -a + $(QUIET_GEN)$(RM) tags; \ + $(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs ctags -a $(TAG_FILES) cscope: - $(RM) cscope* - $(FIND) . -name '*.[hcS]' -print | xargs cscope -b + $(QUIET_GEN)$(RM) cscope*; \ + $(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs cscope -b $(TAG_FILES) ### Detect prefix changes TRACK_CFLAGS = $(subst ','\'',$(CFLAGS)):\ diff --git a/tools/perf/arch/arm/Makefile b/tools/perf/arch/arm/Makefile index 67e9b3d38e89..09d62153d384 100644 --- a/tools/perf/arch/arm/Makefile +++ b/tools/perf/arch/arm/Makefile @@ -5,3 +5,10 @@ endif ifndef NO_LIBUNWIND LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libunwind.o endif +ifndef NO_LIBDW_DWARF_UNWIND +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libdw.o +endif +ifndef NO_DWARF_UNWIND +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/tests/regs_load.o +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/tests/dwarf-unwind.o +endif diff --git a/tools/perf/arch/arm/include/perf_regs.h b/tools/perf/arch/arm/include/perf_regs.h index 2a1cfde66b69..f619c9c5a4bf 100644 --- a/tools/perf/arch/arm/include/perf_regs.h +++ b/tools/perf/arch/arm/include/perf_regs.h @@ -2,10 +2,15 @@ #define ARCH_PERF_REGS_H #include <stdlib.h> -#include "../../util/types.h" +#include <linux/types.h> #include <asm/perf_regs.h> +void perf_regs_load(u64 *regs); + #define PERF_REGS_MASK ((1ULL << PERF_REG_ARM_MAX) - 1) +#define PERF_REGS_MAX PERF_REG_ARM_MAX +#define PERF_SAMPLE_REGS_ABI PERF_SAMPLE_REGS_ABI_32 + #define PERF_REG_IP PERF_REG_ARM_PC #define PERF_REG_SP PERF_REG_ARM_SP diff --git a/tools/perf/arch/arm/tests/dwarf-unwind.c b/tools/perf/arch/arm/tests/dwarf-unwind.c new file mode 100644 index 000000000000..9f870d27cb39 --- /dev/null +++ b/tools/perf/arch/arm/tests/dwarf-unwind.c @@ -0,0 +1,60 @@ +#include <string.h> +#include "perf_regs.h" +#include "thread.h" +#include "map.h" +#include "event.h" +#include "tests/tests.h" + +#define STACK_SIZE 8192 + +static int sample_ustack(struct perf_sample *sample, + struct thread *thread, u64 *regs) +{ + struct stack_dump *stack = &sample->user_stack; + struct map *map; + unsigned long sp; + u64 stack_size, *buf; + + buf = malloc(STACK_SIZE); + if (!buf) { + pr_debug("failed to allocate sample uregs data\n"); + return -1; + } + + sp = (unsigned long) regs[PERF_REG_ARM_SP]; + + map = map_groups__find(thread->mg, MAP__VARIABLE, (u64) sp); + if (!map) { + pr_debug("failed to get stack map\n"); + free(buf); + return -1; + } + + stack_size = map->end - sp; + stack_size = stack_size > STACK_SIZE ? STACK_SIZE : stack_size; + + memcpy(buf, (void *) sp, stack_size); + stack->data = (char *) buf; + stack->size = stack_size; + return 0; +} + +int test__arch_unwind_sample(struct perf_sample *sample, + struct thread *thread) +{ + struct regs_dump *regs = &sample->user_regs; + u64 *buf; + + buf = calloc(1, sizeof(u64) * PERF_REGS_MAX); + if (!buf) { + pr_debug("failed to allocate sample uregs data\n"); + return -1; + } + + perf_regs_load(buf); + regs->abi = PERF_SAMPLE_REGS_ABI; + regs->regs = buf; + regs->mask = PERF_REGS_MASK; + + return sample_ustack(sample, thread, buf); +} diff --git a/tools/perf/arch/arm/tests/regs_load.S b/tools/perf/arch/arm/tests/regs_load.S new file mode 100644 index 000000000000..e09e983946fe --- /dev/null +++ b/tools/perf/arch/arm/tests/regs_load.S @@ -0,0 +1,58 @@ +#include <linux/linkage.h> + +#define R0 0x00 +#define R1 0x08 +#define R2 0x10 +#define R3 0x18 +#define R4 0x20 +#define R5 0x28 +#define R6 0x30 +#define R7 0x38 +#define R8 0x40 +#define R9 0x48 +#define SL 0x50 +#define FP 0x58 +#define IP 0x60 +#define SP 0x68 +#define LR 0x70 +#define PC 0x78 + +/* + * Implementation of void perf_regs_load(u64 *regs); + * + * This functions fills in the 'regs' buffer from the actual registers values, + * in the way the perf built-in unwinding test expects them: + * - the PC at the time at the call to this function. Since this function + * is called using a bl instruction, the PC value is taken from LR. + * The built-in unwinding test then unwinds the call stack from the dwarf + * information in unwind__get_entries. + * + * Notes: + * - the 8 bytes stride in the registers offsets comes from the fact + * that the registers are stored in an u64 array (u64 *regs), + * - the regs buffer needs to be zeroed before the call to this function, + * in this case using a calloc in dwarf-unwind.c. + */ + +.text +.type perf_regs_load,%function +ENTRY(perf_regs_load) + str r0, [r0, #R0] + str r1, [r0, #R1] + str r2, [r0, #R2] + str r3, [r0, #R3] + str r4, [r0, #R4] + str r5, [r0, #R5] + str r6, [r0, #R6] + str r7, [r0, #R7] + str r8, [r0, #R8] + str r9, [r0, #R9] + str sl, [r0, #SL] + str fp, [r0, #FP] + str ip, [r0, #IP] + str sp, [r0, #SP] + str lr, [r0, #LR] + str lr, [r0, #PC] // store pc as lr in order to skip the call + // to this function + mov pc, lr +ENDPROC(perf_regs_load) diff --git a/tools/perf/arch/arm/util/unwind-libdw.c b/tools/perf/arch/arm/util/unwind-libdw.c new file mode 100644 index 000000000000..b4176c60117a --- /dev/null +++ b/tools/perf/arch/arm/util/unwind-libdw.c @@ -0,0 +1,36 @@ +#include <elfutils/libdwfl.h> +#include "../../util/unwind-libdw.h" +#include "../../util/perf_regs.h" + +bool libdw__arch_set_initial_registers(Dwfl_Thread *thread, void *arg) +{ + struct unwind_info *ui = arg; + struct regs_dump *user_regs = &ui->sample->user_regs; + Dwarf_Word dwarf_regs[PERF_REG_ARM_MAX]; + +#define REG(r) ({ \ + Dwarf_Word val = 0; \ + perf_reg_value(&val, user_regs, PERF_REG_ARM_##r); \ + val; \ +}) + + dwarf_regs[0] = REG(R0); + dwarf_regs[1] = REG(R1); + dwarf_regs[2] = REG(R2); + dwarf_regs[3] = REG(R3); + dwarf_regs[4] = REG(R4); + dwarf_regs[5] = REG(R5); + dwarf_regs[6] = REG(R6); + dwarf_regs[7] = REG(R7); + dwarf_regs[8] = REG(R8); + dwarf_regs[9] = REG(R9); + dwarf_regs[10] = REG(R10); + dwarf_regs[11] = REG(FP); + dwarf_regs[12] = REG(IP); + dwarf_regs[13] = REG(SP); + dwarf_regs[14] = REG(LR); + dwarf_regs[15] = REG(PC); + + return dwfl_thread_state_registers(thread, 0, PERF_REG_ARM_MAX, + dwarf_regs); +} diff --git a/tools/perf/arch/arm64/Makefile b/tools/perf/arch/arm64/Makefile new file mode 100644 index 000000000000..67e9b3d38e89 --- /dev/null +++ b/tools/perf/arch/arm64/Makefile @@ -0,0 +1,7 @@ +ifndef NO_DWARF +PERF_HAVE_DWARF_REGS := 1 +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o +endif +ifndef NO_LIBUNWIND +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libunwind.o +endif diff --git a/tools/perf/arch/arm64/include/perf_regs.h b/tools/perf/arch/arm64/include/perf_regs.h new file mode 100644 index 000000000000..e9441b9e2a30 --- /dev/null +++ b/tools/perf/arch/arm64/include/perf_regs.h @@ -0,0 +1,88 @@ +#ifndef ARCH_PERF_REGS_H +#define ARCH_PERF_REGS_H + +#include <stdlib.h> +#include <linux/types.h> +#include <asm/perf_regs.h> + +#define PERF_REGS_MASK ((1ULL << PERF_REG_ARM64_MAX) - 1) +#define PERF_REG_IP PERF_REG_ARM64_PC +#define PERF_REG_SP PERF_REG_ARM64_SP + +static inline const char *perf_reg_name(int id) +{ + switch (id) { + case PERF_REG_ARM64_X0: + return "x0"; + case PERF_REG_ARM64_X1: + return "x1"; + case PERF_REG_ARM64_X2: + return "x2"; + case PERF_REG_ARM64_X3: + return "x3"; + case PERF_REG_ARM64_X4: + return "x4"; + case PERF_REG_ARM64_X5: + return "x5"; + case PERF_REG_ARM64_X6: + return "x6"; + case PERF_REG_ARM64_X7: + return "x7"; + case PERF_REG_ARM64_X8: + return "x8"; + case PERF_REG_ARM64_X9: + return "x9"; + case PERF_REG_ARM64_X10: + return "x10"; + case PERF_REG_ARM64_X11: + return "x11"; + case PERF_REG_ARM64_X12: + return "x12"; + case PERF_REG_ARM64_X13: + return "x13"; + case PERF_REG_ARM64_X14: + return "x14"; + case PERF_REG_ARM64_X15: + return "x15"; + case PERF_REG_ARM64_X16: + return "x16"; + case PERF_REG_ARM64_X17: + return "x17"; + case PERF_REG_ARM64_X18: + return "x18"; + case PERF_REG_ARM64_X19: + return "x19"; + case PERF_REG_ARM64_X20: + return "x20"; + case PERF_REG_ARM64_X21: + return "x21"; + case PERF_REG_ARM64_X22: + return "x22"; + case PERF_REG_ARM64_X23: + return "x23"; + case PERF_REG_ARM64_X24: + return "x24"; + case PERF_REG_ARM64_X25: + return "x25"; + case PERF_REG_ARM64_X26: + return "x26"; + case PERF_REG_ARM64_X27: + return "x27"; + case PERF_REG_ARM64_X28: + return "x28"; + case PERF_REG_ARM64_X29: + return "x29"; + case PERF_REG_ARM64_SP: + return "sp"; + case PERF_REG_ARM64_LR: + return "lr"; + case PERF_REG_ARM64_PC: + return "pc"; + default: + return NULL; + } + + return NULL; +} + +#endif /* ARCH_PERF_REGS_H */ diff --git a/tools/perf/arch/arm64/util/dwarf-regs.c b/tools/perf/arch/arm64/util/dwarf-regs.c new file mode 100644 index 000000000000..d49efeb8172e --- /dev/null +++ b/tools/perf/arch/arm64/util/dwarf-regs.c @@ -0,0 +1,80 @@ +/* + * Mapping of DWARF debug register numbers into register names. + * + * Copyright (C) 2010 Will Deacon, ARM Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <stddef.h> +#include <dwarf-regs.h> + +struct pt_regs_dwarfnum { + const char *name; + unsigned int dwarfnum; +}; + +#define STR(s) #s +#define REG_DWARFNUM_NAME(r, num) {.name = r, .dwarfnum = num} +#define GPR_DWARFNUM_NAME(num) \ + {.name = STR(%x##num), .dwarfnum = num} +#define REG_DWARFNUM_END {.name = NULL, .dwarfnum = 0} + +/* + * Reference: + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0057b/IHI0057B_aadwarf64.pdf + */ +static const struct pt_regs_dwarfnum regdwarfnum_table[] = { + GPR_DWARFNUM_NAME(0), + GPR_DWARFNUM_NAME(1), + GPR_DWARFNUM_NAME(2), + GPR_DWARFNUM_NAME(3), + GPR_DWARFNUM_NAME(4), + GPR_DWARFNUM_NAME(5), + GPR_DWARFNUM_NAME(6), + GPR_DWARFNUM_NAME(7), + GPR_DWARFNUM_NAME(8), + GPR_DWARFNUM_NAME(9), + GPR_DWARFNUM_NAME(10), + GPR_DWARFNUM_NAME(11), + GPR_DWARFNUM_NAME(12), + GPR_DWARFNUM_NAME(13), + GPR_DWARFNUM_NAME(14), + GPR_DWARFNUM_NAME(15), + GPR_DWARFNUM_NAME(16), + GPR_DWARFNUM_NAME(17), + GPR_DWARFNUM_NAME(18), + GPR_DWARFNUM_NAME(19), + GPR_DWARFNUM_NAME(20), + GPR_DWARFNUM_NAME(21), + GPR_DWARFNUM_NAME(22), + GPR_DWARFNUM_NAME(23), + GPR_DWARFNUM_NAME(24), + GPR_DWARFNUM_NAME(25), + GPR_DWARFNUM_NAME(26), + GPR_DWARFNUM_NAME(27), + GPR_DWARFNUM_NAME(28), + GPR_DWARFNUM_NAME(29), + REG_DWARFNUM_NAME("%lr", 30), + REG_DWARFNUM_NAME("%sp", 31), + REG_DWARFNUM_END, +}; + +/** + * get_arch_regstr() - lookup register name from it's DWARF register number + * @n: the DWARF register number + * + * get_arch_regstr() returns the name of the register in struct + * regdwarfnum_table from it's DWARF register number. If the register is not + * found in the table, this returns NULL; + */ +const char *get_arch_regstr(unsigned int n) +{ + const struct pt_regs_dwarfnum *roff; + for (roff = regdwarfnum_table; roff->name != NULL; roff++) + if (roff->dwarfnum == n) + return roff->name; + return NULL; +} diff --git a/tools/perf/arch/arm64/util/unwind-libunwind.c b/tools/perf/arch/arm64/util/unwind-libunwind.c new file mode 100644 index 000000000000..436ee43859dc --- /dev/null +++ b/tools/perf/arch/arm64/util/unwind-libunwind.c @@ -0,0 +1,82 @@ + +#include <errno.h> +#include <libunwind.h> +#include "perf_regs.h" +#include "../../util/unwind.h" + +int libunwind__arch_reg_id(int regnum) +{ + switch (regnum) { + case UNW_AARCH64_X0: + return PERF_REG_ARM64_X0; + case UNW_AARCH64_X1: + return PERF_REG_ARM64_X1; + case UNW_AARCH64_X2: + return PERF_REG_ARM64_X2; + case UNW_AARCH64_X3: + return PERF_REG_ARM64_X3; + case UNW_AARCH64_X4: + return PERF_REG_ARM64_X4; + case UNW_AARCH64_X5: + return PERF_REG_ARM64_X5; + case UNW_AARCH64_X6: + return PERF_REG_ARM64_X6; + case UNW_AARCH64_X7: + return PERF_REG_ARM64_X7; + case UNW_AARCH64_X8: + return PERF_REG_ARM64_X8; + case UNW_AARCH64_X9: + return PERF_REG_ARM64_X9; + case UNW_AARCH64_X10: + return PERF_REG_ARM64_X10; + case UNW_AARCH64_X11: + return PERF_REG_ARM64_X11; + case UNW_AARCH64_X12: + return PERF_REG_ARM64_X12; + case UNW_AARCH64_X13: + return PERF_REG_ARM64_X13; + case UNW_AARCH64_X14: + return PERF_REG_ARM64_X14; + case UNW_AARCH64_X15: + return PERF_REG_ARM64_X15; + case UNW_AARCH64_X16: + return PERF_REG_ARM64_X16; + case UNW_AARCH64_X17: + return PERF_REG_ARM64_X17; + case UNW_AARCH64_X18: + return PERF_REG_ARM64_X18; + case UNW_AARCH64_X19: + return PERF_REG_ARM64_X19; + case UNW_AARCH64_X20: + return PERF_REG_ARM64_X20; + case UNW_AARCH64_X21: + return PERF_REG_ARM64_X21; + case UNW_AARCH64_X22: + return PERF_REG_ARM64_X22; + case UNW_AARCH64_X23: + return PERF_REG_ARM64_X23; + case UNW_AARCH64_X24: + return PERF_REG_ARM64_X24; + case UNW_AARCH64_X25: + return PERF_REG_ARM64_X25; + case UNW_AARCH64_X26: + return PERF_REG_ARM64_X26; + case UNW_AARCH64_X27: + return PERF_REG_ARM64_X27; + case UNW_AARCH64_X28: + return PERF_REG_ARM64_X28; + case UNW_AARCH64_X29: + return PERF_REG_ARM64_X29; + case UNW_AARCH64_X30: + return PERF_REG_ARM64_LR; + case UNW_AARCH64_SP: + return PERF_REG_ARM64_SP; + case UNW_AARCH64_PC: + return PERF_REG_ARM64_PC; + default: + pr_err("unwind: invalid reg id %d\n", regnum); + return -EINVAL; + } + + return -EINVAL; +} diff --git a/tools/perf/arch/powerpc/Makefile b/tools/perf/arch/powerpc/Makefile index 744e629797be..b92219b1900d 100644 --- a/tools/perf/arch/powerpc/Makefile +++ b/tools/perf/arch/powerpc/Makefile @@ -3,3 +3,4 @@ PERF_HAVE_DWARF_REGS := 1 LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o endif LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/skip-callchain-idx.o diff --git a/tools/perf/arch/powerpc/util/header.c b/tools/perf/arch/powerpc/util/header.c index 2f7073d107fd..6c1b8a75db09 100644 --- a/tools/perf/arch/powerpc/util/header.c +++ b/tools/perf/arch/powerpc/util/header.c @@ -5,9 +5,7 @@ #include <string.h> #include "../../util/header.h" - -#define __stringify_1(x) #x -#define __stringify(x) __stringify_1(x) +#include "../../util/util.h" #define mfspr(rn) ({unsigned long rval; \ asm volatile("mfspr %0," __stringify(rn) \ diff --git a/tools/perf/arch/powerpc/util/skip-callchain-idx.c b/tools/perf/arch/powerpc/util/skip-callchain-idx.c new file mode 100644 index 000000000000..a7c23a4b3778 --- /dev/null +++ b/tools/perf/arch/powerpc/util/skip-callchain-idx.c @@ -0,0 +1,266 @@ +/* + * Use DWARF Debug information to skip unnecessary callchain entries. + * + * Copyright (C) 2014 Sukadev Bhattiprolu, IBM Corporation. + * Copyright (C) 2014 Ulrich Weigand, IBM Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include <inttypes.h> +#include <dwarf.h> +#include <elfutils/libdwfl.h> + +#include "util/thread.h" +#include "util/callchain.h" + +/* + * When saving the callchain on Power, the kernel conservatively saves + * excess entries in the callchain. A few of these entries are needed + * in some cases but not others. If the unnecessary entries are not + * ignored, we end up with duplicate arcs in the call-graphs. Use + * DWARF debug information to skip over any unnecessary callchain + * entries. + * + * See function header for arch_adjust_callchain() below for more details. + * + * The libdwfl code in this file is based on code from elfutils + * (libdwfl/argp-std.c, libdwfl/tests/addrcfi.c, etc). + */ +static char *debuginfo_path; + +static const Dwfl_Callbacks offline_callbacks = { + .debuginfo_path = &debuginfo_path, + .find_debuginfo = dwfl_standard_find_debuginfo, + .section_address = dwfl_offline_section_address, +}; + + +/* + * Use the DWARF expression for the Call-frame-address and determine + * if return address is in LR and if a new frame was allocated. + */ +static int check_return_reg(int ra_regno, Dwarf_Frame *frame) +{ + Dwarf_Op ops_mem[2]; + Dwarf_Op dummy; + Dwarf_Op *ops = &dummy; + size_t nops; + int result; + + result = dwarf_frame_register(frame, ra_regno, ops_mem, &ops, &nops); + if (result < 0) { + pr_debug("dwarf_frame_register() %s\n", dwarf_errmsg(-1)); + return -1; + } + + /* + * Check if return address is on the stack. + */ + if (nops != 0 || ops != NULL) + return 0; + + /* + * Return address is in LR. Check if a frame was allocated + * but not-yet used. + */ + result = dwarf_frame_cfa(frame, &ops, &nops); + if (result < 0) { + pr_debug("dwarf_frame_cfa() returns %d, %s\n", result, + dwarf_errmsg(-1)); + return -1; + } + + /* + * If call frame address is in r1, no new frame was allocated. + */ + if (nops == 1 && ops[0].atom == DW_OP_bregx && ops[0].number == 1 && + ops[0].number2 == 0) + return 1; + + /* + * A new frame was allocated but has not yet been used. + */ + return 2; +} + +/* + * Get the DWARF frame from the .eh_frame section. + */ +static Dwarf_Frame *get_eh_frame(Dwfl_Module *mod, Dwarf_Addr pc) +{ + int result; + Dwarf_Addr bias; + Dwarf_CFI *cfi; + Dwarf_Frame *frame; + + cfi = dwfl_module_eh_cfi(mod, &bias); + if (!cfi) { + pr_debug("%s(): no CFI - %s\n", __func__, dwfl_errmsg(-1)); + return NULL; + } + + result = dwarf_cfi_addrframe(cfi, pc, &frame); + if (result) { + pr_debug("%s(): %s\n", __func__, dwfl_errmsg(-1)); + return NULL; + } + + return frame; +} + +/* + * Get the DWARF frame from the .debug_frame section. + */ +static Dwarf_Frame *get_dwarf_frame(Dwfl_Module *mod, Dwarf_Addr pc) +{ + Dwarf_CFI *cfi; + Dwarf_Addr bias; + Dwarf_Frame *frame; + int result; + + cfi = dwfl_module_dwarf_cfi(mod, &bias); + if (!cfi) { + pr_debug("%s(): no CFI - %s\n", __func__, dwfl_errmsg(-1)); + return NULL; + } + + result = dwarf_cfi_addrframe(cfi, pc, &frame); + if (result) { + pr_debug("%s(): %s\n", __func__, dwfl_errmsg(-1)); + return NULL; + } + + return frame; +} + +/* + * Return: + * 0 if return address for the program counter @pc is on stack + * 1 if return address is in LR and no new stack frame was allocated + * 2 if return address is in LR and a new frame was allocated (but not + * yet used) + * -1 in case of errors + */ +static int check_return_addr(const char *exec_file, Dwarf_Addr pc) +{ + int rc = -1; + Dwfl *dwfl; + Dwfl_Module *mod; + Dwarf_Frame *frame; + int ra_regno; + Dwarf_Addr start = pc; + Dwarf_Addr end = pc; + bool signalp; + + dwfl = dwfl_begin(&offline_callbacks); + if (!dwfl) { + pr_debug("dwfl_begin() failed: %s\n", dwarf_errmsg(-1)); + return -1; + } + + if (dwfl_report_offline(dwfl, "", exec_file, -1) == NULL) { + pr_debug("dwfl_report_offline() failed %s\n", dwarf_errmsg(-1)); + goto out; + } + + mod = dwfl_addrmodule(dwfl, pc); + if (!mod) { + pr_debug("dwfl_addrmodule() failed, %s\n", dwarf_errmsg(-1)); + goto out; + } + + /* + * To work with split debug info files (eg: glibc), check both + * .eh_frame and .debug_frame sections of the ELF header. + */ + frame = get_eh_frame(mod, pc); + if (!frame) { + frame = get_dwarf_frame(mod, pc); + if (!frame) + goto out; + } + + ra_regno = dwarf_frame_info(frame, &start, &end, &signalp); + if (ra_regno < 0) { + pr_debug("Return address register unavailable: %s\n", + dwarf_errmsg(-1)); + goto out; + } + + rc = check_return_reg(ra_regno, frame); + +out: + dwfl_end(dwfl); + return rc; +} + +/* + * The callchain saved by the kernel always includes the link register (LR). + * + * 0: PERF_CONTEXT_USER + * 1: Program counter (Next instruction pointer) + * 2: LR value + * 3: Caller's caller + * 4: ... + * + * The value in LR is only needed when it holds a return address. If the + * return address is on the stack, we should ignore the LR value. + * + * Further, when the return address is in the LR, if a new frame was just + * allocated but the LR was not saved into it, then the LR contains the + * caller, slot 4: contains the caller's caller and the contents of slot 3: + * (chain->ips[3]) is undefined and must be ignored. + * + * Use DWARF debug information to determine if any entries need to be skipped. + * + * Return: + * index: of callchain entry that needs to be ignored (if any) + * -1 if no entry needs to be ignored or in case of errors + */ +int arch_skip_callchain_idx(struct machine *machine, struct thread *thread, + struct ip_callchain *chain) +{ + struct addr_location al; + struct dso *dso = NULL; + int rc; + u64 ip; + u64 skip_slot = -1; + + if (chain->nr < 3) + return skip_slot; + + ip = chain->ips[2]; + + thread__find_addr_location(thread, machine, PERF_RECORD_MISC_USER, + MAP__FUNCTION, ip, &al); + + if (al.map) + dso = al.map->dso; + + if (!dso) { + pr_debug("%" PRIx64 " dso is NULL\n", ip); + return skip_slot; + } + + rc = check_return_addr(dso->long_name, ip); + + pr_debug("DSO %s, nr %" PRIx64 ", ip 0x%" PRIx64 "rc %d\n", + dso->long_name, chain->nr, ip, rc); + + if (rc == 0) { + /* + * Return address on stack. Ignore LR value in callchain + */ + skip_slot = 2; + } else if (rc == 2) { + /* + * New frame allocated but return address still in LR. + * Ignore the caller's caller entry in callchain. + */ + skip_slot = 3; + } + return skip_slot; +} diff --git a/tools/perf/arch/s390/Makefile b/tools/perf/arch/s390/Makefile index 15130b50dfe3..798ac7379c5f 100644 --- a/tools/perf/arch/s390/Makefile +++ b/tools/perf/arch/s390/Makefile @@ -2,3 +2,6 @@ ifndef NO_DWARF PERF_HAVE_DWARF_REGS := 1 LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o endif +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o +HAVE_KVM_STAT_SUPPORT := 1 +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/kvm-stat.o diff --git a/tools/perf/arch/s390/util/header.c b/tools/perf/arch/s390/util/header.c new file mode 100644 index 000000000000..9fa6c3e5782c --- /dev/null +++ b/tools/perf/arch/s390/util/header.c @@ -0,0 +1,28 @@ +/* + * Implementation of get_cpuid(). + * + * Copyright 2014 IBM Corp. + * Author(s): Alexander Yarygin <yarygin@linux.vnet.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (version 2 only) + * as published by the Free Software Foundation. + */ + +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> + +#include "../../util/header.h" + +int get_cpuid(char *buffer, size_t sz) +{ + const char *cpuid = "IBM/S390"; + + if (strlen(cpuid) + 1 > sz) + return -1; + + strcpy(buffer, cpuid); + return 0; +} diff --git a/tools/perf/arch/s390/util/kvm-stat.c b/tools/perf/arch/s390/util/kvm-stat.c new file mode 100644 index 000000000000..a5dbc07ec9dc --- /dev/null +++ b/tools/perf/arch/s390/util/kvm-stat.c @@ -0,0 +1,105 @@ +/* + * Arch specific functions for perf kvm stat. + * + * Copyright 2014 IBM Corp. + * Author(s): Alexander Yarygin <yarygin@linux.vnet.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (version 2 only) + * as published by the Free Software Foundation. + */ + +#include "../../util/kvm-stat.h" +#include <asm/kvm_perf.h> + +define_exit_reasons_table(sie_exit_reasons, sie_intercept_code); +define_exit_reasons_table(sie_icpt_insn_codes, icpt_insn_codes); +define_exit_reasons_table(sie_sigp_order_codes, sigp_order_codes); +define_exit_reasons_table(sie_diagnose_codes, diagnose_codes); +define_exit_reasons_table(sie_icpt_prog_codes, icpt_prog_codes); + +static void event_icpt_insn_get_key(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key) +{ + unsigned long insn; + + insn = perf_evsel__intval(evsel, sample, "instruction"); + key->key = icpt_insn_decoder(insn); + key->exit_reasons = sie_icpt_insn_codes; +} + +static void event_sigp_get_key(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key) +{ + key->key = perf_evsel__intval(evsel, sample, "order_code"); + key->exit_reasons = sie_sigp_order_codes; +} + +static void event_diag_get_key(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key) +{ + key->key = perf_evsel__intval(evsel, sample, "code"); + key->exit_reasons = sie_diagnose_codes; +} + +static void event_icpt_prog_get_key(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key) +{ + key->key = perf_evsel__intval(evsel, sample, "code"); + key->exit_reasons = sie_icpt_prog_codes; +} + +static struct child_event_ops child_events[] = { + { .name = "kvm:kvm_s390_intercept_instruction", + .get_key = event_icpt_insn_get_key }, + { .name = "kvm:kvm_s390_handle_sigp", + .get_key = event_sigp_get_key }, + { .name = "kvm:kvm_s390_handle_diag", + .get_key = event_diag_get_key }, + { .name = "kvm:kvm_s390_intercept_prog", + .get_key = event_icpt_prog_get_key }, + { NULL, NULL }, +}; + +static struct kvm_events_ops exit_events = { + .is_begin_event = exit_event_begin, + .is_end_event = exit_event_end, + .child_ops = child_events, + .decode_key = exit_event_decode_key, + .name = "VM-EXIT" +}; + +const char * const kvm_events_tp[] = { + "kvm:kvm_s390_sie_enter", + "kvm:kvm_s390_sie_exit", + "kvm:kvm_s390_intercept_instruction", + "kvm:kvm_s390_handle_sigp", + "kvm:kvm_s390_handle_diag", + "kvm:kvm_s390_intercept_prog", + NULL, +}; + +struct kvm_reg_events_ops kvm_reg_events_ops[] = { + { .name = "vmexit", .ops = &exit_events }, + { NULL, NULL }, +}; + +const char * const kvm_skip_events[] = { + "Wait state", + NULL, +}; + +int cpu_isa_init(struct perf_kvm_stat *kvm, const char *cpuid) +{ + if (strstr(cpuid, "IBM/S390")) { + kvm->exit_reasons = sie_exit_reasons; + kvm->exit_reasons_isa = "SIE"; + } else + return -ENOTSUP; + + return 0; +} diff --git a/tools/perf/arch/x86/Makefile b/tools/perf/arch/x86/Makefile index 1641542e3636..9b21881db52f 100644 --- a/tools/perf/arch/x86/Makefile +++ b/tools/perf/arch/x86/Makefile @@ -15,3 +15,5 @@ endif LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/tsc.o LIB_H += arch/$(ARCH)/util/tsc.h +HAVE_KVM_STAT_SUPPORT := 1 +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/kvm-stat.o diff --git a/tools/perf/arch/x86/include/perf_regs.h b/tools/perf/arch/x86/include/perf_regs.h index fc819ca34a7e..7df517acfef8 100644 --- a/tools/perf/arch/x86/include/perf_regs.h +++ b/tools/perf/arch/x86/include/perf_regs.h @@ -2,7 +2,7 @@ #define ARCH_PERF_REGS_H #include <stdlib.h> -#include "../../util/types.h" +#include <linux/types.h> #include <asm/perf_regs.h> void perf_regs_load(u64 *regs); diff --git a/tools/perf/arch/x86/tests/dwarf-unwind.c b/tools/perf/arch/x86/tests/dwarf-unwind.c index 83bc2385e6d3..d8bbf7ad1681 100644 --- a/tools/perf/arch/x86/tests/dwarf-unwind.c +++ b/tools/perf/arch/x86/tests/dwarf-unwind.c @@ -3,6 +3,7 @@ #include "thread.h" #include "map.h" #include "event.h" +#include "debug.h" #include "tests/tests.h" #define STACK_SIZE 8192 @@ -23,7 +24,7 @@ static int sample_ustack(struct perf_sample *sample, sp = (unsigned long) regs[PERF_REG_X86_SP]; - map = map_groups__find(&thread->mg, MAP__VARIABLE, (u64) sp); + map = map_groups__find(thread->mg, MAP__VARIABLE, (u64) sp); if (!map) { pr_debug("failed to get stack map\n"); free(buf); diff --git a/tools/perf/arch/x86/util/kvm-stat.c b/tools/perf/arch/x86/util/kvm-stat.c new file mode 100644 index 000000000000..14e4e668fad7 --- /dev/null +++ b/tools/perf/arch/x86/util/kvm-stat.c @@ -0,0 +1,156 @@ +#include "../../util/kvm-stat.h" +#include <asm/kvm_perf.h> + +define_exit_reasons_table(vmx_exit_reasons, VMX_EXIT_REASONS); +define_exit_reasons_table(svm_exit_reasons, SVM_EXIT_REASONS); + +static struct kvm_events_ops exit_events = { + .is_begin_event = exit_event_begin, + .is_end_event = exit_event_end, + .decode_key = exit_event_decode_key, + .name = "VM-EXIT" +}; + +/* + * For the mmio events, we treat: + * the time of MMIO write: kvm_mmio(KVM_TRACE_MMIO_WRITE...) -> kvm_entry + * the time of MMIO read: kvm_exit -> kvm_mmio(KVM_TRACE_MMIO_READ...). + */ +static void mmio_event_get_key(struct perf_evsel *evsel, struct perf_sample *sample, + struct event_key *key) +{ + key->key = perf_evsel__intval(evsel, sample, "gpa"); + key->info = perf_evsel__intval(evsel, sample, "type"); +} + +#define KVM_TRACE_MMIO_READ_UNSATISFIED 0 +#define KVM_TRACE_MMIO_READ 1 +#define KVM_TRACE_MMIO_WRITE 2 + +static bool mmio_event_begin(struct perf_evsel *evsel, + struct perf_sample *sample, struct event_key *key) +{ + /* MMIO read begin event in kernel. */ + if (kvm_exit_event(evsel)) + return true; + + /* MMIO write begin event in kernel. */ + if (!strcmp(evsel->name, "kvm:kvm_mmio") && + perf_evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_WRITE) { + mmio_event_get_key(evsel, sample, key); + return true; + } + + return false; +} + +static bool mmio_event_end(struct perf_evsel *evsel, struct perf_sample *sample, + struct event_key *key) +{ + /* MMIO write end event in kernel. */ + if (kvm_entry_event(evsel)) + return true; + + /* MMIO read end event in kernel.*/ + if (!strcmp(evsel->name, "kvm:kvm_mmio") && + perf_evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_READ) { + mmio_event_get_key(evsel, sample, key); + return true; + } + + return false; +} + +static void mmio_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused, + struct event_key *key, + char *decode) +{ + scnprintf(decode, DECODE_STR_LEN, "%#lx:%s", + (unsigned long)key->key, + key->info == KVM_TRACE_MMIO_WRITE ? "W" : "R"); +} + +static struct kvm_events_ops mmio_events = { + .is_begin_event = mmio_event_begin, + .is_end_event = mmio_event_end, + .decode_key = mmio_event_decode_key, + .name = "MMIO Access" +}; + + /* The time of emulation pio access is from kvm_pio to kvm_entry. */ +static void ioport_event_get_key(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key) +{ + key->key = perf_evsel__intval(evsel, sample, "port"); + key->info = perf_evsel__intval(evsel, sample, "rw"); +} + +static bool ioport_event_begin(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key) +{ + if (!strcmp(evsel->name, "kvm:kvm_pio")) { + ioport_event_get_key(evsel, sample, key); + return true; + } + + return false; +} + +static bool ioport_event_end(struct perf_evsel *evsel, + struct perf_sample *sample __maybe_unused, + struct event_key *key __maybe_unused) +{ + return kvm_entry_event(evsel); +} + +static void ioport_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused, + struct event_key *key, + char *decode) +{ + scnprintf(decode, DECODE_STR_LEN, "%#llx:%s", + (unsigned long long)key->key, + key->info ? "POUT" : "PIN"); +} + +static struct kvm_events_ops ioport_events = { + .is_begin_event = ioport_event_begin, + .is_end_event = ioport_event_end, + .decode_key = ioport_event_decode_key, + .name = "IO Port Access" +}; + +const char * const kvm_events_tp[] = { + "kvm:kvm_entry", + "kvm:kvm_exit", + "kvm:kvm_mmio", + "kvm:kvm_pio", + NULL, +}; + +struct kvm_reg_events_ops kvm_reg_events_ops[] = { + { .name = "vmexit", .ops = &exit_events }, + { .name = "mmio", .ops = &mmio_events }, + { .name = "ioport", .ops = &ioport_events }, + { NULL, NULL }, +}; + +const char * const kvm_skip_events[] = { + "HLT", + NULL, +}; + +int cpu_isa_init(struct perf_kvm_stat *kvm, const char *cpuid) +{ + if (strstr(cpuid, "Intel")) { + kvm->exit_reasons = vmx_exit_reasons; + kvm->exit_reasons_isa = "VMX"; + } else if (strstr(cpuid, "AMD")) { + kvm->exit_reasons = svm_exit_reasons; + kvm->exit_reasons_isa = "SVM"; + } else + return -ENOTSUP; + + return 0; +} diff --git a/tools/perf/arch/x86/util/tsc.c b/tools/perf/arch/x86/util/tsc.c index b2519e49424f..fd2868490d00 100644 --- a/tools/perf/arch/x86/util/tsc.c +++ b/tools/perf/arch/x86/util/tsc.c @@ -4,31 +4,11 @@ #include <linux/perf_event.h> #include "../../perf.h" -#include "../../util/types.h" +#include <linux/types.h> #include "../../util/debug.h" +#include "../../util/tsc.h" #include "tsc.h" -u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc) -{ - u64 t, quot, rem; - - t = ns - tc->time_zero; - quot = t / tc->time_mult; - rem = t % tc->time_mult; - return (quot << tc->time_shift) + - (rem << tc->time_shift) / tc->time_mult; -} - -u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc) -{ - u64 quot, rem; - - quot = cyc >> tc->time_shift; - rem = cyc & ((1 << tc->time_shift) - 1); - return tc->time_zero + quot * tc->time_mult + - ((rem * tc->time_mult) >> tc->time_shift); -} - int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, struct perf_tsc_conversion *tc) { @@ -57,3 +37,12 @@ int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, return 0; } + +u64 rdtsc(void) +{ + unsigned int low, high; + + asm volatile("rdtsc" : "=a" (low), "=d" (high)); + + return low | ((u64)high) << 32; +} diff --git a/tools/perf/arch/x86/util/tsc.h b/tools/perf/arch/x86/util/tsc.h index a24dec81c795..2edc4d31065c 100644 --- a/tools/perf/arch/x86/util/tsc.h +++ b/tools/perf/arch/x86/util/tsc.h @@ -1,7 +1,7 @@ #ifndef TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ #define TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ -#include "../../util/types.h" +#include <linux/types.h> struct perf_tsc_conversion { u16 time_shift; @@ -14,7 +14,4 @@ struct perf_event_mmap_page; int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, struct perf_tsc_conversion *tc); -u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc); -u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc); - #endif /* TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ */ diff --git a/tools/perf/arch/x86/util/unwind-libunwind.c b/tools/perf/arch/x86/util/unwind-libunwind.c index 3261f68c6a7c..db25e93d989c 100644 --- a/tools/perf/arch/x86/util/unwind-libunwind.c +++ b/tools/perf/arch/x86/util/unwind-libunwind.c @@ -3,6 +3,7 @@ #include <libunwind.h> #include "perf_regs.h" #include "../../util/unwind.h" +#include "../../util/debug.h" #ifdef HAVE_ARCH_X86_64_SUPPORT int libunwind__arch_reg_id(int regnum) diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h index eba46709b279..3c4dd44d45cb 100644 --- a/tools/perf/bench/bench.h +++ b/tools/perf/bench/bench.h @@ -43,5 +43,6 @@ extern int bench_futex_requeue(int argc, const char **argv, const char *prefix); #define BENCH_FORMAT_UNKNOWN -1 extern int bench_format; +extern unsigned int bench_repeat; #endif diff --git a/tools/perf/bench/futex-requeue.c b/tools/perf/bench/futex-requeue.c index a16255876f1d..732403bfd31a 100644 --- a/tools/perf/bench/futex-requeue.c +++ b/tools/perf/bench/futex-requeue.c @@ -29,13 +29,6 @@ static u_int32_t futex1 = 0, futex2 = 0; */ static unsigned int nrequeue = 1; -/* - * There can be significant variance from run to run, - * the more repeats, the more exact the overall avg and - * the better idea of the futex latency. - */ -static unsigned int repeat = 10; - static pthread_t *worker; static bool done = 0, silent = 0; static pthread_mutex_t thread_lock; @@ -46,7 +39,6 @@ static unsigned int ncpus, threads_starting, nthreads = 0; static const struct option options[] = { OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"), OPT_UINTEGER('q', "nrequeue", &nrequeue, "Specify amount of threads to requeue at once"), - OPT_UINTEGER('r', "repeat", &repeat, "Specify amount of times to repeat the run"), OPT_BOOLEAN( 's', "silent", &silent, "Silent mode: do not display data/details"), OPT_END() }; @@ -146,7 +138,7 @@ int bench_futex_requeue(int argc, const char **argv, pthread_cond_init(&thread_parent, NULL); pthread_cond_init(&thread_worker, NULL); - for (j = 0; j < repeat && !done; j++) { + for (j = 0; j < bench_repeat && !done; j++) { unsigned int nrequeued = 0; struct timeval start, end, runtime; diff --git a/tools/perf/bench/futex-wake.c b/tools/perf/bench/futex-wake.c index d096169b161e..50022cbce87e 100644 --- a/tools/perf/bench/futex-wake.c +++ b/tools/perf/bench/futex-wake.c @@ -30,15 +30,8 @@ static u_int32_t futex1 = 0; */ static unsigned int nwakes = 1; -/* - * There can be significant variance from run to run, - * the more repeats, the more exact the overall avg and - * the better idea of the futex latency. - */ -static unsigned int repeat = 10; - pthread_t *worker; -static bool done = 0, silent = 0; +static bool done = false, silent = false; static pthread_mutex_t thread_lock; static pthread_cond_t thread_parent, thread_worker; static struct stats waketime_stats, wakeup_stats; @@ -47,7 +40,6 @@ static unsigned int ncpus, threads_starting, nthreads = 0; static const struct option options[] = { OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"), OPT_UINTEGER('w', "nwakes", &nwakes, "Specify amount of threads to wake at once"), - OPT_UINTEGER('r', "repeat", &repeat, "Specify amount of times to repeat the run"), OPT_BOOLEAN( 's', "silent", &silent, "Silent mode: do not display data/details"), OPT_END() }; @@ -149,7 +141,7 @@ int bench_futex_wake(int argc, const char **argv, pthread_cond_init(&thread_parent, NULL); pthread_cond_init(&thread_worker, NULL); - for (j = 0; j < repeat && !done; j++) { + for (j = 0; j < bench_repeat && !done; j++) { unsigned int nwoken = 0; struct timeval start, end, runtime; diff --git a/tools/perf/bench/mem-memcpy.c b/tools/perf/bench/mem-memcpy.c index 5ce71d3b72cf..2465141b554b 100644 --- a/tools/perf/bench/mem-memcpy.c +++ b/tools/perf/bench/mem-memcpy.c @@ -10,6 +10,7 @@ #include "../util/util.h" #include "../util/parse-options.h" #include "../util/header.h" +#include "../util/cloexec.h" #include "bench.h" #include "mem-memcpy-arch.h" @@ -83,7 +84,8 @@ static struct perf_event_attr cycle_attr = { static void init_cycle(void) { - cycle_fd = sys_perf_event_open(&cycle_attr, getpid(), -1, -1, 0); + cycle_fd = sys_perf_event_open(&cycle_attr, getpid(), -1, -1, + perf_event_open_cloexec_flag()); if (cycle_fd < 0 && errno == ENOSYS) die("No CONFIG_PERF_EVENTS=y kernel support configured?\n"); @@ -189,6 +191,11 @@ int bench_mem_memcpy(int argc, const char **argv, argc = parse_options(argc, argv, options, bench_mem_memcpy_usage, 0); + if (no_prefault && only_prefault) { + fprintf(stderr, "Invalid options: -o and -n are mutually exclusive\n"); + return 1; + } + if (use_cycle) init_cycle(); diff --git a/tools/perf/bench/mem-memset.c b/tools/perf/bench/mem-memset.c index 9af79d2b18e5..75fc3e65fb2a 100644 --- a/tools/perf/bench/mem-memset.c +++ b/tools/perf/bench/mem-memset.c @@ -10,6 +10,7 @@ #include "../util/util.h" #include "../util/parse-options.h" #include "../util/header.h" +#include "../util/cloexec.h" #include "bench.h" #include "mem-memset-arch.h" @@ -83,7 +84,8 @@ static struct perf_event_attr cycle_attr = { static void init_cycle(void) { - cycle_fd = sys_perf_event_open(&cycle_attr, getpid(), -1, -1, 0); + cycle_fd = sys_perf_event_open(&cycle_attr, getpid(), -1, -1, + perf_event_open_cloexec_flag()); if (cycle_fd < 0 && errno == ENOSYS) die("No CONFIG_PERF_EVENTS=y kernel support configured?\n"); @@ -181,6 +183,11 @@ int bench_mem_memset(int argc, const char **argv, argc = parse_options(argc, argv, options, bench_mem_memset_usage, 0); + if (no_prefault && only_prefault) { + fprintf(stderr, "Invalid options: -o and -n are mutually exclusive\n"); + return 1; + } + if (use_cycle) init_cycle(); diff --git a/tools/perf/bench/sched-messaging.c b/tools/perf/bench/sched-messaging.c index cc1190a0849b..52a56599a543 100644 --- a/tools/perf/bench/sched-messaging.c +++ b/tools/perf/bench/sched-messaging.c @@ -28,6 +28,7 @@ #include <sys/time.h> #include <sys/poll.h> #include <limits.h> +#include <err.h> #define DATASIZE 100 @@ -50,12 +51,6 @@ struct receiver_context { int wakefd; }; -static void barf(const char *msg) -{ - fprintf(stderr, "%s (error: %s)\n", msg, strerror(errno)); - exit(1); -} - static void fdpair(int fds[2]) { if (use_pipes) { @@ -66,7 +61,7 @@ static void fdpair(int fds[2]) return; } - barf(use_pipes ? "pipe()" : "socketpair()"); + err(EXIT_FAILURE, use_pipes ? "pipe()" : "socketpair()"); } /* Block until we're ready to go */ @@ -77,11 +72,11 @@ static void ready(int ready_out, int wakefd) /* Tell them we're ready. */ if (write(ready_out, &dummy, 1) != 1) - barf("CLIENT: ready write"); + err(EXIT_FAILURE, "CLIENT: ready write"); /* Wait for "GO" signal */ if (poll(&pollfd, 1, -1) != 1) - barf("poll"); + err(EXIT_FAILURE, "poll"); } /* Sender sprays loops messages down each file descriptor */ @@ -101,7 +96,7 @@ again: ret = write(ctx->out_fds[j], data + done, sizeof(data)-done); if (ret < 0) - barf("SENDER: write"); + err(EXIT_FAILURE, "SENDER: write"); done += ret; if (done < DATASIZE) goto again; @@ -131,7 +126,7 @@ static void *receiver(struct receiver_context* ctx) again: ret = read(ctx->in_fds[0], data + done, DATASIZE - done); if (ret < 0) - barf("SERVER: read"); + err(EXIT_FAILURE, "SERVER: read"); done += ret; if (done < DATASIZE) goto again; @@ -144,14 +139,14 @@ static pthread_t create_worker(void *ctx, void *(*func)(void *)) { pthread_attr_t attr; pthread_t childid; - int err; + int ret; if (!thread_mode) { /* process mode */ /* Fork the receiver. */ switch (fork()) { case -1: - barf("fork()"); + err(EXIT_FAILURE, "fork()"); break; case 0: (*func) (ctx); @@ -165,19 +160,17 @@ static pthread_t create_worker(void *ctx, void *(*func)(void *)) } if (pthread_attr_init(&attr) != 0) - barf("pthread_attr_init:"); + err(EXIT_FAILURE, "pthread_attr_init:"); #ifndef __ia64__ if (pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN) != 0) - barf("pthread_attr_setstacksize"); + err(EXIT_FAILURE, "pthread_attr_setstacksize"); #endif - err = pthread_create(&childid, &attr, func, ctx); - if (err != 0) { - fprintf(stderr, "pthread_create failed: %s (%d)\n", - strerror(err), err); - exit(-1); - } + ret = pthread_create(&childid, &attr, func, ctx); + if (ret != 0) + err(EXIT_FAILURE, "pthread_create failed"); + return childid; } @@ -207,14 +200,14 @@ static unsigned int group(pthread_t *pth, + num_fds * sizeof(int)); if (!snd_ctx) - barf("malloc()"); + err(EXIT_FAILURE, "malloc()"); for (i = 0; i < num_fds; i++) { int fds[2]; struct receiver_context *ctx = malloc(sizeof(*ctx)); if (!ctx) - barf("malloc()"); + err(EXIT_FAILURE, "malloc()"); /* Create the pipe between client and server */ @@ -281,7 +274,7 @@ int bench_sched_messaging(int argc, const char **argv, pth_tab = malloc(num_fds * 2 * num_groups * sizeof(pthread_t)); if (!pth_tab) - barf("main:malloc()"); + err(EXIT_FAILURE, "main:malloc()"); fdpair(readyfds); fdpair(wakefds); @@ -294,13 +287,13 @@ int bench_sched_messaging(int argc, const char **argv, /* Wait for everyone to be ready */ for (i = 0; i < total_children; i++) if (read(readyfds[0], &dummy, 1) != 1) - barf("Reading for readyfds"); + err(EXIT_FAILURE, "Reading for readyfds"); gettimeofday(&start, NULL); /* Kick them off */ if (write(wakefds[1], &dummy, 1) != 1) - barf("Writing to start them"); + err(EXIT_FAILURE, "Writing to start them"); /* Reap them all */ for (i = 0; i < total_children; i++) @@ -332,5 +325,7 @@ int bench_sched_messaging(int argc, const char **argv, break; } + free(pth_tab); + return 0; } diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index 0da603b79b61..1ec429fef2be 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -46,7 +46,7 @@ struct perf_annotate { }; static int perf_evsel__add_sample(struct perf_evsel *evsel, - struct perf_sample *sample, + struct perf_sample *sample __maybe_unused, struct addr_location *al, struct perf_annotate *ann) { @@ -65,13 +65,13 @@ static int perf_evsel__add_sample(struct perf_evsel *evsel, return 0; } - he = __hists__add_entry(&evsel->hists, al, NULL, NULL, NULL, 1, 1, 0); + he = __hists__add_entry(&evsel->hists, al, NULL, NULL, NULL, 1, 1, 0, + true); if (he == NULL) return -ENOMEM; ret = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); - evsel->hists.stats.total_period += sample->period; - hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); + hists__inc_nr_samples(&evsel->hists, true); return ret; } diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c index 1e6e77710545..b9a56fa83330 100644 --- a/tools/perf/builtin-bench.c +++ b/tools/perf/builtin-bench.c @@ -104,9 +104,11 @@ static const char *bench_format_str; /* Output/formatting style, exported to benchmark modules: */ int bench_format = BENCH_FORMAT_DEFAULT; +unsigned int bench_repeat = 10; /* default number of times to repeat the run */ static const struct option bench_options[] = { OPT_STRING('f', "format", &bench_format_str, "default", "Specify format style"), + OPT_UINTEGER('r', "repeat", &bench_repeat, "Specify amount of times to repeat the run"), OPT_END() }; @@ -226,6 +228,11 @@ int cmd_bench(int argc, const char **argv, const char *prefix __maybe_unused) goto end; } + if (bench_repeat == 0) { + printf("Invalid repeat option: Must specify a positive value\n"); + goto end; + } + if (argc < 1) { print_usage(); goto end; diff --git a/tools/perf/builtin-buildid-cache.c b/tools/perf/builtin-buildid-cache.c index b22dbb16f877..2a2c78f80876 100644 --- a/tools/perf/builtin-buildid-cache.c +++ b/tools/perf/builtin-buildid-cache.c @@ -125,7 +125,8 @@ static int build_id_cache__kcore_existing(const char *from_dir, char *to_dir, return ret; } -static int build_id_cache__add_kcore(const char *filename, const char *debugdir) +static int build_id_cache__add_kcore(const char *filename, const char *debugdir, + bool force) { char dir[32], sbuildid[BUILD_ID_SIZE * 2 + 1]; char from_dir[PATH_MAX], to_dir[PATH_MAX]; @@ -144,7 +145,8 @@ static int build_id_cache__add_kcore(const char *filename, const char *debugdir) scnprintf(to_dir, sizeof(to_dir), "%s/[kernel.kcore]/%s", debugdir, sbuildid); - if (!build_id_cache__kcore_existing(from_dir, to_dir, sizeof(to_dir))) { + if (!force && + !build_id_cache__kcore_existing(from_dir, to_dir, sizeof(to_dir))) { pr_debug("same kcore found in %s\n", to_dir); return 0; } @@ -389,7 +391,7 @@ int cmd_buildid_cache(int argc, const char **argv, } if (kcore_filename && - build_id_cache__add_kcore(kcore_filename, debugdir)) + build_id_cache__add_kcore(kcore_filename, debugdir, force)) pr_warning("Couldn't add %s\n", kcore_filename); return ret; diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index 204fffe22532..9a5a035cb426 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -60,7 +60,6 @@ static int data__files_cnt; #define data__for_each_file(i, d) data__for_each_file_start(i, d, 0) #define data__for_each_file_new(i, d) data__for_each_file_start(i, d, 1) -static char diff__default_sort_order[] = "dso,symbol"; static bool force; static bool show_period; static bool show_formula; @@ -220,7 +219,8 @@ static int setup_compute(const struct option *opt, const char *str, static double period_percent(struct hist_entry *he, u64 period) { - u64 total = he->hists->stats.total_period; + u64 total = hists__total_period(he->hists); + return (period * 100.0) / total; } @@ -259,11 +259,18 @@ static s64 compute_wdiff(struct hist_entry *he, struct hist_entry *pair) static int formula_delta(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size) { + u64 he_total = he->hists->stats.total_period; + u64 pair_total = pair->hists->stats.total_period; + + if (symbol_conf.filter_relative) { + he_total = he->hists->stats.total_non_filtered_period; + pair_total = pair->hists->stats.total_non_filtered_period; + } return scnprintf(buf, size, "(%" PRIu64 " * 100 / %" PRIu64 ") - " "(%" PRIu64 " * 100 / %" PRIu64 ")", - pair->stat.period, pair->hists->stats.total_period, - he->stat.period, he->hists->stats.total_period); + pair->stat.period, pair_total, + he->stat.period, he_total); } static int formula_ratio(struct hist_entry *he, struct hist_entry *pair, @@ -308,7 +315,7 @@ static int hists__add_entry(struct hists *hists, u64 weight, u64 transaction) { if (__hists__add_entry(hists, al, NULL, NULL, NULL, period, weight, - transaction) != NULL) + transaction, true) != NULL) return 0; return -ENOMEM; } @@ -327,16 +334,22 @@ static int diff__process_sample_event(struct perf_tool *tool __maybe_unused, return -1; } - if (al.filtered) - return 0; - if (hists__add_entry(&evsel->hists, &al, sample->period, sample->weight, sample->transaction)) { pr_warning("problem incrementing symbol period, skipping event\n"); return -1; } + /* + * The total_period is updated here before going to the output + * tree since normally only the baseline hists will call + * hists__output_resort() and precompute needs the total + * period in order to sort entries by percentage delta. + */ evsel->hists.stats.total_period += sample->period; + if (!al.filtered) + evsel->hists.stats.total_non_filtered_period += sample->period; + return 0; } @@ -564,8 +577,7 @@ static void hists__compute_resort(struct hists *hists) hists->entries = RB_ROOT; next = rb_first(root); - hists->nr_entries = 0; - hists->stats.total_period = 0; + hists__reset_stats(hists); hists__reset_col_len(hists); while (next != NULL) { @@ -575,7 +587,10 @@ static void hists__compute_resort(struct hists *hists) next = rb_next(&he->rb_node_in); insert_hist_entry_by_compute(&hists->entries, he, compute); - hists__inc_nr_entries(hists, he); + hists__inc_stats(hists, he); + + if (!he->filtered) + hists__calc_col_len(hists, he); } } @@ -725,20 +740,24 @@ static const struct option options[] = { OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]", "only consider these symbols"), 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, cpu, srcline, ..." + " Please refer the man page for the complete list."), OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator", "separator for columns, no spaces will be added between " "columns '.' is reserved."), OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", "Look for files with symbols relative to this directory"), OPT_UINTEGER('o', "order", &sort_compute, "Specify compute sorting."), + OPT_CALLBACK(0, "percentage", NULL, "relative|absolute", + "How to display percentage of filtered entries", parse_filter_percentage), OPT_END() }; static double baseline_percent(struct hist_entry *he) { - struct hists *hists = he->hists; - return 100.0 * he->stat.period / hists->stats.total_period; + u64 total = hists__total_period(he->hists); + + return 100.0 * he->stat.period / total; } static int hpp__color_baseline(struct perf_hpp_fmt *fmt, @@ -1120,7 +1139,8 @@ static int data_init(int argc, const char **argv) int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused) { - sort_order = diff__default_sort_order; + perf_config(perf_default_config, NULL); + argc = parse_options(argc, argv, options, diff_usage, 0); if (symbol__init() < 0) @@ -1131,6 +1151,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused) ui_init(); + sort__mode = SORT_MODE__DIFF; + if (setup_sorting() < 0) usage_with_options(diff_usage, options); diff --git a/tools/perf/builtin-evlist.c b/tools/perf/builtin-evlist.c index c99e0de7e54a..66e12f55c052 100644 --- a/tools/perf/builtin-evlist.c +++ b/tools/perf/builtin-evlist.c @@ -15,6 +15,7 @@ #include "util/parse-options.h" #include "util/session.h" #include "util/data.h" +#include "util/debug.h" static int __cmd_evlist(const char *file_name, struct perf_attr_details *details) { diff --git a/tools/perf/builtin-help.c b/tools/perf/builtin-help.c index 178b88ae3d2f..0384d930480b 100644 --- a/tools/perf/builtin-help.c +++ b/tools/perf/builtin-help.c @@ -11,6 +11,7 @@ #include "util/parse-options.h" #include "util/run-command.h" #include "util/help.h" +#include "util/debug.h" static struct man_viewer_list { struct man_viewer_list *next; diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 3a7387551369..9a02807387d6 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -72,7 +72,7 @@ static int perf_event__repipe_attr(struct perf_tool *tool, if (ret) return ret; - if (&inject->output.is_pipe) + if (!inject->output.is_pipe) return 0; return perf_event__repipe_synth(tool, event); @@ -209,7 +209,7 @@ static int perf_event__inject_buildid(struct perf_tool *tool, cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - thread = machine__findnew_thread(machine, sample->pid, sample->pid); + thread = machine__findnew_thread(machine, sample->pid, sample->tid); if (thread == NULL) { pr_err("problem processing %d event, skipping it.\n", event->header.type); @@ -389,6 +389,9 @@ static int __cmd_inject(struct perf_inject *inject) ret = perf_session__process_events(session, &inject->tool); if (!file_out->is_pipe) { + if (inject->build_ids) + perf_header__set_feat(&session->header, + HEADER_BUILD_ID); session->header.data_size = inject->bytes_written; perf_session__write_header(session, session->evlist, file_out->fd, true); } @@ -436,6 +439,8 @@ int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused) "where and how long tasks slept"), OPT_INCR('v', "verbose", &verbose, "be more verbose (show build ids, etc)"), + OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, "file", + "kallsyms pathname"), OPT_END() }; const char * const inject_usage[] = { diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index 929462aa4943..bef3376bfaf3 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c @@ -14,6 +14,7 @@ #include "util/parse-options.h" #include "util/trace-event.h" #include "util/data.h" +#include "util/cpumap.h" #include "util/debug.h" @@ -31,9 +32,6 @@ static int caller_lines = -1; static bool raw_ip; -static int *cpunode_map; -static int max_cpu_num; - struct alloc_stat { u64 call_site; u64 ptr; @@ -55,76 +53,6 @@ static struct rb_root root_caller_sorted; static unsigned long total_requested, total_allocated; static unsigned long nr_allocs, nr_cross_allocs; -#define PATH_SYS_NODE "/sys/devices/system/node" - -static int init_cpunode_map(void) -{ - FILE *fp; - int i, err = -1; - - fp = fopen("/sys/devices/system/cpu/kernel_max", "r"); - if (!fp) { - max_cpu_num = 4096; - return 0; - } - - if (fscanf(fp, "%d", &max_cpu_num) < 1) { - pr_err("Failed to read 'kernel_max' from sysfs"); - goto out_close; - } - - max_cpu_num++; - - cpunode_map = calloc(max_cpu_num, sizeof(int)); - if (!cpunode_map) { - pr_err("%s: calloc failed\n", __func__); - goto out_close; - } - - for (i = 0; i < max_cpu_num; i++) - cpunode_map[i] = -1; - - err = 0; -out_close: - fclose(fp); - return err; -} - -static int setup_cpunode_map(void) -{ - struct dirent *dent1, *dent2; - DIR *dir1, *dir2; - unsigned int cpu, mem; - char buf[PATH_MAX]; - - if (init_cpunode_map()) - return -1; - - dir1 = opendir(PATH_SYS_NODE); - if (!dir1) - return 0; - - while ((dent1 = readdir(dir1)) != NULL) { - if (dent1->d_type != DT_DIR || - sscanf(dent1->d_name, "node%u", &mem) < 1) - continue; - - snprintf(buf, PATH_MAX, "%s/%s", PATH_SYS_NODE, dent1->d_name); - dir2 = opendir(buf); - if (!dir2) - continue; - while ((dent2 = readdir(dir2)) != NULL) { - if (dent2->d_type != DT_LNK || - sscanf(dent2->d_name, "cpu%u", &cpu) < 1) - continue; - cpunode_map[cpu] = mem; - } - closedir(dir2); - } - closedir(dir1); - return 0; -} - static int insert_alloc_stat(unsigned long call_site, unsigned long ptr, int bytes_req, int bytes_alloc, int cpu) { @@ -235,7 +163,7 @@ static int perf_evsel__process_alloc_node_event(struct perf_evsel *evsel, int ret = perf_evsel__process_alloc_event(evsel, sample); if (!ret) { - int node1 = cpunode_map[sample->cpu], + int node1 = cpu__get_node(sample->cpu), node2 = perf_evsel__intval(evsel, sample, "node"); if (node1 != node2) @@ -307,7 +235,7 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, struct machine *machine) { struct thread *thread = machine__findnew_thread(machine, sample->pid, - sample->pid); + sample->tid); if (thread == NULL) { pr_debug("problem processing %d event, skipping it.\n", @@ -756,11 +684,13 @@ int cmd_kmem(int argc, const char **argv, const char *prefix __maybe_unused) OPT_BOOLEAN(0, "raw-ip", &raw_ip, "show raw ip instead of symbol"), OPT_END() }; - const char * const kmem_usage[] = { - "perf kmem [<options>] {record|stat}", + const char *const kmem_subcommands[] = { "record", "stat", NULL }; + const char *kmem_usage[] = { + NULL, NULL }; - argc = parse_options(argc, argv, kmem_options, kmem_usage, 0); + argc = parse_options_subcommand(argc, argv, kmem_options, + kmem_subcommands, kmem_usage, 0); if (!argc) usage_with_options(kmem_usage, kmem_options); @@ -770,7 +700,7 @@ int cmd_kmem(int argc, const char **argv, const char *prefix __maybe_unused) if (!strncmp(argv[0], "rec", 3)) { return __cmd_record(argc, argv); } else if (!strcmp(argv[0], "stat")) { - if (setup_cpunode_map()) + if (cpu__setup_cpunode_map()) return -1; if (list_empty(&caller_sort)) diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c index 0f1e5a2f6ad7..43367eb00510 100644 --- a/tools/perf/builtin-kvm.c +++ b/tools/perf/builtin-kvm.c @@ -29,114 +29,25 @@ #include <pthread.h> #include <math.h> -#if defined(__i386__) || defined(__x86_64__) -#include <asm/svm.h> -#include <asm/vmx.h> -#include <asm/kvm.h> - -struct event_key { - #define INVALID_KEY (~0ULL) - u64 key; - int info; -}; - -struct kvm_event_stats { - u64 time; - struct stats stats; -}; - -struct kvm_event { - struct list_head hash_entry; - struct rb_node rb; - - struct event_key key; - - struct kvm_event_stats total; - - #define DEFAULT_VCPU_NUM 8 - int max_vcpu; - struct kvm_event_stats *vcpu; -}; - -typedef int (*key_cmp_fun)(struct kvm_event*, struct kvm_event*, int); - -struct kvm_event_key { - const char *name; - key_cmp_fun key; -}; - - -struct perf_kvm_stat; - -struct kvm_events_ops { - bool (*is_begin_event)(struct perf_evsel *evsel, - struct perf_sample *sample, - struct event_key *key); - bool (*is_end_event)(struct perf_evsel *evsel, - struct perf_sample *sample, struct event_key *key); - void (*decode_key)(struct perf_kvm_stat *kvm, struct event_key *key, - char decode[20]); - const char *name; -}; - -struct exit_reasons_table { - unsigned long exit_code; - const char *reason; -}; +#ifdef HAVE_KVM_STAT_SUPPORT +#include <asm/kvm_perf.h> +#include "util/kvm-stat.h" -#define EVENTS_BITS 12 -#define EVENTS_CACHE_SIZE (1UL << EVENTS_BITS) - -struct perf_kvm_stat { - struct perf_tool tool; - struct record_opts opts; - struct perf_evlist *evlist; - struct perf_session *session; - - const char *file_name; - const char *report_event; - const char *sort_key; - int trace_vcpu; - - struct exit_reasons_table *exit_reasons; - int exit_reasons_size; - const char *exit_reasons_isa; - - struct kvm_events_ops *events_ops; - key_cmp_fun compare; - struct list_head kvm_events_cache[EVENTS_CACHE_SIZE]; - - u64 total_time; - u64 total_count; - u64 lost_events; - u64 duration; - - const char *pid_str; - struct intlist *pid_list; - - struct rb_root result; - - int timerfd; - unsigned int display_time; - bool live; -}; - - -static void exit_event_get_key(struct perf_evsel *evsel, - struct perf_sample *sample, - struct event_key *key) +void exit_event_get_key(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key) { key->info = 0; - key->key = perf_evsel__intval(evsel, sample, "exit_reason"); + key->key = perf_evsel__intval(evsel, sample, KVM_EXIT_REASON); } -static bool kvm_exit_event(struct perf_evsel *evsel) +bool kvm_exit_event(struct perf_evsel *evsel) { - return !strcmp(evsel->name, "kvm:kvm_exit"); + return !strcmp(evsel->name, KVM_EXIT_TRACE); } -static bool exit_event_begin(struct perf_evsel *evsel, - struct perf_sample *sample, struct event_key *key) +bool exit_event_begin(struct perf_evsel *evsel, + struct perf_sample *sample, struct event_key *key) { if (kvm_exit_event(evsel)) { exit_event_get_key(evsel, sample, key); @@ -146,32 +57,23 @@ static bool exit_event_begin(struct perf_evsel *evsel, return false; } -static bool kvm_entry_event(struct perf_evsel *evsel) +bool kvm_entry_event(struct perf_evsel *evsel) { - return !strcmp(evsel->name, "kvm:kvm_entry"); + return !strcmp(evsel->name, KVM_ENTRY_TRACE); } -static bool exit_event_end(struct perf_evsel *evsel, - struct perf_sample *sample __maybe_unused, - struct event_key *key __maybe_unused) +bool exit_event_end(struct perf_evsel *evsel, + struct perf_sample *sample __maybe_unused, + struct event_key *key __maybe_unused) { return kvm_entry_event(evsel); } -static struct exit_reasons_table vmx_exit_reasons[] = { - VMX_EXIT_REASONS -}; - -static struct exit_reasons_table svm_exit_reasons[] = { - SVM_EXIT_REASONS -}; - -static const char *get_exit_reason(struct perf_kvm_stat *kvm, u64 exit_code) +static const char *get_exit_reason(struct perf_kvm_stat *kvm, + struct exit_reasons_table *tbl, + u64 exit_code) { - int i = kvm->exit_reasons_size; - struct exit_reasons_table *tbl = kvm->exit_reasons; - - while (i--) { + while (tbl->reason != NULL) { if (tbl->exit_code == exit_code) return tbl->reason; tbl++; @@ -182,148 +84,30 @@ static const char *get_exit_reason(struct perf_kvm_stat *kvm, u64 exit_code) return "UNKNOWN"; } -static void exit_event_decode_key(struct perf_kvm_stat *kvm, - struct event_key *key, - char decode[20]) +void exit_event_decode_key(struct perf_kvm_stat *kvm, + struct event_key *key, + char *decode) { - const char *exit_reason = get_exit_reason(kvm, key->key); + const char *exit_reason = get_exit_reason(kvm, key->exit_reasons, + key->key); - scnprintf(decode, 20, "%s", exit_reason); + scnprintf(decode, DECODE_STR_LEN, "%s", exit_reason); } -static struct kvm_events_ops exit_events = { - .is_begin_event = exit_event_begin, - .is_end_event = exit_event_end, - .decode_key = exit_event_decode_key, - .name = "VM-EXIT" -}; - -/* - * For the mmio events, we treat: - * the time of MMIO write: kvm_mmio(KVM_TRACE_MMIO_WRITE...) -> kvm_entry - * the time of MMIO read: kvm_exit -> kvm_mmio(KVM_TRACE_MMIO_READ...). - */ -static void mmio_event_get_key(struct perf_evsel *evsel, struct perf_sample *sample, - struct event_key *key) -{ - key->key = perf_evsel__intval(evsel, sample, "gpa"); - key->info = perf_evsel__intval(evsel, sample, "type"); -} - -#define KVM_TRACE_MMIO_READ_UNSATISFIED 0 -#define KVM_TRACE_MMIO_READ 1 -#define KVM_TRACE_MMIO_WRITE 2 - -static bool mmio_event_begin(struct perf_evsel *evsel, - struct perf_sample *sample, struct event_key *key) -{ - /* MMIO read begin event in kernel. */ - if (kvm_exit_event(evsel)) - return true; - - /* MMIO write begin event in kernel. */ - if (!strcmp(evsel->name, "kvm:kvm_mmio") && - perf_evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_WRITE) { - mmio_event_get_key(evsel, sample, key); - return true; - } - - return false; -} - -static bool mmio_event_end(struct perf_evsel *evsel, struct perf_sample *sample, - struct event_key *key) -{ - /* MMIO write end event in kernel. */ - if (kvm_entry_event(evsel)) - return true; - - /* MMIO read end event in kernel.*/ - if (!strcmp(evsel->name, "kvm:kvm_mmio") && - perf_evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_READ) { - mmio_event_get_key(evsel, sample, key); - return true; - } - - return false; -} - -static void mmio_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused, - struct event_key *key, - char decode[20]) -{ - scnprintf(decode, 20, "%#lx:%s", (unsigned long)key->key, - key->info == KVM_TRACE_MMIO_WRITE ? "W" : "R"); -} - -static struct kvm_events_ops mmio_events = { - .is_begin_event = mmio_event_begin, - .is_end_event = mmio_event_end, - .decode_key = mmio_event_decode_key, - .name = "MMIO Access" -}; - - /* The time of emulation pio access is from kvm_pio to kvm_entry. */ -static void ioport_event_get_key(struct perf_evsel *evsel, - struct perf_sample *sample, - struct event_key *key) +static bool register_kvm_events_ops(struct perf_kvm_stat *kvm) { - key->key = perf_evsel__intval(evsel, sample, "port"); - key->info = perf_evsel__intval(evsel, sample, "rw"); -} + struct kvm_reg_events_ops *events_ops = kvm_reg_events_ops; -static bool ioport_event_begin(struct perf_evsel *evsel, - struct perf_sample *sample, - struct event_key *key) -{ - if (!strcmp(evsel->name, "kvm:kvm_pio")) { - ioport_event_get_key(evsel, sample, key); - return true; + for (events_ops = kvm_reg_events_ops; events_ops->name; events_ops++) { + if (!strcmp(events_ops->name, kvm->report_event)) { + kvm->events_ops = events_ops->ops; + return true; + } } return false; } -static bool ioport_event_end(struct perf_evsel *evsel, - struct perf_sample *sample __maybe_unused, - struct event_key *key __maybe_unused) -{ - return kvm_entry_event(evsel); -} - -static void ioport_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused, - struct event_key *key, - char decode[20]) -{ - scnprintf(decode, 20, "%#llx:%s", (unsigned long long)key->key, - key->info ? "POUT" : "PIN"); -} - -static struct kvm_events_ops ioport_events = { - .is_begin_event = ioport_event_begin, - .is_end_event = ioport_event_end, - .decode_key = ioport_event_decode_key, - .name = "IO Port Access" -}; - -static bool register_kvm_events_ops(struct perf_kvm_stat *kvm) -{ - bool ret = true; - - if (!strcmp(kvm->report_event, "vmexit")) - kvm->events_ops = &exit_events; - else if (!strcmp(kvm->report_event, "mmio")) - kvm->events_ops = &mmio_events; - else if (!strcmp(kvm->report_event, "ioport")) - kvm->events_ops = &ioport_events; - else { - pr_err("Unknown report event:%s\n", kvm->report_event); - ret = false; - } - - return ret; -} - struct vcpu_event_record { int vcpu_id; u64 start_time; @@ -477,6 +261,54 @@ static bool update_kvm_event(struct kvm_event *event, int vcpu_id, return true; } +static bool is_child_event(struct perf_kvm_stat *kvm, + struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key) +{ + struct child_event_ops *child_ops; + + child_ops = kvm->events_ops->child_ops; + + if (!child_ops) + return false; + + for (; child_ops->name; child_ops++) { + if (!strcmp(evsel->name, child_ops->name)) { + child_ops->get_key(evsel, sample, key); + return true; + } + } + + return false; +} + +static bool handle_child_event(struct perf_kvm_stat *kvm, + struct vcpu_event_record *vcpu_record, + struct event_key *key, + struct perf_sample *sample __maybe_unused) +{ + struct kvm_event *event = NULL; + + if (key->key != INVALID_KEY) + event = find_create_kvm_event(kvm, key); + + vcpu_record->last_event = event; + + return true; +} + +static bool skip_event(const char *event) +{ + const char * const *skip_events; + + for (skip_events = kvm_skip_events; *skip_events; skip_events++) + if (!strcmp(event, *skip_events)) + return true; + + return false; +} + static bool handle_end_event(struct perf_kvm_stat *kvm, struct vcpu_event_record *vcpu_record, struct event_key *key, @@ -525,10 +357,10 @@ static bool handle_end_event(struct perf_kvm_stat *kvm, time_diff = sample->time - time_begin; if (kvm->duration && time_diff > kvm->duration) { - char decode[32]; + char decode[DECODE_STR_LEN]; kvm->events_ops->decode_key(kvm, &event->key, decode); - if (strcmp(decode, "HLT")) { + if (!skip_event(decode)) { pr_info("%" PRIu64 " VM %d, vcpu %d: %s event took %" PRIu64 "usec\n", sample->time, sample->pid, vcpu_record->vcpu_id, decode, time_diff/1000); @@ -553,7 +385,7 @@ struct vcpu_event_record *per_vcpu_record(struct thread *thread, return NULL; } - vcpu_record->vcpu_id = perf_evsel__intval(evsel, sample, "vcpu_id"); + vcpu_record->vcpu_id = perf_evsel__intval(evsel, sample, VCPU_ID); thread->priv = vcpu_record; } @@ -566,7 +398,8 @@ static bool handle_kvm_event(struct perf_kvm_stat *kvm, struct perf_sample *sample) { struct vcpu_event_record *vcpu_record; - struct event_key key = {.key = INVALID_KEY}; + struct event_key key = { .key = INVALID_KEY, + .exit_reasons = kvm->exit_reasons }; vcpu_record = per_vcpu_record(thread, evsel, sample); if (!vcpu_record) @@ -580,6 +413,9 @@ static bool handle_kvm_event(struct perf_kvm_stat *kvm, if (kvm->events_ops->is_begin_event(evsel, sample, &key)) return handle_begin_event(kvm, vcpu_record, &key, sample->time); + if (is_child_event(kvm, evsel, sample, &key)) + return handle_child_event(kvm, vcpu_record, &key, sample); + if (kvm->events_ops->is_end_event(evsel, sample, &key)) return handle_end_event(kvm, vcpu_record, &key, sample); @@ -740,7 +576,7 @@ static void show_timeofday(void) static void print_result(struct perf_kvm_stat *kvm) { - char decode[20]; + char decode[DECODE_STR_LEN]; struct kvm_event *event; int vcpu = kvm->trace_vcpu; @@ -751,7 +587,7 @@ static void print_result(struct perf_kvm_stat *kvm) pr_info("\n\n"); print_vcpu_info(kvm); - pr_info("%20s ", kvm->events_ops->name); + pr_info("%*s ", DECODE_STR_LEN, kvm->events_ops->name); pr_info("%10s ", "Samples"); pr_info("%9s ", "Samples%"); @@ -770,7 +606,7 @@ static void print_result(struct perf_kvm_stat *kvm) min = get_event_min(event, vcpu); kvm->events_ops->decode_key(kvm, &event->key, decode); - pr_info("%20s ", decode); + pr_info("%*s ", DECODE_STR_LEN, decode); pr_info("%10llu ", (unsigned long long)ecount); pr_info("%8.2f%% ", (double)ecount / kvm->total_count * 100); pr_info("%8.2f%% ", (double)etime / kvm->total_time * 100); @@ -839,34 +675,28 @@ static int process_sample_event(struct perf_tool *tool, static int cpu_isa_config(struct perf_kvm_stat *kvm) { char buf[64], *cpuid; - int err, isa; + int err; if (kvm->live) { err = get_cpuid(buf, sizeof(buf)); if (err != 0) { - pr_err("Failed to look up CPU type (Intel or AMD)\n"); + pr_err("Failed to look up CPU type\n"); return err; } cpuid = buf; } else cpuid = kvm->session->header.env.cpuid; - if (strstr(cpuid, "Intel")) - isa = 1; - else if (strstr(cpuid, "AMD")) - isa = 0; - else { - pr_err("CPU %s is not supported.\n", cpuid); - return -ENOTSUP; + if (!cpuid) { + pr_err("Failed to look up CPU type\n"); + return -EINVAL; } - if (isa == 1) { - kvm->exit_reasons = vmx_exit_reasons; - kvm->exit_reasons_size = ARRAY_SIZE(vmx_exit_reasons); - kvm->exit_reasons_isa = "VMX"; - } + err = cpu_isa_init(kvm, cpuid); + if (err == -ENOTSUP) + pr_err("CPU %s is not supported.\n", cpuid); - return 0; + return err; } static bool verify_vcpu(int vcpu) @@ -1300,13 +1130,6 @@ exit: return ret; } -static const char * const kvm_events_tp[] = { - "kvm:kvm_entry", - "kvm:kvm_exit", - "kvm:kvm_mmio", - "kvm:kvm_pio", -}; - #define STRDUP_FAIL_EXIT(s) \ ({ char *_p; \ _p = strdup(s); \ @@ -1318,7 +1141,7 @@ static const char * const kvm_events_tp[] = { static int kvm_events_record(struct perf_kvm_stat *kvm, int argc, const char **argv) { - unsigned int rec_argc, i, j; + unsigned int rec_argc, i, j, events_tp_size; const char **rec_argv; const char * const record_args[] = { "record", @@ -1326,9 +1149,14 @@ kvm_events_record(struct perf_kvm_stat *kvm, int argc, const char **argv) "-m", "1024", "-c", "1", }; + const char * const *events_tp; + events_tp_size = 0; + + for (events_tp = kvm_events_tp; *events_tp; events_tp++) + events_tp_size++; rec_argc = ARRAY_SIZE(record_args) + argc + 2 + - 2 * ARRAY_SIZE(kvm_events_tp); + 2 * events_tp_size; rec_argv = calloc(rec_argc + 1, sizeof(char *)); if (rec_argv == NULL) @@ -1337,7 +1165,7 @@ kvm_events_record(struct perf_kvm_stat *kvm, int argc, const char **argv) for (i = 0; i < ARRAY_SIZE(record_args); i++) rec_argv[i] = STRDUP_FAIL_EXIT(record_args[i]); - for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) { + for (j = 0; j < events_tp_size; j++) { rec_argv[i++] = "-e"; rec_argv[i++] = STRDUP_FAIL_EXIT(kvm_events_tp[j]); } @@ -1356,7 +1184,8 @@ kvm_events_report(struct perf_kvm_stat *kvm, int argc, const char **argv) { const struct option kvm_events_report_options[] = { OPT_STRING(0, "event", &kvm->report_event, "report event", - "event for reporting: vmexit, mmio, ioport"), + "event for reporting: vmexit, " + "mmio (x86 only), ioport (x86 only)"), OPT_INTEGER(0, "vcpu", &kvm->trace_vcpu, "vcpu id to report"), OPT_STRING('k', "key", &kvm->sort_key, "sort-key", @@ -1391,16 +1220,16 @@ static struct perf_evlist *kvm_live_event_list(void) { struct perf_evlist *evlist; char *tp, *name, *sys; - unsigned int j; int err = -1; + const char * const *events_tp; evlist = perf_evlist__new(); if (evlist == NULL) return NULL; - for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) { + for (events_tp = kvm_events_tp; *events_tp; events_tp++) { - tp = strdup(kvm_events_tp[j]); + tp = strdup(*events_tp); if (tp == NULL) goto out; @@ -1409,7 +1238,7 @@ static struct perf_evlist *kvm_live_event_list(void) name = strchr(tp, ':'); if (name == NULL) { pr_err("Error parsing %s tracepoint: subsystem delimiter not found\n", - kvm_events_tp[j]); + *events_tp); free(tp); goto out; } @@ -1417,7 +1246,7 @@ static struct perf_evlist *kvm_live_event_list(void) name++; if (perf_evlist__add_newtp(evlist, sys, name, NULL)) { - pr_err("Failed to add %s tracepoint to the list\n", kvm_events_tp[j]); + pr_err("Failed to add %s tracepoint to the list\n", *events_tp); free(tp); goto out; } @@ -1462,7 +1291,9 @@ static int kvm_events_live(struct perf_kvm_stat *kvm, "key for sorting: sample(sort by samples number)" " time (sort by avg time)"), OPT_U64(0, "duration", &kvm->duration, - "show events other than HALT that take longer than duration usecs"), + "show events other than" + " HLT (x86 only) or Wait state (s390 only)" + " that take longer than duration usecs"), OPT_END() }; const char * const live_usage[] = { @@ -1585,9 +1416,6 @@ static int kvm_cmd_stat(const char *file_name, int argc, const char **argv) .report_event = "vmexit", .sort_key = "sample", - .exit_reasons = svm_exit_reasons, - .exit_reasons_size = ARRAY_SIZE(svm_exit_reasons), - .exit_reasons_isa = "SVM", }; if (argc == 1) { @@ -1609,7 +1437,7 @@ static int kvm_cmd_stat(const char *file_name, int argc, const char **argv) perf_stat: return cmd_stat(argc, argv, NULL); } -#endif +#endif /* HAVE_KVM_STAT_SUPPORT */ static int __cmd_record(const char *file_name, int argc, const char **argv) { @@ -1726,7 +1554,7 @@ int cmd_kvm(int argc, const char **argv, const char *prefix __maybe_unused) return cmd_top(argc, argv, NULL); else if (!strncmp(argv[0], "buildid-list", 12)) return __cmd_buildid_list(file_name, argc, argv); -#if defined(__i386__) || defined(__x86_64__) +#ifdef HAVE_KVM_STAT_SUPPORT else if (!strncmp(argv[0], "stat", 4)) return kvm_cmd_stat(file_name, argc, argv); #endif diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index c852c7a85d32..6148afc995c6 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -961,8 +961,10 @@ int cmd_lock(int argc, const char **argv, const char *prefix __maybe_unused) "perf lock info [<options>]", NULL }; - const char * const lock_usage[] = { - "perf lock [<options>] {record|report|script|info}", + const char *const lock_subcommands[] = { "record", "report", "script", + "info", NULL }; + const char *lock_usage[] = { + NULL, NULL }; const char * const report_usage[] = { @@ -976,8 +978,8 @@ int cmd_lock(int argc, const char **argv, const char *prefix __maybe_unused) for (i = 0; i < LOCKHASH_SIZE; i++) INIT_LIST_HEAD(lockhash_table + i); - argc = parse_options(argc, argv, lock_options, lock_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + argc = parse_options_subcommand(argc, argv, lock_options, lock_subcommands, + lock_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (!argc) usage_with_options(lock_usage, lock_options); diff --git a/tools/perf/builtin-mem.c b/tools/perf/builtin-mem.c index 2e3ade69a58e..4a1a6c94a5eb 100644 --- a/tools/perf/builtin-mem.c +++ b/tools/perf/builtin-mem.c @@ -21,11 +21,6 @@ struct perf_mem { DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); }; -static const char * const mem_usage[] = { - "perf mem [<options>] {record <command> |report}", - NULL -}; - static int __cmd_record(int argc, const char **argv) { int rec_argc, i = 0, j; @@ -220,9 +215,15 @@ int cmd_mem(int argc, const char **argv, const char *prefix __maybe_unused) " between columns '.' is reserved."), OPT_END() }; + const char *const mem_subcommands[] = { "record", "report", NULL }; + const char *mem_usage[] = { + NULL, + NULL + }; + - argc = parse_options(argc, argv, mem_options, mem_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + argc = parse_options_subcommand(argc, argv, mem_options, mem_subcommands, + mem_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (!argc || !(strncmp(argv[0], "rec", 3) || mem_operation)) usage_with_options(mem_usage, mem_options); diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index cdcd4eb3a57d..c63fa2925075 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -288,6 +288,13 @@ static void cleanup_params(void) memset(¶ms, 0, sizeof(params)); } +static void pr_err_with_code(const char *msg, int err) +{ + pr_err("%s", msg); + pr_debug(" Reason: %s (Code: %d)", strerror(-err), err); + pr_err("\n"); +} + static int __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) { @@ -379,7 +386,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) } ret = parse_probe_event_argv(argc, argv); if (ret < 0) { - pr_err(" Error: Parse Error. (%d)\n", ret); + pr_err_with_code(" Error: Command Parse Error.", ret); return ret; } } @@ -419,8 +426,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) } ret = show_perf_probe_events(); if (ret < 0) - pr_err(" Error: Failed to show event list. (%d)\n", - ret); + pr_err_with_code(" Error: Failed to show event list.", ret); return ret; } if (params.show_funcs) { @@ -445,8 +451,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) strfilter__delete(params.filter); params.filter = NULL; if (ret < 0) - pr_err(" Error: Failed to show functions." - " (%d)\n", ret); + pr_err_with_code(" Error: Failed to show functions.", ret); return ret; } @@ -464,7 +469,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) ret = show_line_range(¶ms.line_range, params.target); if (ret < 0) - pr_err(" Error: Failed to show lines. (%d)\n", ret); + pr_err_with_code(" Error: Failed to show lines.", ret); return ret; } if (params.show_vars) { @@ -485,7 +490,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) strfilter__delete(params.filter); params.filter = NULL; if (ret < 0) - pr_err(" Error: Failed to show vars. (%d)\n", ret); + pr_err_with_code(" Error: Failed to show vars.", ret); return ret; } #endif @@ -493,7 +498,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) if (params.dellist) { ret = del_perf_probe_events(params.dellist); if (ret < 0) { - pr_err(" Error: Failed to delete events. (%d)\n", ret); + pr_err_with_code(" Error: Failed to delete events.", ret); return ret; } } @@ -504,7 +509,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) params.target, params.force_add); if (ret < 0) { - pr_err(" Error: Failed to add events. (%d)\n", ret); + pr_err_with_code(" Error: Failed to add events.", ret); return ret; } } diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 8ce62ef7f6c3..4869050e7194 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -30,37 +30,6 @@ #include <sched.h> #include <sys/mman.h> -#ifndef HAVE_ON_EXIT_SUPPORT -#ifndef ATEXIT_MAX -#define ATEXIT_MAX 32 -#endif -static int __on_exit_count = 0; -typedef void (*on_exit_func_t) (int, void *); -static on_exit_func_t __on_exit_funcs[ATEXIT_MAX]; -static void *__on_exit_args[ATEXIT_MAX]; -static int __exitcode = 0; -static void __handle_on_exit_funcs(void); -static int on_exit(on_exit_func_t function, void *arg); -#define exit(x) (exit)(__exitcode = (x)) - -static int on_exit(on_exit_func_t function, void *arg) -{ - if (__on_exit_count == ATEXIT_MAX) - return -ENOMEM; - else if (__on_exit_count == 0) - atexit(__handle_on_exit_funcs); - __on_exit_funcs[__on_exit_count] = function; - __on_exit_args[__on_exit_count++] = arg; - return 0; -} - -static void __handle_on_exit_funcs(void) -{ - int i; - for (i = 0; i < __on_exit_count; i++) - __on_exit_funcs[i] (__exitcode, __on_exit_args[i]); -} -#endif struct record { struct perf_tool tool; @@ -147,29 +116,19 @@ static void sig_handler(int sig) { if (sig == SIGCHLD) child_finished = 1; + else + signr = sig; done = 1; - signr = sig; } -static void record__sig_exit(int exit_status __maybe_unused, void *arg) +static void record__sig_exit(void) { - struct record *rec = arg; - int status; - - if (rec->evlist->workload.pid > 0) { - if (!child_finished) - kill(rec->evlist->workload.pid, SIGTERM); - - wait(&status); - if (WIFSIGNALED(status)) - psignal(WTERMSIG(status), rec->progname); - } - - if (signr == -1 || signr == SIGUSR1) + if (signr == -1) return; signal(signr, SIG_DFL); + raise(signr); } static int record__open(struct record *rec) @@ -243,27 +202,6 @@ static int process_buildids(struct record *rec) size, &build_id__mark_dso_hit_ops); } -static void record__exit(int status, void *arg) -{ - struct record *rec = arg; - struct perf_data_file *file = &rec->file; - - if (status != 0) - return; - - if (!file->is_pipe) { - rec->session->header.data_size += rec->bytes_written; - - if (!rec->no_buildid) - process_buildids(rec); - perf_session__write_header(rec->session, rec->evlist, - file->fd, true); - perf_session__delete(rec->session); - perf_evlist__delete(rec->evlist); - symbol__exit(); - } -} - static void perf_event__synthesize_guest_os(struct machine *machine, void *data) { int err; @@ -300,6 +238,7 @@ static struct perf_event_header finished_round_event = { static int record__mmap_read_all(struct record *rec) { + u64 bytes_written = rec->bytes_written; int i; int rc = 0; @@ -312,7 +251,11 @@ static int record__mmap_read_all(struct record *rec) } } - if (perf_header__has_feat(&rec->session->header, HEADER_TRACING_DATA)) + /* + * Mark the round finished in case we wrote + * at least one event. + */ + if (bytes_written != rec->bytes_written) rc = record__write(rec, &finished_round_event, sizeof(finished_round_event)); out: @@ -344,18 +287,19 @@ static volatile int workload_exec_errno; * if the fork fails, since we asked by setting its * want_signal to true. */ -static void workload_exec_failed_signal(int signo, siginfo_t *info, +static void workload_exec_failed_signal(int signo __maybe_unused, + siginfo_t *info, void *ucontext __maybe_unused) { workload_exec_errno = info->si_value.sival_int; done = 1; - signr = signo; child_finished = 1; } static int __cmd_record(struct record *rec, int argc, const char **argv) { int err; + int status = 0; unsigned long waking = 0; const bool forks = argc > 0; struct machine *machine; @@ -367,7 +311,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) rec->progname = argv[0]; - on_exit(record__sig_exit, rec); + atexit(record__sig_exit); signal(SIGCHLD, sig_handler); signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); @@ -388,32 +332,28 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) workload_exec_failed_signal); if (err < 0) { pr_err("Couldn't run the workload!\n"); + status = err; goto out_delete_session; } } if (record__open(rec) != 0) { err = -1; - goto out_delete_session; + goto out_child; } if (!rec->evlist->nr_groups) perf_header__clear_feat(&session->header, HEADER_GROUP_DESC); - /* - * perf_session__delete(session) will be called at record__exit() - */ - on_exit(record__exit, rec); - if (file->is_pipe) { err = perf_header__write_pipe(file->fd); if (err < 0) - goto out_delete_session; + goto out_child; } else { err = perf_session__write_header(session, rec->evlist, file->fd, false); if (err < 0) - goto out_delete_session; + goto out_child; } if (!rec->no_buildid @@ -421,7 +361,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) pr_err("Couldn't generate buildids. " "Use --no-buildid to profile anyway.\n"); err = -1; - goto out_delete_session; + goto out_child; } machine = &session->machines.host; @@ -431,7 +371,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) process_synthesized_event); if (err < 0) { pr_err("Couldn't synthesize attrs.\n"); - goto out_delete_session; + goto out_child; } if (have_tracepoints(&rec->evlist->entries)) { @@ -447,7 +387,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) process_synthesized_event); if (err <= 0) { pr_err("Couldn't record tracing data.\n"); - goto out_delete_session; + goto out_child; } rec->bytes_written += err; } @@ -475,7 +415,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) err = __machine__synthesize_threads(machine, tool, &opts->target, rec->evlist->threads, process_synthesized_event, opts->sample_address); if (err != 0) - goto out_delete_session; + goto out_child; if (rec->realtime_prio) { struct sched_param param; @@ -484,7 +424,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) if (sched_setscheduler(0, SCHED_FIFO, ¶m)) { pr_err("Could not set realtime priority.\n"); err = -1; - goto out_delete_session; + goto out_child; } } @@ -512,13 +452,19 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) if (record__mmap_read_all(rec) < 0) { err = -1; - goto out_delete_session; + goto out_child; } if (hits == rec->samples) { if (done) break; err = poll(rec->evlist->pollfd, rec->evlist->nr_fds, -1); + /* + * Propagate error, only if there's any. Ignore positive + * number of returned events and interrupt error. + */ + if (err > 0 || (err < 0 && errno == EINTR)) + err = 0; waking++; } @@ -538,28 +484,52 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) const char *emsg = strerror_r(workload_exec_errno, msg, sizeof(msg)); pr_err("Workload failed: %s\n", emsg); err = -1; - goto out_delete_session; + goto out_child; } - if (quiet || signr == SIGUSR1) - return 0; + if (!quiet) { + fprintf(stderr, "[ perf record: Woken up %ld times to write data ]\n", waking); - fprintf(stderr, "[ perf record: Woken up %ld times to write data ]\n", waking); + /* + * Approximate RIP event size: 24 bytes. + */ + fprintf(stderr, + "[ perf record: Captured and wrote %.3f MB %s (~%" PRIu64 " samples) ]\n", + (double)rec->bytes_written / 1024.0 / 1024.0, + file->path, + rec->bytes_written / 24); + } - /* - * Approximate RIP event size: 24 bytes. - */ - fprintf(stderr, - "[ perf record: Captured and wrote %.3f MB %s (~%" PRIu64 " samples) ]\n", - (double)rec->bytes_written / 1024.0 / 1024.0, - file->path, - rec->bytes_written / 24); +out_child: + if (forks) { + int exit_status; - return 0; + if (!child_finished) + kill(rec->evlist->workload.pid, SIGTERM); + + wait(&exit_status); + + if (err < 0) + status = err; + else if (WIFEXITED(exit_status)) + status = WEXITSTATUS(exit_status); + else if (WIFSIGNALED(exit_status)) + signr = WTERMSIG(exit_status); + } else + status = err; + + if (!err && !file->is_pipe) { + rec->session->header.data_size += rec->bytes_written; + + if (!rec->no_buildid) + process_buildids(rec); + perf_session__write_header(rec->session, rec->evlist, + file->fd, true); + } out_delete_session: perf_session__delete(session); - return err; + return status; } #define BRANCH_OPT(n, m) \ @@ -583,6 +553,7 @@ static const struct branch_mode branch_modes[] = { BRANCH_OPT("abort_tx", PERF_SAMPLE_BRANCH_ABORT_TX), BRANCH_OPT("in_tx", PERF_SAMPLE_BRANCH_IN_TX), BRANCH_OPT("no_tx", PERF_SAMPLE_BRANCH_NO_TX), + BRANCH_OPT("cond", PERF_SAMPLE_BRANCH_COND), BRANCH_END }; @@ -988,6 +959,7 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused) err = __cmd_record(&record, argc, argv); out_symbol_exit: + perf_evlist__delete(rec->evlist); symbol__exit(); return err; } diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index c8f21137dfd8..21d830bafff3 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -57,6 +57,7 @@ struct report { const char *cpu_list; const char *symbol_filter_str; float min_percent; + u64 nr_entries; DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); }; @@ -71,150 +72,69 @@ static int report__config(const char *var, const char *value, void *cb) rep->min_percent = strtof(value, NULL); return 0; } + if (!strcmp(var, "report.children")) { + symbol_conf.cumulate_callchain = perf_config_bool(var, value); + return 0; + } return perf_default_config(var, value, cb); } -static int report__add_mem_hist_entry(struct report *rep, struct addr_location *al, - struct perf_sample *sample, struct perf_evsel *evsel) +static void report__inc_stats(struct report *rep, struct hist_entry *he) { - struct symbol *parent = NULL; - struct hist_entry *he; - struct mem_info *mi, *mx; - uint64_t cost; - int err = sample__resolve_callchain(sample, &parent, evsel, al, rep->max_stack); - - if (err) - return err; - - mi = sample__resolve_mem(sample, al); - if (!mi) - return -ENOMEM; - - if (rep->hide_unresolved && !al->sym) - return 0; - - cost = sample->weight; - if (!cost) - cost = 1; - /* - * must pass period=weight in order to get the correct - * sorting from hists__collapse_resort() which is solely - * based on periods. We want sorting be done on nr_events * weight - * and this is indirectly achieved by passing period=weight here - * and the he_stat__add_period() function. + * The @he is either of a newly created one or an existing one + * merging current sample. We only want to count a new one so + * checking ->nr_events being 1. */ - he = __hists__add_entry(&evsel->hists, al, parent, NULL, mi, - cost, cost, 0); - if (!he) - return -ENOMEM; - - if (ui__has_annotation()) { - err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); - if (err) - goto out; - - mx = he->mem_info; - err = addr_map_symbol__inc_samples(&mx->daddr, evsel->idx); - if (err) - goto out; - } - - evsel->hists.stats.total_period += cost; - hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); - err = hist_entry__append_callchain(he, sample); -out: - return err; + if (he->stat.nr_events == 1) + rep->nr_entries++; } -static int report__add_branch_hist_entry(struct report *rep, struct addr_location *al, - struct perf_sample *sample, struct perf_evsel *evsel) +static int hist_iter__report_callback(struct hist_entry_iter *iter, + struct addr_location *al, bool single, + void *arg) { - struct symbol *parent = NULL; - unsigned i; - struct hist_entry *he; - struct branch_info *bi, *bx; - int err = sample__resolve_callchain(sample, &parent, evsel, al, rep->max_stack); - - if (err) - return err; - - bi = sample__resolve_bstack(sample, al); - 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; + int err = 0; + struct report *rep = arg; + struct hist_entry *he = iter->he; + struct perf_evsel *evsel = iter->evsel; + struct mem_info *mi; + struct branch_info *bi; - err = -ENOMEM; + report__inc_stats(rep, he); - /* overwrite the 'al' to branch-to info */ - al->map = bi[i].to.map; - al->sym = bi[i].to.sym; - al->addr = bi[i].to.addr; - /* - * The report shows the percentage of total branches captured - * and not events sampled. Thus we use a pseudo period of 1. - */ - he = __hists__add_entry(&evsel->hists, al, parent, &bi[i], NULL, - 1, 1, 0); - if (he) { - if (ui__has_annotation()) { - bx = he->branch_info; - err = addr_map_symbol__inc_samples(&bx->from, - evsel->idx); - if (err) - goto out; - - err = addr_map_symbol__inc_samples(&bx->to, - evsel->idx); - if (err) - goto out; - } + if (!ui__has_annotation()) + return 0; - evsel->hists.stats.total_period += 1; - hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); - } else + if (sort__mode == SORT_MODE__BRANCH) { + bi = he->branch_info; + err = addr_map_symbol__inc_samples(&bi->from, evsel->idx); + if (err) goto out; - } - err = 0; -out: - free(bi); - return err; -} -static int report__add_hist_entry(struct report *rep, struct perf_evsel *evsel, - struct addr_location *al, struct perf_sample *sample) -{ - struct symbol *parent = NULL; - struct hist_entry *he; - int err = sample__resolve_callchain(sample, &parent, evsel, al, rep->max_stack); - - if (err) - return err; + err = addr_map_symbol__inc_samples(&bi->to, evsel->idx); - he = __hists__add_entry(&evsel->hists, al, parent, NULL, NULL, - sample->period, sample->weight, - sample->transaction); - if (he == NULL) - return -ENOMEM; + } else if (rep->mem_mode) { + mi = he->mem_info; + err = addr_map_symbol__inc_samples(&mi->daddr, evsel->idx); + if (err) + goto out; - err = hist_entry__append_callchain(he, sample); - if (err) - goto out; + err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); - if (ui__has_annotation()) + } else if (symbol_conf.cumulate_callchain) { + if (single) + err = hist_entry__inc_addr_samples(he, evsel->idx, + al->addr); + } else { err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); + } - evsel->hists.stats.total_period += sample->period; - hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); out: return err; } - static int process_sample_event(struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, @@ -223,6 +143,10 @@ static int process_sample_event(struct perf_tool *tool, { struct report *rep = container_of(tool, struct report, tool); struct addr_location al; + struct hist_entry_iter iter = { + .hide_unresolved = rep->hide_unresolved, + .add_entry_cb = hist_iter__report_callback, + }; int ret; if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { @@ -237,22 +161,23 @@ static int process_sample_event(struct perf_tool *tool, if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap)) return 0; - if (sort__mode == SORT_MODE__BRANCH) { - ret = report__add_branch_hist_entry(rep, &al, sample, evsel); - if (ret < 0) - pr_debug("problem adding lbr entry, skipping event\n"); - } else if (rep->mem_mode == 1) { - ret = report__add_mem_hist_entry(rep, &al, sample, evsel); - if (ret < 0) - pr_debug("problem adding mem entry, skipping event\n"); - } else { - if (al.map != NULL) - al.map->dso->hit = 1; + if (sort__mode == SORT_MODE__BRANCH) + iter.ops = &hist_iter_branch; + else if (rep->mem_mode) + iter.ops = &hist_iter_mem; + else if (symbol_conf.cumulate_callchain) + iter.ops = &hist_iter_cumulative; + else + iter.ops = &hist_iter_normal; + + if (al.map != NULL) + al.map->dso->hit = 1; + + ret = hist_entry_iter__add(&iter, &al, evsel, sample, rep->max_stack, + rep); + if (ret < 0) + pr_debug("problem adding hist entry, skipping event\n"); - ret = report__add_hist_entry(rep, evsel, &al, sample); - if (ret < 0) - pr_debug("problem incrementing symbol period, skipping event\n"); - } return ret; } @@ -309,6 +234,14 @@ static int report__setup_sample_type(struct report *rep) } } + if (symbol_conf.cumulate_callchain) { + /* Silently ignore if callchain is missing */ + if (!(sample_type & PERF_SAMPLE_CALLCHAIN)) { + symbol_conf.cumulate_callchain = false; + perf_hpp__cancel_cumulate(); + } + } + if (sort__mode == SORT_MODE__BRANCH) { if (!is_pipe && !(sample_type & PERF_SAMPLE_BRANCH_STACK)) { @@ -337,6 +270,11 @@ static size_t hists__fprintf_nr_sample_events(struct hists *hists, struct report char buf[512]; size_t size = sizeof(buf); + if (symbol_conf.filter_relative) { + nr_samples = hists->stats.nr_non_filtered_samples; + nr_events = hists->stats.total_non_filtered_period; + } + if (perf_evsel__is_group_event(evsel)) { struct perf_evsel *pos; @@ -344,8 +282,13 @@ static size_t hists__fprintf_nr_sample_events(struct hists *hists, struct report evname = buf; for_each_group_member(pos, evsel) { - nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; - nr_events += pos->hists.stats.total_period; + if (symbol_conf.filter_relative) { + nr_samples += pos->hists.stats.nr_non_filtered_samples; + nr_events += pos->hists.stats.total_non_filtered_period; + } else { + nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; + nr_events += pos->hists.stats.total_period; + } } } @@ -470,24 +413,12 @@ static int report__browse_hists(struct report *rep) return ret; } -static u64 report__collapse_hists(struct report *rep) +static void report__collapse_hists(struct report *rep) { struct ui_progress prog; struct perf_evsel *pos; - u64 nr_samples = 0; - /* - * Count number of histogram entries to use when showing progress, - * reusing nr_samples variable. - */ - evlist__for_each(rep->session->evlist, pos) - nr_samples += pos->hists.nr_entries; - ui_progress__init(&prog, nr_samples, "Merging related events..."); - /* - * Count total number of samples, will be used to check if this - * session had any. - */ - nr_samples = 0; + ui_progress__init(&prog, rep->nr_entries, "Merging related events..."); evlist__for_each(rep->session->evlist, pos) { struct hists *hists = &pos->hists; @@ -496,7 +427,6 @@ static u64 report__collapse_hists(struct report *rep) hists->symbol_filter_str = rep->symbol_filter_str; hists__collapse_resort(hists, &prog); - nr_samples += hists->stats.nr_events[PERF_RECORD_SAMPLE]; /* Non-group events are considered as leader */ if (symbol_conf.event_group && @@ -509,14 +439,11 @@ static u64 report__collapse_hists(struct report *rep) } ui_progress__finish(); - - return nr_samples; } static int __cmd_report(struct report *rep) { int ret; - u64 nr_samples; struct perf_session *session = rep->session; struct perf_evsel *pos; struct perf_data_file *file = session->file; @@ -556,12 +483,12 @@ static int __cmd_report(struct report *rep) } } - nr_samples = report__collapse_hists(rep); + report__collapse_hists(rep); if (session_done()) return 0; - if (nr_samples == 0) { + if (rep->nr_entries == 0) { ui__error("The %s file has no samples!\n", file->path); return 0; } @@ -573,11 +500,9 @@ static int __cmd_report(struct report *rep) } static int -parse_callchain_opt(const struct option *opt, const char *arg, int unset) +report_parse_callchain_opt(const struct option *opt, const char *arg, int unset) { struct report *rep = (struct report *)opt->value; - char *tok, *tok2; - char *endptr; /* * --no-call-graph @@ -587,80 +512,7 @@ parse_callchain_opt(const struct option *opt, const char *arg, int unset) return 0; } - symbol_conf.use_callchain = true; - - if (!arg) - return 0; - - tok = strtok((char *)arg, ","); - if (!tok) - return -1; - - /* get the output mode */ - if (!strncmp(tok, "graph", strlen(arg))) - callchain_param.mode = CHAIN_GRAPH_ABS; - - else if (!strncmp(tok, "flat", strlen(arg))) - callchain_param.mode = CHAIN_FLAT; - - else if (!strncmp(tok, "fractal", strlen(arg))) - callchain_param.mode = CHAIN_GRAPH_REL; - - else if (!strncmp(tok, "none", strlen(arg))) { - callchain_param.mode = CHAIN_NONE; - symbol_conf.use_callchain = false; - - return 0; - } - - else - return -1; - - /* get the min percentage */ - tok = strtok(NULL, ","); - if (!tok) - goto setup; - - callchain_param.min_percent = strtod(tok, &endptr); - if (tok == endptr) - return -1; - - /* get the print limit */ - tok2 = strtok(NULL, ","); - if (!tok2) - goto setup; - - if (tok2[0] != 'c') { - callchain_param.print_limit = strtoul(tok2, &endptr, 0); - tok2 = strtok(NULL, ","); - if (!tok2) - goto setup; - } - - /* get the call chain order */ - if (!strncmp(tok2, "caller", strlen("caller"))) - callchain_param.order = ORDER_CALLER; - else if (!strncmp(tok2, "callee", strlen("callee"))) - callchain_param.order = ORDER_CALLEE; - else - return -1; - - /* Get the sort key */ - tok2 = strtok(NULL, ","); - if (!tok2) - goto setup; - if (!strncmp(tok2, "function", strlen("function"))) - callchain_param.key = CCKEY_FUNCTION; - else if (!strncmp(tok2, "address", strlen("address"))) - callchain_param.key = CCKEY_ADDRESS; - else - return -1; -setup: - if (callchain_register_param(&callchain_param) < 0) { - pr_err("Can't register callchain params\n"); - return -1; - } - return 0; + return parse_callchain_report_opt(arg); } int @@ -760,10 +612,10 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) OPT_BOOLEAN(0, "header-only", &report.header_only, "Show only data header."), OPT_STRING('s', "sort", &sort_order, "key[,key2...]", - "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline," - " dso_to, dso_from, symbol_to, symbol_from, mispredict," - " weight, local_weight, mem, symbol_daddr, dso_daddr, tlb, " - "snoop, locked, abort, in_tx, transaction"), + "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..." + " Please refer the man page for the complete list."), + OPT_STRING('F', "fields", &field_order, "key[,keys...]", + "output field(s): overhead, period, sample plus all of sort keys"), OPT_BOOLEAN(0, "showcpuutilization", &symbol_conf.show_cpu_utilization, "Show sample percentage for different cpu modes"), OPT_STRING('p', "parent", &parent_pattern, "regex", @@ -772,7 +624,9 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) "Only display entries with parent-match"), OPT_CALLBACK_DEFAULT('g', "call-graph", &report, "output_type,min_percent[,print_limit],call_order", "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit, callchain order, key (function or address). " - "Default: fractal,0.5,callee,function", &parse_callchain_opt, callchain_default_opt), + "Default: fractal,0.5,callee,function", &report_parse_callchain_opt, callchain_default_opt), + OPT_BOOLEAN(0, "children", &symbol_conf.cumulate_callchain, + "Accumulate callchains of children and show total overhead as well"), OPT_INTEGER(0, "max-stack", &report.max_stack, "Set the maximum stack depth when parsing the callchain, " "anything beyond the specified depth will be ignored. " @@ -823,6 +677,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) OPT_BOOLEAN(0, "mem-mode", &report.mem_mode, "mem access profile"), OPT_CALLBACK(0, "percent-limit", &report, "percent", "Don't show entries under that percent", parse_percent_limit), + OPT_CALLBACK(0, "percentage", NULL, "relative|absolute", + "how to display percentage of filtered entries", parse_filter_percentage), OPT_END() }; struct perf_data_file file = { @@ -863,55 +719,37 @@ repeat: has_br_stack = perf_header__has_feat(&session->header, HEADER_BRANCH_STACK); - if (branch_mode == -1 && has_br_stack) + if (branch_mode == -1 && has_br_stack) { sort__mode = SORT_MODE__BRANCH; - - /* sort__mode could be NORMAL if --no-branch-stack */ - if (sort__mode == SORT_MODE__BRANCH) { - /* - * 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"; - + symbol_conf.cumulate_callchain = false; } + if (report.mem_mode) { if (sort__mode == SORT_MODE__BRANCH) { pr_err("branch and mem mode incompatible\n"); goto error; } sort__mode = SORT_MODE__MEMORY; - - /* - * if no sort_order is provided, then specify - * branch-mode specific order - */ - if (sort_order == default_sort_order) - sort_order = "local_weight,mem,sym,dso,symbol_daddr,dso_daddr,snoop,tlb,locked"; + symbol_conf.cumulate_callchain = false; } if (setup_sorting() < 0) { - parse_options_usage(report_usage, options, "s", 1); + if (sort_order) + parse_options_usage(report_usage, options, "s", 1); + if (field_order) + parse_options_usage(sort_order ? NULL : report_usage, + options, "F", 1); goto error; } - if (parent_pattern != default_parent_pattern) { - if (sort_dimension__add("parent") < 0) - goto error; - } - /* Force tty output for header output. */ if (report.header || report.header_only) use_browser = 0; if (strcmp(input_name, "-") != 0) setup_browser(true); - else { + else use_browser = 0; - perf_hpp__init(); - } if (report.header || report.header_only) { perf_session__fprintf_info(session, stdout, diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index 9ac0a495c954..f83c08c0dd87 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c @@ -10,6 +10,7 @@ #include "util/header.h" #include "util/session.h" #include "util/tool.h" +#include "util/cloexec.h" #include "util/parse-options.h" #include "util/trace-event.h" @@ -66,7 +67,7 @@ struct sched_atom { struct task_desc *wakee; }; -#define TASK_STATE_TO_CHAR_STR "RSDTtZX" +#define TASK_STATE_TO_CHAR_STR "RSDTtZXxKWP" enum thread_state { THREAD_SLEEPING = 0, @@ -149,7 +150,6 @@ struct perf_sched { unsigned long nr_runs; unsigned long nr_timestamps; unsigned long nr_unordered_timestamps; - unsigned long nr_state_machine_bugs; unsigned long nr_context_switch_bugs; unsigned long nr_events; unsigned long nr_lost_chunks; @@ -435,7 +435,8 @@ static int self_open_counters(void) attr.type = PERF_TYPE_SOFTWARE; attr.config = PERF_COUNT_SW_TASK_CLOCK; - fd = sys_perf_event_open(&attr, 0, -1, -1, 0); + fd = sys_perf_event_open(&attr, 0, -1, -1, + perf_event_open_cloexec_flag()); if (fd < 0) pr_err("Error: sys_perf_event_open() syscall returned " @@ -936,8 +937,8 @@ static int latency_switch_event(struct perf_sched *sched, return -1; } - sched_out = machine__findnew_thread(machine, 0, prev_pid); - sched_in = machine__findnew_thread(machine, 0, next_pid); + sched_out = machine__findnew_thread(machine, -1, prev_pid); + sched_in = machine__findnew_thread(machine, -1, next_pid); out_events = thread_atoms_search(&sched->atom_root, sched_out, &sched->cmp_pid); if (!out_events) { @@ -980,7 +981,7 @@ static int latency_runtime_event(struct perf_sched *sched, { const u32 pid = perf_evsel__intval(evsel, sample, "pid"); const u64 runtime = perf_evsel__intval(evsel, sample, "runtime"); - struct thread *thread = machine__findnew_thread(machine, 0, pid); + struct thread *thread = machine__findnew_thread(machine, -1, pid); struct work_atoms *atoms = thread_atoms_search(&sched->atom_root, thread, &sched->cmp_pid); u64 timestamp = sample->time; int cpu = sample->cpu; @@ -1007,18 +1008,13 @@ static int latency_wakeup_event(struct perf_sched *sched, struct perf_sample *sample, struct machine *machine) { - const u32 pid = perf_evsel__intval(evsel, sample, "pid"), - success = perf_evsel__intval(evsel, sample, "success"); + const u32 pid = perf_evsel__intval(evsel, sample, "pid"); struct work_atoms *atoms; struct work_atom *atom; struct thread *wakee; u64 timestamp = sample->time; - /* Note for later, it may be interesting to observe the failing cases */ - if (!success) - return 0; - - wakee = machine__findnew_thread(machine, 0, pid); + wakee = machine__findnew_thread(machine, -1, pid); atoms = thread_atoms_search(&sched->atom_root, wakee, &sched->cmp_pid); if (!atoms) { if (thread_atoms_insert(sched, wakee)) @@ -1037,12 +1033,18 @@ static int latency_wakeup_event(struct perf_sched *sched, atom = list_entry(atoms->work_list.prev, struct work_atom, list); /* + * As we do not guarantee the wakeup event happens when + * task is out of run queue, also may happen when task is + * on run queue and wakeup only change ->state to TASK_RUNNING, + * then we should not set the ->wake_up_time when wake up a + * task which is on run queue. + * * You WILL be missing events if you've recorded only * one CPU, or are only looking at only one, so don't - * make useless noise. + * skip in this case. */ if (sched->profile_cpu == -1 && atom->state != THREAD_SLEEPING) - sched->nr_state_machine_bugs++; + return 0; sched->nr_timestamps++; if (atom->sched_out_time > timestamp) { @@ -1072,7 +1074,7 @@ static int latency_migrate_task_event(struct perf_sched *sched, if (sched->profile_cpu == -1) return 0; - migrant = machine__findnew_thread(machine, 0, pid); + migrant = machine__findnew_thread(machine, -1, pid); atoms = thread_atoms_search(&sched->atom_root, migrant, &sched->cmp_pid); if (!atoms) { if (thread_atoms_insert(sched, migrant)) @@ -1266,9 +1268,8 @@ static int process_sched_wakeup_event(struct perf_tool *tool, static int map_switch_event(struct perf_sched *sched, struct perf_evsel *evsel, struct perf_sample *sample, struct machine *machine) { - const u32 prev_pid = perf_evsel__intval(evsel, sample, "prev_pid"), - next_pid = perf_evsel__intval(evsel, sample, "next_pid"); - struct thread *sched_out __maybe_unused, *sched_in; + const u32 next_pid = perf_evsel__intval(evsel, sample, "next_pid"); + struct thread *sched_in; int new_shortname; u64 timestamp0, timestamp = sample->time; s64 delta; @@ -1291,8 +1292,7 @@ static int map_switch_event(struct perf_sched *sched, struct perf_evsel *evsel, return -1; } - sched_out = machine__findnew_thread(machine, 0, prev_pid); - sched_in = machine__findnew_thread(machine, 0, next_pid); + sched_in = machine__findnew_thread(machine, -1, next_pid); sched->curr_thread[this_cpu] = sched_in; @@ -1300,17 +1300,25 @@ static int map_switch_event(struct perf_sched *sched, struct perf_evsel *evsel, new_shortname = 0; if (!sched_in->shortname[0]) { - sched_in->shortname[0] = sched->next_shortname1; - sched_in->shortname[1] = sched->next_shortname2; - - if (sched->next_shortname1 < 'Z') { - sched->next_shortname1++; + if (!strcmp(thread__comm_str(sched_in), "swapper")) { + /* + * Don't allocate a letter-number for swapper:0 + * as a shortname. Instead, we use '.' for it. + */ + sched_in->shortname[0] = '.'; + sched_in->shortname[1] = ' '; } else { - sched->next_shortname1='A'; - if (sched->next_shortname2 < '9') { - sched->next_shortname2++; + sched_in->shortname[0] = sched->next_shortname1; + sched_in->shortname[1] = sched->next_shortname2; + + if (sched->next_shortname1 < 'Z') { + sched->next_shortname1++; } else { - sched->next_shortname2='0'; + sched->next_shortname1 = 'A'; + if (sched->next_shortname2 < '9') + sched->next_shortname2++; + else + sched->next_shortname2 = '0'; } } new_shortname = 1; @@ -1322,12 +1330,9 @@ static int map_switch_event(struct perf_sched *sched, struct perf_evsel *evsel, else printf("*"); - if (sched->curr_thread[cpu]) { - if (sched->curr_thread[cpu]->tid) - printf("%2s ", sched->curr_thread[cpu]->shortname); - else - printf(". "); - } else + if (sched->curr_thread[cpu]) + printf("%2s ", sched->curr_thread[cpu]->shortname); + else printf(" "); } @@ -1425,7 +1430,7 @@ static int perf_sched__process_tracepoint_sample(struct perf_tool *tool __maybe_ int err = 0; evsel->hists.stats.total_period += sample->period; - hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); + hists__inc_nr_samples(&evsel->hists, true); if (evsel->handler != NULL) { tracepoint_handler f = evsel->handler; @@ -1496,14 +1501,6 @@ static void print_bad_events(struct perf_sched *sched) (double)sched->nr_lost_events/(double)sched->nr_events * 100.0, sched->nr_lost_events, sched->nr_events, sched->nr_lost_chunks); } - if (sched->nr_state_machine_bugs && sched->nr_timestamps) { - printf(" INFO: %.3f%% state machine bugs (%ld out of %ld)", - (double)sched->nr_state_machine_bugs/(double)sched->nr_timestamps*100.0, - sched->nr_state_machine_bugs, sched->nr_timestamps); - if (sched->nr_lost_events) - printf(" (due to lost events?)"); - printf("\n"); - } if (sched->nr_context_switch_bugs && sched->nr_timestamps) { printf(" INFO: %.3f%% context switch bugs (%ld out of %ld)", (double)sched->nr_context_switch_bugs/(double)sched->nr_timestamps*100.0, @@ -1635,6 +1632,7 @@ static int __cmd_record(int argc, const char **argv) "-e", "sched:sched_stat_runtime", "-e", "sched:sched_process_fork", "-e", "sched:sched_wakeup", + "-e", "sched:sched_wakeup_new", "-e", "sched:sched_migrate_task", }; @@ -1713,8 +1711,10 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) "perf sched replay [<options>]", NULL }; - const char * const sched_usage[] = { - "perf sched [<options>] {record|latency|map|replay|script}", + const char *const sched_subcommands[] = { "record", "latency", "map", + "replay", "script", NULL }; + const char *sched_usage[] = { + NULL, NULL }; struct trace_sched_handler lat_ops = { @@ -1736,8 +1736,8 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) for (i = 0; i < ARRAY_SIZE(sched.curr_pid); i++) sched.curr_pid[i] = -1; - argc = parse_options(argc, argv, sched_options, sched_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + argc = parse_options_subcommand(argc, argv, sched_options, sched_subcommands, + sched_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (!argc) usage_with_options(sched_usage, sched_options); diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index 9e9c91f5b7fa..f57035b89c15 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -358,27 +358,6 @@ static void print_sample_start(struct perf_sample *sample, } } -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) && - ((attr->config == PERF_COUNT_SW_PAGE_FAULTS) || - (attr->config == PERF_COUNT_SW_PAGE_FAULTS_MIN) || - (attr->config == PERF_COUNT_SW_PAGE_FAULTS_MAJ))) - return true; - - if (is_bts_event(attr)) - return true; - - return false; -} - static void print_sample_addr(union perf_event *event, struct perf_sample *sample, struct machine *machine, @@ -386,24 +365,13 @@ static void print_sample_addr(union perf_event *event, struct perf_event_attr *attr) { struct addr_location al; - u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; printf("%16" PRIx64, sample->addr); if (!sample_addr_correlates_sym(attr)) return; - thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION, - sample->addr, &al); - if (!al.map) - thread__find_addr_map(thread, machine, cpumode, MAP__VARIABLE, - sample->addr, &al); - - al.cpu = sample->cpu; - al.sym = NULL; - - if (al.map) - al.sym = map__find_symbol(al.map, al.addr, NULL); + perf_event__preprocess_sample_addr(event, sample, machine, thread, &al); if (PRINT_FIELD(SYM)) { printf(" "); @@ -427,25 +395,35 @@ static void print_sample_bts(union perf_event *event, struct addr_location *al) { struct perf_event_attr *attr = &evsel->attr; + bool print_srcline_last = false; /* print branch_from information */ if (PRINT_FIELD(IP)) { - if (!symbol_conf.use_callchain) - printf(" "); - else + unsigned int print_opts = output[attr->type].print_ip_opts; + + if (symbol_conf.use_callchain && sample->callchain) { printf("\n"); - perf_evsel__print_ip(evsel, sample, al, - output[attr->type].print_ip_opts, + } else { + printf(" "); + if (print_opts & PRINT_IP_OPT_SRCLINE) { + print_srcline_last = true; + print_opts &= ~PRINT_IP_OPT_SRCLINE; + } + } + perf_evsel__print_ip(evsel, sample, al, print_opts, PERF_MAX_STACK_DEPTH); } - printf(" => "); - /* print branch_to information */ if (PRINT_FIELD(ADDR) || ((evsel->attr.sample_type & PERF_SAMPLE_ADDR) && - !output[attr->type].user_set)) + !output[attr->type].user_set)) { + printf(" => "); print_sample_addr(event, sample, al->machine, thread, attr); + } + + if (print_srcline_last) + map__fprintf_srcline(al->map, al->addr, "\n ", stdout); printf("\n"); } diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 65a151e36067..3e80aa10cfd8 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -184,7 +184,7 @@ static void perf_evsel__reset_stat_priv(struct perf_evsel *evsel) static int perf_evsel__alloc_stat_priv(struct perf_evsel *evsel) { evsel->priv = zalloc(sizeof(struct perf_stat)); - if (evsel == NULL) + if (evsel->priv == NULL) return -ENOMEM; perf_evsel__reset_stat_priv(evsel); return 0; diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index 74db2568b867..2f1a5220c090 100644 --- a/tools/perf/builtin-timechart.c +++ b/tools/perf/builtin-timechart.c @@ -37,6 +37,7 @@ #include "util/svghelper.h" #include "util/tool.h" #include "util/data.h" +#include "util/debug.h" #define SUPPORT_OLD_POWER_EVENTS 1 #define PWR_EVENT_EXIT -1 @@ -60,10 +61,17 @@ struct timechart { tasks_only, with_backtrace, topology; + /* IO related settings */ + u64 io_events; + bool io_only, + skip_eagain; + u64 min_time, + merge_dist; }; struct per_pidcomm; struct cpu_sample; +struct io_sample; /* * Datastructure layout: @@ -84,6 +92,7 @@ struct per_pid { u64 start_time; u64 end_time; u64 total_time; + u64 total_bytes; int display; struct per_pidcomm *all; @@ -97,6 +106,8 @@ struct per_pidcomm { u64 start_time; u64 end_time; u64 total_time; + u64 max_bytes; + u64 total_bytes; int Y; int display; @@ -107,6 +118,7 @@ struct per_pidcomm { char *comm; struct cpu_sample *samples; + struct io_sample *io_samples; }; struct sample_wrapper { @@ -131,6 +143,27 @@ struct cpu_sample { const char *backtrace; }; +enum { + IOTYPE_READ, + IOTYPE_WRITE, + IOTYPE_SYNC, + IOTYPE_TX, + IOTYPE_RX, + IOTYPE_POLL, +}; + +struct io_sample { + struct io_sample *next; + + u64 start_time; + u64 end_time; + u64 bytes; + int type; + int fd; + int err; + int merges; +}; + #define CSTATE 1 #define PSTATE 2 @@ -213,7 +246,7 @@ static void pid_fork(struct timechart *tchart, int pid, int ppid, u64 timestamp) pid_set_comm(tchart, pid, pp->current->comm); p->start_time = timestamp; - if (p->current) { + if (p->current && !p->current->start_time) { p->current->start_time = timestamp; p->current->state_since = timestamp; } @@ -682,6 +715,249 @@ static void end_sample_processing(struct timechart *tchart) } } +static int pid_begin_io_sample(struct timechart *tchart, int pid, int type, + u64 start, int fd) +{ + struct per_pid *p = find_create_pid(tchart, pid); + struct per_pidcomm *c = p->current; + struct io_sample *sample; + struct io_sample *prev; + + if (!c) { + c = zalloc(sizeof(*c)); + if (!c) + return -ENOMEM; + p->current = c; + c->next = p->all; + p->all = c; + } + + prev = c->io_samples; + + if (prev && prev->start_time && !prev->end_time) { + pr_warning("Skip invalid start event: " + "previous event already started!\n"); + + /* remove previous event that has been started, + * we are not sure we will ever get an end for it */ + c->io_samples = prev->next; + free(prev); + return 0; + } + + sample = zalloc(sizeof(*sample)); + if (!sample) + return -ENOMEM; + sample->start_time = start; + sample->type = type; + sample->fd = fd; + sample->next = c->io_samples; + c->io_samples = sample; + + if (c->start_time == 0 || c->start_time > start) + c->start_time = start; + + return 0; +} + +static int pid_end_io_sample(struct timechart *tchart, int pid, int type, + u64 end, long ret) +{ + struct per_pid *p = find_create_pid(tchart, pid); + struct per_pidcomm *c = p->current; + struct io_sample *sample, *prev; + + if (!c) { + pr_warning("Invalid pidcomm!\n"); + return -1; + } + + sample = c->io_samples; + + if (!sample) /* skip partially captured events */ + return 0; + + if (sample->end_time) { + pr_warning("Skip invalid end event: " + "previous event already ended!\n"); + return 0; + } + + if (sample->type != type) { + pr_warning("Skip invalid end event: invalid event type!\n"); + return 0; + } + + sample->end_time = end; + prev = sample->next; + + /* we want to be able to see small and fast transfers, so make them + * at least min_time long, but don't overlap them */ + if (sample->end_time - sample->start_time < tchart->min_time) + sample->end_time = sample->start_time + tchart->min_time; + if (prev && sample->start_time < prev->end_time) { + if (prev->err) /* try to make errors more visible */ + sample->start_time = prev->end_time; + else + prev->end_time = sample->start_time; + } + + if (ret < 0) { + sample->err = ret; + } else if (type == IOTYPE_READ || type == IOTYPE_WRITE || + type == IOTYPE_TX || type == IOTYPE_RX) { + + if ((u64)ret > c->max_bytes) + c->max_bytes = ret; + + c->total_bytes += ret; + p->total_bytes += ret; + sample->bytes = ret; + } + + /* merge two requests to make svg smaller and render-friendly */ + if (prev && + prev->type == sample->type && + prev->err == sample->err && + prev->fd == sample->fd && + prev->end_time + tchart->merge_dist >= sample->start_time) { + + sample->bytes += prev->bytes; + sample->merges += prev->merges + 1; + + sample->start_time = prev->start_time; + sample->next = prev->next; + free(prev); + + if (!sample->err && sample->bytes > c->max_bytes) + c->max_bytes = sample->bytes; + } + + tchart->io_events++; + + return 0; +} + +static int +process_enter_read(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long fd = perf_evsel__intval(evsel, sample, "fd"); + return pid_begin_io_sample(tchart, sample->tid, IOTYPE_READ, + sample->time, fd); +} + +static int +process_exit_read(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long ret = perf_evsel__intval(evsel, sample, "ret"); + return pid_end_io_sample(tchart, sample->tid, IOTYPE_READ, + sample->time, ret); +} + +static int +process_enter_write(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long fd = perf_evsel__intval(evsel, sample, "fd"); + return pid_begin_io_sample(tchart, sample->tid, IOTYPE_WRITE, + sample->time, fd); +} + +static int +process_exit_write(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long ret = perf_evsel__intval(evsel, sample, "ret"); + return pid_end_io_sample(tchart, sample->tid, IOTYPE_WRITE, + sample->time, ret); +} + +static int +process_enter_sync(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long fd = perf_evsel__intval(evsel, sample, "fd"); + return pid_begin_io_sample(tchart, sample->tid, IOTYPE_SYNC, + sample->time, fd); +} + +static int +process_exit_sync(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long ret = perf_evsel__intval(evsel, sample, "ret"); + return pid_end_io_sample(tchart, sample->tid, IOTYPE_SYNC, + sample->time, ret); +} + +static int +process_enter_tx(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long fd = perf_evsel__intval(evsel, sample, "fd"); + return pid_begin_io_sample(tchart, sample->tid, IOTYPE_TX, + sample->time, fd); +} + +static int +process_exit_tx(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long ret = perf_evsel__intval(evsel, sample, "ret"); + return pid_end_io_sample(tchart, sample->tid, IOTYPE_TX, + sample->time, ret); +} + +static int +process_enter_rx(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long fd = perf_evsel__intval(evsel, sample, "fd"); + return pid_begin_io_sample(tchart, sample->tid, IOTYPE_RX, + sample->time, fd); +} + +static int +process_exit_rx(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long ret = perf_evsel__intval(evsel, sample, "ret"); + return pid_end_io_sample(tchart, sample->tid, IOTYPE_RX, + sample->time, ret); +} + +static int +process_enter_poll(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long fd = perf_evsel__intval(evsel, sample, "fd"); + return pid_begin_io_sample(tchart, sample->tid, IOTYPE_POLL, + sample->time, fd); +} + +static int +process_exit_poll(struct timechart *tchart, + struct perf_evsel *evsel, + struct perf_sample *sample) +{ + long ret = perf_evsel__intval(evsel, sample, "ret"); + return pid_end_io_sample(tchart, sample->tid, IOTYPE_POLL, + sample->time, ret); +} + /* * Sort the pid datastructure */ @@ -852,6 +1128,121 @@ static void draw_cpu_usage(struct timechart *tchart) } } +static void draw_io_bars(struct timechart *tchart) +{ + const char *suf; + double bytes; + char comm[256]; + struct per_pid *p; + struct per_pidcomm *c; + struct io_sample *sample; + int Y = 1; + + p = tchart->all_data; + while (p) { + c = p->all; + while (c) { + if (!c->display) { + c->Y = 0; + c = c->next; + continue; + } + + svg_box(Y, c->start_time, c->end_time, "process3"); + sample = c->io_samples; + for (sample = c->io_samples; sample; sample = sample->next) { + double h = (double)sample->bytes / c->max_bytes; + + if (tchart->skip_eagain && + sample->err == -EAGAIN) + continue; + + if (sample->err) + h = 1; + + if (sample->type == IOTYPE_SYNC) + svg_fbox(Y, + sample->start_time, + sample->end_time, + 1, + sample->err ? "error" : "sync", + sample->fd, + sample->err, + sample->merges); + else if (sample->type == IOTYPE_POLL) + svg_fbox(Y, + sample->start_time, + sample->end_time, + 1, + sample->err ? "error" : "poll", + sample->fd, + sample->err, + sample->merges); + else if (sample->type == IOTYPE_READ) + svg_ubox(Y, + sample->start_time, + sample->end_time, + h, + sample->err ? "error" : "disk", + sample->fd, + sample->err, + sample->merges); + else if (sample->type == IOTYPE_WRITE) + svg_lbox(Y, + sample->start_time, + sample->end_time, + h, + sample->err ? "error" : "disk", + sample->fd, + sample->err, + sample->merges); + else if (sample->type == IOTYPE_RX) + svg_ubox(Y, + sample->start_time, + sample->end_time, + h, + sample->err ? "error" : "net", + sample->fd, + sample->err, + sample->merges); + else if (sample->type == IOTYPE_TX) + svg_lbox(Y, + sample->start_time, + sample->end_time, + h, + sample->err ? "error" : "net", + sample->fd, + sample->err, + sample->merges); + } + + suf = ""; + bytes = c->total_bytes; + if (bytes > 1024) { + bytes = bytes / 1024; + suf = "K"; + } + if (bytes > 1024) { + bytes = bytes / 1024; + suf = "M"; + } + if (bytes > 1024) { + bytes = bytes / 1024; + suf = "G"; + } + + + sprintf(comm, "%s:%i (%3.1f %sbytes)", c->comm ?: "", p->pid, bytes, suf); + svg_text(Y, c->start_time, comm); + + c->Y = Y; + Y++; + c = c->next; + } + p = p->next; + } +} + static void draw_process_bars(struct timechart *tchart) { struct per_pid *p; @@ -987,9 +1378,6 @@ static int determine_display_tasks(struct timechart *tchart, u64 threshold) struct per_pidcomm *c; int count = 0; - if (process_filter) - return determine_display_tasks_filtered(tchart); - p = tchart->all_data; while (p) { p->display = 0; @@ -1025,15 +1413,46 @@ static int determine_display_tasks(struct timechart *tchart, u64 threshold) return count; } +static int determine_display_io_tasks(struct timechart *timechart, u64 threshold) +{ + struct per_pid *p; + struct per_pidcomm *c; + int count = 0; + + p = timechart->all_data; + while (p) { + /* no exit marker, task kept running to the end */ + if (p->end_time == 0) + p->end_time = timechart->last_time; + c = p->all; + while (c) { + c->display = 0; + + if (c->total_bytes >= threshold) { + c->display = 1; + count++; + } + + if (c->end_time == 0) + c->end_time = timechart->last_time; + + c = c->next; + } + p = p->next; + } + return count; +} + +#define BYTES_THRESH (1 * 1024 * 1024) #define TIME_THRESH 10000000 static void write_svg_file(struct timechart *tchart, const char *filename) { u64 i; int count; - int thresh = TIME_THRESH; + int thresh = tchart->io_events ? BYTES_THRESH : TIME_THRESH; if (tchart->power_only) tchart->proc_num = 0; @@ -1041,28 +1460,43 @@ static void write_svg_file(struct timechart *tchart, const char *filename) /* We'd like to show at least proc_num tasks; * be less picky if we have fewer */ do { - count = determine_display_tasks(tchart, thresh); + if (process_filter) + count = determine_display_tasks_filtered(tchart); + else if (tchart->io_events) + count = determine_display_io_tasks(tchart, thresh); + else + count = determine_display_tasks(tchart, thresh); thresh /= 10; } while (!process_filter && thresh && count < tchart->proc_num); if (!tchart->proc_num) count = 0; - open_svg(filename, tchart->numcpus, count, tchart->first_time, tchart->last_time); + if (tchart->io_events) { + open_svg(filename, 0, count, tchart->first_time, tchart->last_time); - svg_time_grid(); - svg_legenda(); + svg_time_grid(0.5); + svg_io_legenda(); + + draw_io_bars(tchart); + } else { + open_svg(filename, tchart->numcpus, count, tchart->first_time, tchart->last_time); - for (i = 0; i < tchart->numcpus; i++) - svg_cpu_box(i, tchart->max_freq, tchart->turbo_frequency); + svg_time_grid(0); - draw_cpu_usage(tchart); - if (tchart->proc_num) - draw_process_bars(tchart); - if (!tchart->tasks_only) - draw_c_p_states(tchart); - if (tchart->proc_num) - draw_wakeups(tchart); + svg_legenda(); + + for (i = 0; i < tchart->numcpus; i++) + svg_cpu_box(i, tchart->max_freq, tchart->turbo_frequency); + + draw_cpu_usage(tchart); + if (tchart->proc_num) + draw_process_bars(tchart); + if (!tchart->tasks_only) + draw_c_p_states(tchart); + if (tchart->proc_num) + draw_wakeups(tchart); + } svg_close(); } @@ -1110,6 +1544,56 @@ static int __cmd_timechart(struct timechart *tchart, const char *output_name) { "power:power_end", process_sample_power_end }, { "power:power_frequency", process_sample_power_frequency }, #endif + + { "syscalls:sys_enter_read", process_enter_read }, + { "syscalls:sys_enter_pread64", process_enter_read }, + { "syscalls:sys_enter_readv", process_enter_read }, + { "syscalls:sys_enter_preadv", process_enter_read }, + { "syscalls:sys_enter_write", process_enter_write }, + { "syscalls:sys_enter_pwrite64", process_enter_write }, + { "syscalls:sys_enter_writev", process_enter_write }, + { "syscalls:sys_enter_pwritev", process_enter_write }, + { "syscalls:sys_enter_sync", process_enter_sync }, + { "syscalls:sys_enter_sync_file_range", process_enter_sync }, + { "syscalls:sys_enter_fsync", process_enter_sync }, + { "syscalls:sys_enter_msync", process_enter_sync }, + { "syscalls:sys_enter_recvfrom", process_enter_rx }, + { "syscalls:sys_enter_recvmmsg", process_enter_rx }, + { "syscalls:sys_enter_recvmsg", process_enter_rx }, + { "syscalls:sys_enter_sendto", process_enter_tx }, + { "syscalls:sys_enter_sendmsg", process_enter_tx }, + { "syscalls:sys_enter_sendmmsg", process_enter_tx }, + { "syscalls:sys_enter_epoll_pwait", process_enter_poll }, + { "syscalls:sys_enter_epoll_wait", process_enter_poll }, + { "syscalls:sys_enter_poll", process_enter_poll }, + { "syscalls:sys_enter_ppoll", process_enter_poll }, + { "syscalls:sys_enter_pselect6", process_enter_poll }, + { "syscalls:sys_enter_select", process_enter_poll }, + + { "syscalls:sys_exit_read", process_exit_read }, + { "syscalls:sys_exit_pread64", process_exit_read }, + { "syscalls:sys_exit_readv", process_exit_read }, + { "syscalls:sys_exit_preadv", process_exit_read }, + { "syscalls:sys_exit_write", process_exit_write }, + { "syscalls:sys_exit_pwrite64", process_exit_write }, + { "syscalls:sys_exit_writev", process_exit_write }, + { "syscalls:sys_exit_pwritev", process_exit_write }, + { "syscalls:sys_exit_sync", process_exit_sync }, + { "syscalls:sys_exit_sync_file_range", process_exit_sync }, + { "syscalls:sys_exit_fsync", process_exit_sync }, + { "syscalls:sys_exit_msync", process_exit_sync }, + { "syscalls:sys_exit_recvfrom", process_exit_rx }, + { "syscalls:sys_exit_recvmmsg", process_exit_rx }, + { "syscalls:sys_exit_recvmsg", process_exit_rx }, + { "syscalls:sys_exit_sendto", process_exit_tx }, + { "syscalls:sys_exit_sendmsg", process_exit_tx }, + { "syscalls:sys_exit_sendmmsg", process_exit_tx }, + { "syscalls:sys_exit_epoll_pwait", process_exit_poll }, + { "syscalls:sys_exit_epoll_wait", process_exit_poll }, + { "syscalls:sys_exit_poll", process_exit_poll }, + { "syscalls:sys_exit_ppoll", process_exit_poll }, + { "syscalls:sys_exit_pselect6", process_exit_poll }, + { "syscalls:sys_exit_select", process_exit_poll }, }; struct perf_data_file file = { .path = input_name, @@ -1154,6 +1638,139 @@ out_delete: return ret; } +static int timechart__io_record(int argc, const char **argv) +{ + unsigned int rec_argc, i; + const char **rec_argv; + const char **p; + char *filter = NULL; + + const char * const common_args[] = { + "record", "-a", "-R", "-c", "1", + }; + unsigned int common_args_nr = ARRAY_SIZE(common_args); + + const char * const disk_events[] = { + "syscalls:sys_enter_read", + "syscalls:sys_enter_pread64", + "syscalls:sys_enter_readv", + "syscalls:sys_enter_preadv", + "syscalls:sys_enter_write", + "syscalls:sys_enter_pwrite64", + "syscalls:sys_enter_writev", + "syscalls:sys_enter_pwritev", + "syscalls:sys_enter_sync", + "syscalls:sys_enter_sync_file_range", + "syscalls:sys_enter_fsync", + "syscalls:sys_enter_msync", + + "syscalls:sys_exit_read", + "syscalls:sys_exit_pread64", + "syscalls:sys_exit_readv", + "syscalls:sys_exit_preadv", + "syscalls:sys_exit_write", + "syscalls:sys_exit_pwrite64", + "syscalls:sys_exit_writev", + "syscalls:sys_exit_pwritev", + "syscalls:sys_exit_sync", + "syscalls:sys_exit_sync_file_range", + "syscalls:sys_exit_fsync", + "syscalls:sys_exit_msync", + }; + unsigned int disk_events_nr = ARRAY_SIZE(disk_events); + + const char * const net_events[] = { + "syscalls:sys_enter_recvfrom", + "syscalls:sys_enter_recvmmsg", + "syscalls:sys_enter_recvmsg", + "syscalls:sys_enter_sendto", + "syscalls:sys_enter_sendmsg", + "syscalls:sys_enter_sendmmsg", + + "syscalls:sys_exit_recvfrom", + "syscalls:sys_exit_recvmmsg", + "syscalls:sys_exit_recvmsg", + "syscalls:sys_exit_sendto", + "syscalls:sys_exit_sendmsg", + "syscalls:sys_exit_sendmmsg", + }; + unsigned int net_events_nr = ARRAY_SIZE(net_events); + + const char * const poll_events[] = { + "syscalls:sys_enter_epoll_pwait", + "syscalls:sys_enter_epoll_wait", + "syscalls:sys_enter_poll", + "syscalls:sys_enter_ppoll", + "syscalls:sys_enter_pselect6", + "syscalls:sys_enter_select", + + "syscalls:sys_exit_epoll_pwait", + "syscalls:sys_exit_epoll_wait", + "syscalls:sys_exit_poll", + "syscalls:sys_exit_ppoll", + "syscalls:sys_exit_pselect6", + "syscalls:sys_exit_select", + }; + unsigned int poll_events_nr = ARRAY_SIZE(poll_events); + + rec_argc = common_args_nr + + disk_events_nr * 4 + + net_events_nr * 4 + + poll_events_nr * 4 + + argc; + rec_argv = calloc(rec_argc + 1, sizeof(char *)); + + if (rec_argv == NULL) + return -ENOMEM; + + if (asprintf(&filter, "common_pid != %d", getpid()) < 0) + return -ENOMEM; + + p = rec_argv; + for (i = 0; i < common_args_nr; i++) + *p++ = strdup(common_args[i]); + + for (i = 0; i < disk_events_nr; i++) { + if (!is_valid_tracepoint(disk_events[i])) { + rec_argc -= 4; + continue; + } + + *p++ = "-e"; + *p++ = strdup(disk_events[i]); + *p++ = "--filter"; + *p++ = filter; + } + for (i = 0; i < net_events_nr; i++) { + if (!is_valid_tracepoint(net_events[i])) { + rec_argc -= 4; + continue; + } + + *p++ = "-e"; + *p++ = strdup(net_events[i]); + *p++ = "--filter"; + *p++ = filter; + } + for (i = 0; i < poll_events_nr; i++) { + if (!is_valid_tracepoint(poll_events[i])) { + rec_argc -= 4; + continue; + } + + *p++ = "-e"; + *p++ = strdup(poll_events[i]); + *p++ = "--filter"; + *p++ = filter; + } + + for (i = 0; i < (unsigned int)argc; i++) + *p++ = argv[i]; + + return cmd_record(rec_argc, rec_argv, NULL); +} + + static int timechart__record(struct timechart *tchart, int argc, const char **argv) { unsigned int rec_argc, i, j; @@ -1270,6 +1887,30 @@ parse_highlight(const struct option *opt __maybe_unused, const char *arg, return 0; } +static int +parse_time(const struct option *opt, const char *arg, int __maybe_unused unset) +{ + char unit = 'n'; + u64 *value = opt->value; + + if (sscanf(arg, "%" PRIu64 "%cs", value, &unit) > 0) { + switch (unit) { + case 'm': + *value *= 1000000; + break; + case 'u': + *value *= 1000; + break; + case 'n': + break; + default: + return -1; + } + } + + return 0; +} + int cmd_timechart(int argc, const char **argv, const char *prefix __maybe_unused) { @@ -1282,6 +1923,8 @@ int cmd_timechart(int argc, const char **argv, .ordered_samples = true, }, .proc_num = 15, + .min_time = 1000000, + .merge_dist = 1000, }; const char *output_name = "output.svg"; const struct option timechart_options[] = { @@ -1303,6 +1946,14 @@ int cmd_timechart(int argc, const char **argv, "min. number of tasks to print"), OPT_BOOLEAN('t', "topology", &tchart.topology, "sort CPUs according to topology"), + OPT_BOOLEAN(0, "io-skip-eagain", &tchart.skip_eagain, + "skip EAGAIN errors"), + OPT_CALLBACK(0, "io-min-time", &tchart.min_time, "time", + "all IO faster than min-time will visually appear longer", + parse_time), + OPT_CALLBACK(0, "io-merge-dist", &tchart.merge_dist, "time", + "merge events that are merge-dist us apart", + parse_time), OPT_END() }; const char * const timechart_usage[] = { @@ -1314,6 +1965,8 @@ int cmd_timechart(int argc, const char **argv, OPT_BOOLEAN('P', "power-only", &tchart.power_only, "output power data only"), OPT_BOOLEAN('T', "tasks-only", &tchart.tasks_only, "output processes data only"), + OPT_BOOLEAN('I', "io-only", &tchart.io_only, + "record only IO data"), OPT_BOOLEAN('g', "callchain", &tchart.with_backtrace, "record callchain"), OPT_END() }; @@ -1340,7 +1993,10 @@ int cmd_timechart(int argc, const char **argv, return -1; } - return timechart__record(&tchart, argc, argv); + if (tchart.io_only) + return timechart__io_record(argc, argv); + else + return timechart__record(&tchart, argc, argv); } else if (argc) usage_with_options(timechart_usage, timechart_options); diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index 65aaa5bbf7ec..377971dc89a3 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -196,6 +196,12 @@ static void perf_top__record_precise_ip(struct perf_top *top, pthread_mutex_unlock(¬es->lock); + /* + * This function is now called with he->hists->lock held. + * Release it before going to sleep. + */ + pthread_mutex_unlock(&he->hists->lock); + if (err == -ERANGE && !he->ms.map->erange_warned) ui__warn_map_erange(he->ms.map, sym, ip); else if (err == -ENOMEM) { @@ -203,6 +209,8 @@ static void perf_top__record_precise_ip(struct perf_top *top, sym->name); sleep(1); } + + pthread_mutex_lock(&he->hists->lock); } static void perf_top__show_details(struct perf_top *top) @@ -238,24 +246,6 @@ out_unlock: pthread_mutex_unlock(¬es->lock); } -static struct hist_entry *perf_evsel__add_hist_entry(struct perf_evsel *evsel, - struct addr_location *al, - struct perf_sample *sample) -{ - struct hist_entry *he; - - pthread_mutex_lock(&evsel->hists.lock); - he = __hists__add_entry(&evsel->hists, al, NULL, NULL, NULL, - sample->period, sample->weight, - sample->transaction); - pthread_mutex_unlock(&evsel->hists.lock); - if (he == NULL) - return NULL; - - hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); - return he; -} - static void perf_top__print_sym_table(struct perf_top *top) { char bf[160]; @@ -659,6 +649,26 @@ static int symbol_filter(struct map *map __maybe_unused, struct symbol *sym) return 0; } +static int hist_iter__top_callback(struct hist_entry_iter *iter, + struct addr_location *al, bool single, + void *arg) +{ + struct perf_top *top = arg; + struct hist_entry *he = iter->he; + struct perf_evsel *evsel = iter->evsel; + + if (sort__has_sym && single) { + u64 ip = al->addr; + + if (al->map) + ip = al->map->unmap_ip(al->map, ip); + + perf_top__record_precise_ip(top, he, evsel->idx, ip); + } + + return 0; +} + static void perf_event__process_sample(struct perf_tool *tool, const union perf_event *event, struct perf_evsel *evsel, @@ -666,8 +676,6 @@ static void perf_event__process_sample(struct perf_tool *tool, struct machine *machine) { struct perf_top *top = container_of(tool, struct perf_top, tool); - struct symbol *parent = NULL; - u64 ip = sample->ip; struct addr_location al; int err; @@ -694,8 +702,7 @@ static void perf_event__process_sample(struct perf_tool *tool, if (event->header.misc & PERF_RECORD_MISC_EXACT_IP) top->exact_samples++; - if (perf_event__preprocess_sample(event, machine, &al, sample) < 0 || - al.filtered) + if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) return; if (!top->kptr_restrict_warned && @@ -743,25 +750,23 @@ static void perf_event__process_sample(struct perf_tool *tool, } if (al.sym == NULL || !al.sym->ignore) { - struct hist_entry *he; + struct hist_entry_iter iter = { + .add_entry_cb = hist_iter__top_callback, + }; - err = sample__resolve_callchain(sample, &parent, evsel, &al, - top->max_stack); - if (err) - return; + if (symbol_conf.cumulate_callchain) + iter.ops = &hist_iter_cumulative; + else + iter.ops = &hist_iter_normal; - he = perf_evsel__add_hist_entry(evsel, &al, sample); - if (he == NULL) { - pr_err("Problem incrementing symbol period, skipping event\n"); - return; - } + pthread_mutex_lock(&evsel->hists.lock); - err = hist_entry__append_callchain(he, sample); - if (err) - return; + err = hist_entry_iter__add(&iter, &al, evsel, sample, + top->max_stack, top); + if (err < 0) + pr_err("Problem incrementing symbol period, skipping event\n"); - if (sort__has_sym) - perf_top__record_precise_ip(top, he, evsel->idx, ip); + pthread_mutex_unlock(&evsel->hists.lock); } return; @@ -999,6 +1004,10 @@ static int perf_top_config(const char *var, const char *value, void *cb) if (!strcmp(var, "top.call-graph")) return record_parse_callchain(value, &top->record_opts); + if (!strcmp(var, "top.children")) { + symbol_conf.cumulate_callchain = perf_config_bool(var, value); + return 0; + } return perf_default_config(var, value, cb); } @@ -1081,8 +1090,10 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) OPT_INCR('v', "verbose", &verbose, "be more verbose (show counter open errors, etc)"), OPT_STRING('s', "sort", &sort_order, "key[,key2...]", - "sort by key(s): pid, comm, dso, symbol, parent, weight, local_weight," - " abort, in_tx, transaction"), + "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..." + " Please refer the man page for the complete list."), + OPT_STRING(0, "fields", &field_order, "key[,keys...]", + "output field(s): overhead, period, sample plus all of sort keys"), OPT_BOOLEAN('n', "show-nr-samples", &symbol_conf.show_nr_samples, "Show a column with the number of samples"), OPT_CALLBACK_NOOPT('g', NULL, &top.record_opts, @@ -1091,6 +1102,8 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) OPT_CALLBACK(0, "call-graph", &top.record_opts, "mode[,dump_size]", record_callchain_help, &parse_callchain_opt), + OPT_BOOLEAN(0, "children", &symbol_conf.cumulate_callchain, + "Accumulate callchains of children and show total overhead as well"), OPT_INTEGER(0, "max-stack", &top.max_stack, "Set the maximum stack depth when parsing the callchain. " "Default: " __stringify(PERF_MAX_STACK_DEPTH)), @@ -1116,6 +1129,8 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) OPT_STRING('u', "uid", &target->uid_str, "user", "user to profile"), OPT_CALLBACK(0, "percent-limit", &top, "percent", "Don't show entries under that percent", parse_percent_limit), + OPT_CALLBACK(0, "percentage", NULL, "relative|absolute", + "How to display percentage of filtered entries", parse_filter_percentage), OPT_END() }; const char * const top_usage[] = { @@ -1133,17 +1148,19 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) if (argc) usage_with_options(top_usage, options); - if (sort_order == default_sort_order) - sort_order = "dso,symbol"; + sort__mode = SORT_MODE__TOP; + /* display thread wants entries to be collapsed in a different tree */ + sort__need_collapse = 1; if (setup_sorting() < 0) { - parse_options_usage(top_usage, options, "s", 1); + if (sort_order) + parse_options_usage(top_usage, options, "s", 1); + if (field_order) + parse_options_usage(sort_order ? NULL : top_usage, + options, "fields", 0); goto out_delete_evlist; } - /* display thread wants entries to be collapsed in a different tree */ - sort__need_collapse = 1; - if (top.use_stdio) use_browser = 0; else if (top.use_tui) @@ -1192,6 +1209,11 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) top.sym_evsel = perf_evlist__first(top.evlist); + if (!symbol_conf.use_callchain) { + symbol_conf.cumulate_callchain = false; + perf_hpp__cancel_cumulate(); + } + symbol_conf.priv_size = sizeof(struct annotation); symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL); diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index f954c26de231..a6c375224f46 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -1108,6 +1108,7 @@ struct syscall { struct event_format *tp_format; const char *name; bool filtered; + bool is_exit; struct syscall_fmt *fmt; size_t (**arg_scnprintf)(char *bf, size_t size, struct syscall_arg *arg); void **arg_parm; @@ -1132,6 +1133,7 @@ struct thread_trace { u64 exit_time; bool entry_pending; unsigned long nr_events; + unsigned long pfmaj, pfmin; char *entry_str; double runtime_ms; struct { @@ -1177,6 +1179,9 @@ fail: return NULL; } +#define TRACE_PFMAJ (1 << 0) +#define TRACE_PFMIN (1 << 1) + struct trace { struct perf_tool tool; struct { @@ -1211,6 +1216,8 @@ struct trace { bool summary_only; bool show_comm; bool show_tool_stats; + bool trace_syscalls; + int trace_pgfaults; }; static int trace__set_fd_pathname(struct thread *thread, int fd, const char *pathname) @@ -1276,11 +1283,11 @@ static const char *thread__fd_path(struct thread *thread, int fd, if (fd < 0) return NULL; - if ((fd > ttrace->paths.max || ttrace->paths.table[fd] == NULL)) + if ((fd > ttrace->paths.max || ttrace->paths.table[fd] == NULL)) { if (!trace->live) return NULL; ++trace->stats.proc_getname; - if (thread__read_fd_path(thread, fd)) { + if (thread__read_fd_path(thread, fd)) return NULL; } @@ -1473,6 +1480,8 @@ static int trace__read_syscall_info(struct trace *trace, int id) if (sc->tp_format == NULL) return -1; + sc->is_exit = !strcmp(name, "exit_group") || !strcmp(name, "exit"); + return syscall__set_arg_fmts(sc); } @@ -1535,6 +1544,7 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, } typedef int (*tracepoint_handler)(struct trace *trace, struct perf_evsel *evsel, + union perf_event *event, struct perf_sample *sample); static struct syscall *trace__syscall_info(struct trace *trace, @@ -1607,6 +1617,7 @@ static void thread__update_stats(struct thread_trace *ttrace, } static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel, + union perf_event *event __maybe_unused, struct perf_sample *sample) { char *msg; @@ -1629,7 +1640,6 @@ static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel, return -1; args = perf_evsel__sc_tp_ptr(evsel, args, sample); - ttrace = thread->priv; if (ttrace->entry_str == NULL) { ttrace->entry_str = malloc(1024); @@ -1644,7 +1654,7 @@ static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel, printed += syscall__scnprintf_args(sc, msg + printed, 1024 - printed, args, trace, thread); - if (!strcmp(sc->name, "exit_group") || !strcmp(sc->name, "exit")) { + if (sc->is_exit) { if (!trace->duration_filter && !trace->summary_only) { trace__fprintf_entry_head(trace, thread, 1, sample->time, trace->output); fprintf(trace->output, "%-70s\n", ttrace->entry_str); @@ -1656,6 +1666,7 @@ static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel, } static int trace__sys_exit(struct trace *trace, struct perf_evsel *evsel, + union perf_event *event __maybe_unused, struct perf_sample *sample) { int ret; @@ -1687,8 +1698,6 @@ static int trace__sys_exit(struct trace *trace, struct perf_evsel *evsel, ++trace->stats.vfs_getname; } - ttrace = thread->priv; - ttrace->exit_time = sample->time; if (ttrace->entry_time) { @@ -1735,6 +1744,7 @@ out: } static int trace__vfs_getname(struct trace *trace, struct perf_evsel *evsel, + union perf_event *event __maybe_unused, struct perf_sample *sample) { trace->last_vfs_getname = perf_evsel__rawptr(evsel, sample, "pathname"); @@ -1742,6 +1752,7 @@ static int trace__vfs_getname(struct trace *trace, struct perf_evsel *evsel, } static int trace__sched_stat_runtime(struct trace *trace, struct perf_evsel *evsel, + union perf_event *event __maybe_unused, struct perf_sample *sample) { u64 runtime = perf_evsel__intval(evsel, sample, "runtime"); @@ -1768,6 +1779,80 @@ out_dump: return 0; } +static void print_location(FILE *f, struct perf_sample *sample, + struct addr_location *al, + bool print_dso, bool print_sym) +{ + + if ((verbose || print_dso) && al->map) + fprintf(f, "%s@", al->map->dso->long_name); + + if ((verbose || print_sym) && al->sym) + fprintf(f, "%s+0x%" PRIx64, al->sym->name, + al->addr - al->sym->start); + else if (al->map) + fprintf(f, "0x%" PRIx64, al->addr); + else + fprintf(f, "0x%" PRIx64, sample->addr); +} + +static int trace__pgfault(struct trace *trace, + struct perf_evsel *evsel, + union perf_event *event, + struct perf_sample *sample) +{ + struct thread *thread; + u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + struct addr_location al; + char map_type = 'd'; + struct thread_trace *ttrace; + + thread = machine__findnew_thread(trace->host, sample->pid, sample->tid); + ttrace = thread__trace(thread, trace->output); + if (ttrace == NULL) + return -1; + + if (evsel->attr.config == PERF_COUNT_SW_PAGE_FAULTS_MAJ) + ttrace->pfmaj++; + else + ttrace->pfmin++; + + if (trace->summary_only) + return 0; + + thread__find_addr_location(thread, trace->host, cpumode, MAP__FUNCTION, + sample->ip, &al); + + trace__fprintf_entry_head(trace, thread, 0, sample->time, trace->output); + + fprintf(trace->output, "%sfault [", + evsel->attr.config == PERF_COUNT_SW_PAGE_FAULTS_MAJ ? + "maj" : "min"); + + print_location(trace->output, sample, &al, false, true); + + fprintf(trace->output, "] => "); + + thread__find_addr_location(thread, trace->host, cpumode, MAP__VARIABLE, + sample->addr, &al); + + if (!al.map) { + thread__find_addr_location(thread, trace->host, cpumode, + MAP__FUNCTION, sample->addr, &al); + + if (al.map) + map_type = 'x'; + else + map_type = '?'; + } + + print_location(trace->output, sample, &al, true, false); + + fprintf(trace->output, " (%c%c)\n", map_type, al.level); + + return 0; +} + static bool skip_sample(struct trace *trace, struct perf_sample *sample) { if ((trace->pid_list && intlist__find(trace->pid_list, sample->pid)) || @@ -1781,7 +1866,7 @@ static bool skip_sample(struct trace *trace, struct perf_sample *sample) } static int trace__process_sample(struct perf_tool *tool, - union perf_event *event __maybe_unused, + union perf_event *event, struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine __maybe_unused) @@ -1799,7 +1884,7 @@ static int trace__process_sample(struct perf_tool *tool, if (handler) { ++trace->nr_events; - handler(trace, evsel, sample); + handler(trace, evsel, event, sample); } return err; @@ -1826,7 +1911,7 @@ static int parse_target_str(struct trace *trace) return 0; } -static int trace__record(int argc, const char **argv) +static int trace__record(struct trace *trace, int argc, const char **argv) { unsigned int rec_argc, i, j; const char **rec_argv; @@ -1835,34 +1920,54 @@ static int trace__record(int argc, const char **argv) "-R", "-m", "1024", "-c", "1", - "-e", }; + const char * const sc_args[] = { "-e", }; + unsigned int sc_args_nr = ARRAY_SIZE(sc_args); + const char * const majpf_args[] = { "-e", "major-faults" }; + unsigned int majpf_args_nr = ARRAY_SIZE(majpf_args); + const char * const minpf_args[] = { "-e", "minor-faults" }; + unsigned int minpf_args_nr = ARRAY_SIZE(minpf_args); + /* +1 is for the event string below */ - rec_argc = ARRAY_SIZE(record_args) + 1 + argc; + rec_argc = ARRAY_SIZE(record_args) + sc_args_nr + 1 + + majpf_args_nr + minpf_args_nr + argc; rec_argv = calloc(rec_argc + 1, sizeof(char *)); if (rec_argv == NULL) return -ENOMEM; + j = 0; for (i = 0; i < ARRAY_SIZE(record_args); i++) - rec_argv[i] = record_args[i]; - - /* event string may be different for older kernels - e.g., RHEL6 */ - if (is_valid_tracepoint("raw_syscalls:sys_enter")) - rec_argv[i] = "raw_syscalls:sys_enter,raw_syscalls:sys_exit"; - else if (is_valid_tracepoint("syscalls:sys_enter")) - rec_argv[i] = "syscalls:sys_enter,syscalls:sys_exit"; - else { - pr_err("Neither raw_syscalls nor syscalls events exist.\n"); - return -1; + rec_argv[j++] = record_args[i]; + + if (trace->trace_syscalls) { + for (i = 0; i < sc_args_nr; i++) + rec_argv[j++] = sc_args[i]; + + /* event string may be different for older kernels - e.g., RHEL6 */ + if (is_valid_tracepoint("raw_syscalls:sys_enter")) + rec_argv[j++] = "raw_syscalls:sys_enter,raw_syscalls:sys_exit"; + else if (is_valid_tracepoint("syscalls:sys_enter")) + rec_argv[j++] = "syscalls:sys_enter,syscalls:sys_exit"; + else { + pr_err("Neither raw_syscalls nor syscalls events exist.\n"); + return -1; + } } - i++; - for (j = 0; j < (unsigned int)argc; j++, i++) - rec_argv[i] = argv[j]; + if (trace->trace_pgfaults & TRACE_PFMAJ) + for (i = 0; i < majpf_args_nr; i++) + rec_argv[j++] = majpf_args[i]; + + if (trace->trace_pgfaults & TRACE_PFMIN) + for (i = 0; i < minpf_args_nr; i++) + rec_argv[j++] = minpf_args[i]; + + for (i = 0; i < (unsigned int)argc; i++) + rec_argv[j++] = argv[i]; - return cmd_record(i, rec_argv, NULL); + return cmd_record(j, rec_argv, NULL); } static size_t trace__fprintf_thread_summary(struct trace *trace, FILE *fp); @@ -1882,6 +1987,30 @@ static void perf_evlist__add_vfs_getname(struct perf_evlist *evlist) perf_evlist__add(evlist, evsel); } +static int perf_evlist__add_pgfault(struct perf_evlist *evlist, + u64 config) +{ + struct perf_evsel *evsel; + struct perf_event_attr attr = { + .type = PERF_TYPE_SOFTWARE, + .mmap_data = 1, + }; + + attr.config = config; + attr.sample_period = 1; + + event_attr_init(&attr); + + evsel = perf_evsel__new(&attr); + if (!evsel) + return -ENOMEM; + + evsel->handler = trace__pgfault; + perf_evlist__add(evlist, evsel); + + return 0; +} + static int trace__run(struct trace *trace, int argc, const char **argv) { struct perf_evlist *evlist = perf_evlist__new(); @@ -1897,10 +2026,21 @@ static int trace__run(struct trace *trace, int argc, const char **argv) goto out; } - if (perf_evlist__add_syscall_newtp(evlist, trace__sys_enter, trace__sys_exit)) + if (trace->trace_syscalls && + perf_evlist__add_syscall_newtp(evlist, trace__sys_enter, + trace__sys_exit)) goto out_error_tp; - perf_evlist__add_vfs_getname(evlist); + if (trace->trace_syscalls) + perf_evlist__add_vfs_getname(evlist); + + if ((trace->trace_pgfaults & TRACE_PFMAJ) && + perf_evlist__add_pgfault(evlist, PERF_COUNT_SW_PAGE_FAULTS_MAJ)) + goto out_error_tp; + + if ((trace->trace_pgfaults & TRACE_PFMIN) && + perf_evlist__add_pgfault(evlist, PERF_COUNT_SW_PAGE_FAULTS_MIN)) + goto out_error_tp; if (trace->sched && perf_evlist__add_newtp(evlist, "sched", "sched_stat_runtime", @@ -1982,7 +2122,8 @@ again: goto next_event; } - if (sample.raw_data == NULL) { + if (evsel->attr.type == PERF_TYPE_TRACEPOINT && + sample.raw_data == NULL) { fprintf(trace->output, "%s sample with no payload for tid: %d, cpu %d, raw_size=%d, skipping...\n", perf_evsel__name(evsel), sample.tid, sample.cpu, sample.raw_size); @@ -1990,7 +2131,7 @@ again: } handler = evsel->handler; - handler(trace, evsel, &sample); + handler(trace, evsel, event, &sample); next_event: perf_evlist__mmap_consume(evlist, i); @@ -2093,13 +2234,10 @@ static int trace__replay(struct trace *trace) if (evsel == NULL) evsel = perf_evlist__find_tracepoint_by_name(session->evlist, "syscalls:sys_enter"); - if (evsel == NULL) { - pr_err("Data file does not have raw_syscalls:sys_enter event\n"); - goto out; - } - if (perf_evsel__init_syscall_tp(evsel, trace__sys_enter) < 0 || - perf_evsel__init_sc_tp_ptr_field(evsel, args)) { + if (evsel && + (perf_evsel__init_syscall_tp(evsel, trace__sys_enter) < 0 || + perf_evsel__init_sc_tp_ptr_field(evsel, args))) { pr_err("Error during initialize raw_syscalls:sys_enter event\n"); goto out; } @@ -2109,15 +2247,19 @@ static int trace__replay(struct trace *trace) if (evsel == NULL) evsel = perf_evlist__find_tracepoint_by_name(session->evlist, "syscalls:sys_exit"); - if (evsel == NULL) { - pr_err("Data file does not have raw_syscalls:sys_exit event\n"); + if (evsel && + (perf_evsel__init_syscall_tp(evsel, trace__sys_exit) < 0 || + perf_evsel__init_sc_tp_uint_field(evsel, ret))) { + pr_err("Error during initialize raw_syscalls:sys_exit event\n"); goto out; } - if (perf_evsel__init_syscall_tp(evsel, trace__sys_exit) < 0 || - perf_evsel__init_sc_tp_uint_field(evsel, ret)) { - pr_err("Error during initialize raw_syscalls:sys_exit event\n"); - goto out; + evlist__for_each(session->evlist, evsel) { + if (evsel->attr.type == PERF_TYPE_SOFTWARE && + (evsel->attr.config == PERF_COUNT_SW_PAGE_FAULTS_MAJ || + evsel->attr.config == PERF_COUNT_SW_PAGE_FAULTS_MIN || + evsel->attr.config == PERF_COUNT_SW_PAGE_FAULTS)) + evsel->handler = trace__pgfault; } err = parse_target_str(trace); @@ -2217,6 +2359,10 @@ static int trace__fprintf_one_thread(struct thread *thread, void *priv) printed += fprintf(fp, " %s (%d), ", thread__comm_str(thread), thread->tid); printed += fprintf(fp, "%lu events, ", ttrace->nr_events); printed += fprintf(fp, "%.1f%%", ratio); + if (ttrace->pfmaj) + printed += fprintf(fp, ", %lu majfaults", ttrace->pfmaj); + if (ttrace->pfmin) + printed += fprintf(fp, ", %lu minfaults", ttrace->pfmin); printed += fprintf(fp, ", %.3f msec\n", ttrace->runtime_ms); printed += thread__dump_stats(ttrace, trace, fp); @@ -2264,6 +2410,23 @@ static int trace__open_output(struct trace *trace, const char *filename) return trace->output == NULL ? -errno : 0; } +static int parse_pagefaults(const struct option *opt, const char *str, + int unset __maybe_unused) +{ + int *trace_pgfaults = opt->value; + + if (strcmp(str, "all") == 0) + *trace_pgfaults |= TRACE_PFMAJ | TRACE_PFMIN; + else if (strcmp(str, "maj") == 0) + *trace_pgfaults |= TRACE_PFMAJ; + else if (strcmp(str, "min") == 0) + *trace_pgfaults |= TRACE_PFMIN; + else + return -1; + + return 0; +} + int cmd_trace(int argc, const char **argv, const char *prefix __maybe_unused) { const char * const trace_usage[] = { @@ -2293,6 +2456,7 @@ int cmd_trace(int argc, const char **argv, const char *prefix __maybe_unused) }, .output = stdout, .show_comm = true, + .trace_syscalls = true, }; const char *output_name = NULL; const char *ev_qualifier_str = NULL; @@ -2330,20 +2494,34 @@ int cmd_trace(int argc, const char **argv, const char *prefix __maybe_unused) "Show only syscall summary with statistics"), OPT_BOOLEAN('S', "with-summary", &trace.summary, "Show all syscalls and summary with statistics"), + OPT_CALLBACK_DEFAULT('F', "pf", &trace.trace_pgfaults, "all|maj|min", + "Trace pagefaults", parse_pagefaults, "maj"), + OPT_BOOLEAN(0, "syscalls", &trace.trace_syscalls, "Trace syscalls"), OPT_END() }; int err; char bf[BUFSIZ]; - if ((argc > 1) && (strcmp(argv[1], "record") == 0)) - return trace__record(argc-2, &argv[2]); + argc = parse_options(argc, argv, trace_options, trace_usage, + PARSE_OPT_STOP_AT_NON_OPTION); - argc = parse_options(argc, argv, trace_options, trace_usage, 0); + if (trace.trace_pgfaults) { + trace.opts.sample_address = true; + trace.opts.sample_time = true; + } + + if ((argc >= 1) && (strcmp(argv[0], "record") == 0)) + return trace__record(&trace, argc-1, &argv[1]); /* summary_only implies summary option, but don't overwrite summary if set */ if (trace.summary_only) trace.summary = trace.summary_only; + if (!trace.trace_syscalls && !trace.trace_pgfaults) { + pr_err("Please specify something to trace.\n"); + return -1; + } + if (output_name != NULL) { err = trace__open_output(&trace, output_name); if (err < 0) { diff --git a/tools/perf/config/Makefile b/tools/perf/config/Makefile index 802cf544202b..1f67aa02d240 100644 --- a/tools/perf/config/Makefile +++ b/tools/perf/config/Makefile @@ -29,19 +29,29 @@ ifeq ($(ARCH),x86) endif NO_PERF_REGS := 0 endif + ifeq ($(ARCH),arm) NO_PERF_REGS := 0 LIBUNWIND_LIBS = -lunwind -lunwind-arm endif -# So far there's only x86 libdw unwind support merged in perf. +ifeq ($(ARCH),arm64) + NO_PERF_REGS := 0 + LIBUNWIND_LIBS = -lunwind -lunwind-aarch64 +endif + +# So far there's only x86 and arm libdw unwind support merged in perf. # Disable it on all other architectures in case libdw unwind # support is detected in system. Add supported architectures # to the check. -ifneq ($(ARCH),x86) +ifneq ($(ARCH),$(filter $(ARCH),x86 arm)) NO_LIBDW_DWARF_UNWIND := 1 endif +ifeq ($(ARCH),powerpc) + CFLAGS += -DHAVE_SKIP_CALLCHAIN_IDX +endif + ifeq ($(LIBUNWIND_LIBS),) NO_LIBUNWIND := 1 else @@ -154,6 +164,7 @@ CORE_FEATURE_TESTS = \ backtrace \ dwarf \ fortify-source \ + sync-compare-and-swap \ glibc \ gtk2 \ gtk2-infobar \ @@ -168,7 +179,6 @@ CORE_FEATURE_TESTS = \ libpython-version \ libslang \ libunwind \ - on-exit \ stackprotector-all \ timerfd \ libdw-dwarf-unwind @@ -190,11 +200,11 @@ LIB_FEATURE_TESTS = \ VF_FEATURE_TESTS = \ backtrace \ fortify-source \ + sync-compare-and-swap \ gtk2-infobar \ libelf-getphdrnum \ libelf-mmap \ libpython-version \ - on-exit \ stackprotector-all \ timerfd \ libunwind-debug-frame \ @@ -264,6 +274,10 @@ CFLAGS += -I$(LIB_INCLUDE) CFLAGS += -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE +ifeq ($(feature-sync-compare-and-swap), 1) + CFLAGS += -DHAVE_SYNC_COMPARE_AND_SWAP_SUPPORT +endif + ifndef NO_BIONIC $(call feature_check,bionic) ifeq ($(feature-bionic), 1) @@ -295,7 +309,11 @@ else NO_LIBUNWIND := 1 NO_LIBDW_DWARF_UNWIND := 1 else - msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]/glibc-static); + ifneq ($(filter s% -static%,$(LDFLAGS),),) + msg := $(error No static glibc found, please install glibc-static); + else + msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]); + endif endif else ifndef NO_LIBDW_DWARF_UNWIND @@ -370,7 +388,7 @@ else endif ifndef NO_LIBUNWIND - ifeq ($(ARCH),arm) + ifeq ($(ARCH),$(filter $(ARCH),arm arm64)) $(call feature_check,libunwind-debug-frame) ifneq ($(feature-libunwind-debug-frame), 1) msg := $(warning No debug_frame support found in libunwind); @@ -443,6 +461,7 @@ else ifneq ($(feature-libperl), 1) CFLAGS += -DNO_LIBPERL NO_LIBPERL := 1 + msg := $(warning Missing perl devel files. Disabling perl scripting support, consider installing perl-ExtUtils-Embed); else LDFLAGS += $(PERL_EMBED_LDFLAGS) EXTLIBS += $(PERL_EMBED_LIBADD) @@ -565,12 +584,6 @@ ifneq ($(filter -lbfd,$(EXTLIBS)),) CFLAGS += -DHAVE_LIBBFD_SUPPORT endif -ifndef NO_ON_EXIT - ifeq ($(feature-on-exit), 1) - CFLAGS += -DHAVE_ON_EXIT_SUPPORT - endif -endif - ifndef NO_BACKTRACE ifeq ($(feature-backtrace), 1) CFLAGS += -DHAVE_BACKTRACE_SUPPORT @@ -587,6 +600,10 @@ ifndef NO_LIBNUMA endif endif +ifdef HAVE_KVM_STAT_SUPPORT + CFLAGS += -DHAVE_KVM_STAT_SUPPORT +endif + # Among the variables below, these: # perfexecdir # template_dir @@ -601,7 +618,7 @@ endif # Make the path relative to DESTDIR, not to prefix ifndef DESTDIR -prefix = $(HOME) +prefix ?= $(HOME) endif bindir_relative = bin bindir = $(prefix)/$(bindir_relative) diff --git a/tools/perf/config/feature-checks/Makefile b/tools/perf/config/feature-checks/Makefile index 2da103c53f89..6088f8d8a434 100644 --- a/tools/perf/config/feature-checks/Makefile +++ b/tools/perf/config/feature-checks/Makefile @@ -5,6 +5,7 @@ FILES= \ test-bionic.bin \ test-dwarf.bin \ test-fortify-source.bin \ + test-sync-compare-and-swap.bin \ test-glibc.bin \ test-gtk2.bin \ test-gtk2-infobar.bin \ @@ -24,7 +25,6 @@ FILES= \ test-libslang.bin \ test-libunwind.bin \ test-libunwind-debug-frame.bin \ - test-on-exit.bin \ test-stackprotector-all.bin \ test-timerfd.bin \ test-libdw-dwarf-unwind.bin @@ -133,9 +133,6 @@ test-liberty-z.bin: test-cplus-demangle.bin: $(BUILD) -liberty -test-on-exit.bin: - $(BUILD) - test-backtrace.bin: $(BUILD) @@ -145,6 +142,9 @@ test-timerfd.bin: test-libdw-dwarf-unwind.bin: $(BUILD) +test-sync-compare-and-swap.bin: + $(BUILD) -Werror + -include *.d ############################### diff --git a/tools/perf/config/feature-checks/test-all.c b/tools/perf/config/feature-checks/test-all.c index fc37eb3ca17b..a7d022e161c0 100644 --- a/tools/perf/config/feature-checks/test-all.c +++ b/tools/perf/config/feature-checks/test-all.c @@ -69,10 +69,6 @@ # include "test-libbfd.c" #undef main -#define main main_test_on_exit -# include "test-on-exit.c" -#undef main - #define main main_test_backtrace # include "test-backtrace.c" #undef main @@ -93,6 +89,10 @@ # include "test-libdw-dwarf-unwind.c" #undef main +#define main main_test_sync_compare_and_swap +# include "test-sync-compare-and-swap.c" +#undef main + int main(int argc, char *argv[]) { main_test_libpython(); @@ -110,12 +110,12 @@ int main(int argc, char *argv[]) main_test_gtk2(argc, argv); main_test_gtk2_infobar(argc, argv); main_test_libbfd(); - main_test_on_exit(); main_test_backtrace(); main_test_libnuma(); main_test_timerfd(); main_test_stackprotector_all(); main_test_libdw_dwarf_unwind(); + main_test_sync_compare_and_swap(argc, argv); return 0; } diff --git a/tools/perf/config/feature-checks/test-on-exit.c b/tools/perf/config/feature-checks/test-on-exit.c deleted file mode 100644 index 8e88b16e6ded..000000000000 --- a/tools/perf/config/feature-checks/test-on-exit.c +++ /dev/null @@ -1,16 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> - -static void exit_fn(int status, void *__data) -{ - printf("exit status: %d, data: %d\n", status, *(int *)__data); -} - -static int data = 123; - -int main(void) -{ - on_exit(exit_fn, &data); - - return 321; -} diff --git a/tools/perf/config/feature-checks/test-sync-compare-and-swap.c b/tools/perf/config/feature-checks/test-sync-compare-and-swap.c new file mode 100644 index 000000000000..c34d4ca4af56 --- /dev/null +++ b/tools/perf/config/feature-checks/test-sync-compare-and-swap.c @@ -0,0 +1,14 @@ +#include <stdint.h> + +volatile uint64_t x; + +int main(int argc, char *argv[]) +{ + uint64_t old, new = argc; + + argv = argv; + do { + old = __sync_val_compare_and_swap(&x, 0, 0); + } while (!__sync_bool_compare_and_swap(&x, old, new)); + return old == new; +} diff --git a/tools/perf/perf-completion.sh b/tools/perf/perf-completion.sh index ae3a57694b6b..33569847fdcc 100644 --- a/tools/perf/perf-completion.sh +++ b/tools/perf/perf-completion.sh @@ -121,8 +121,8 @@ __perf_main () elif [[ $prev == "-e" && "${words[1]}" == @(record|stat|top) ]]; then evts=$($cmd list --raw-dump) __perfcomp_colon "$evts" "$cur" - # List subcommands for 'perf kvm' - elif [[ $prev == "kvm" ]]; then + # List subcommands for perf commands + elif [[ $prev == @(kvm|kmem|mem|lock|sched) ]]; then subcmds=$($cmd $prev --list-cmds) __perfcomp_colon "$subcmds" "$cur" # List long option names diff --git a/tools/perf/perf-sys.h b/tools/perf/perf-sys.h new file mode 100644 index 000000000000..937e4324ad94 --- /dev/null +++ b/tools/perf/perf-sys.h @@ -0,0 +1,191 @@ +#ifndef _PERF_SYS_H +#define _PERF_SYS_H + +#include <unistd.h> +#include <sys/types.h> +#include <sys/syscall.h> +#include <linux/types.h> +#include <linux/perf_event.h> +#include <asm/unistd.h> + +#if defined(__i386__) +#define mb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory") +#define wmb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory") +#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 +#ifndef __NR_futex +# define __NR_futex 240 +#endif +#ifndef __NR_gettid +# define __NR_gettid 224 +#endif +#endif + +#if defined(__x86_64__) +#define mb() asm volatile("mfence" ::: "memory") +#define wmb() asm volatile("sfence" ::: "memory") +#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 +#ifndef __NR_futex +# define __NR_futex 202 +#endif +#ifndef __NR_gettid +# define __NR_gettid 186 +#endif +#endif + +#ifdef __powerpc__ +#include "../../arch/powerpc/include/uapi/asm/unistd.h" +#define mb() asm volatile ("sync" ::: "memory") +#define wmb() asm volatile ("sync" ::: "memory") +#define rmb() asm volatile ("sync" ::: "memory") +#define CPUINFO_PROC "cpu" +#endif + +#ifdef __s390__ +#define mb() asm volatile("bcr 15,0" ::: "memory") +#define wmb() asm volatile("bcr 15,0" ::: "memory") +#define rmb() asm volatile("bcr 15,0" ::: "memory") +#define CPUINFO_PROC "vendor_id" +#endif + +#ifdef __sh__ +#if defined(__SH4A__) || defined(__SH5__) +# define mb() asm volatile("synco" ::: "memory") +# define wmb() asm volatile("synco" ::: "memory") +# define rmb() asm volatile("synco" ::: "memory") +#else +# define mb() asm volatile("" ::: "memory") +# define wmb() asm volatile("" ::: "memory") +# define rmb() asm volatile("" ::: "memory") +#endif +#define CPUINFO_PROC "cpu type" +#endif + +#ifdef __hppa__ +#define mb() asm volatile("" ::: "memory") +#define wmb() asm volatile("" ::: "memory") +#define rmb() asm volatile("" ::: "memory") +#define CPUINFO_PROC "cpu" +#endif + +#ifdef __sparc__ +#ifdef __LP64__ +#define mb() asm volatile("ba,pt %%xcc, 1f\n" \ + "membar #StoreLoad\n" \ + "1:\n":::"memory") +#else +#define mb() asm volatile("":::"memory") +#endif +#define wmb() asm volatile("":::"memory") +#define rmb() asm volatile("":::"memory") +#define CPUINFO_PROC "cpu" +#endif + +#ifdef __alpha__ +#define mb() asm volatile("mb" ::: "memory") +#define wmb() asm volatile("wmb" ::: "memory") +#define rmb() asm volatile("mb" ::: "memory") +#define CPUINFO_PROC "cpu model" +#endif + +#ifdef __ia64__ +#define mb() asm volatile ("mf" ::: "memory") +#define wmb() asm volatile ("mf" ::: "memory") +#define rmb() asm volatile ("mf" ::: "memory") +#define cpu_relax() asm volatile ("hint @pause" ::: "memory") +#define CPUINFO_PROC "model name" +#endif + +#ifdef __arm__ +/* + * Use the __kuser_memory_barrier helper in the CPU helper page. See + * arch/arm/kernel/entry-armv.S in the kernel source for details. + */ +#define mb() ((void(*)(void))0xffff0fa0)() +#define wmb() ((void(*)(void))0xffff0fa0)() +#define rmb() ((void(*)(void))0xffff0fa0)() +#define CPUINFO_PROC "Processor" +#endif + +#ifdef __aarch64__ +#define mb() asm volatile("dmb ish" ::: "memory") +#define wmb() asm volatile("dmb ishst" ::: "memory") +#define rmb() asm volatile("dmb ishld" ::: "memory") +#define cpu_relax() asm volatile("yield" ::: "memory") +#endif + +#ifdef __mips__ +#define mb() asm volatile( \ + ".set mips2\n\t" \ + "sync\n\t" \ + ".set mips0" \ + : /* no output */ \ + : /* no input */ \ + : "memory") +#define wmb() mb() +#define rmb() mb() +#define CPUINFO_PROC "cpu model" +#endif + +#ifdef __arc__ +#define mb() asm volatile("" ::: "memory") +#define wmb() asm volatile("" ::: "memory") +#define rmb() asm volatile("" ::: "memory") +#define CPUINFO_PROC "Processor" +#endif + +#ifdef __metag__ +#define mb() asm volatile("" ::: "memory") +#define wmb() asm volatile("" ::: "memory") +#define rmb() asm volatile("" ::: "memory") +#define CPUINFO_PROC "CPU" +#endif + +#ifdef __xtensa__ +#define mb() asm volatile("memw" ::: "memory") +#define wmb() asm volatile("memw" ::: "memory") +#define rmb() asm volatile("" ::: "memory") +#define CPUINFO_PROC "core ID" +#endif + +#ifdef __tile__ +#define mb() asm volatile ("mf" ::: "memory") +#define wmb() asm volatile ("mf" ::: "memory") +#define rmb() asm volatile ("mf" ::: "memory") +#define cpu_relax() asm volatile ("mfspr zero, PASS" ::: "memory") +#define CPUINFO_PROC "model name" +#endif + +#define barrier() asm volatile ("" ::: "memory") + +#ifndef cpu_relax +#define cpu_relax() barrier() +#endif + +static inline int +sys_perf_event_open(struct perf_event_attr *attr, + pid_t pid, int cpu, int group_fd, + unsigned long flags) +{ + int fd; + + fd = syscall(__NR_perf_event_open, attr, pid, cpu, + group_fd, flags); + +#ifdef HAVE_ATTR_TEST + if (unlikely(test_attr__enabled)) + test_attr__open(attr, pid, cpu, fd, group_fd, flags); +#endif + return fd; +} + +#endif /* _PERF_SYS_H */ diff --git a/tools/perf/perf.c b/tools/perf/perf.c index 431798a4110d..2282d41879a2 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -13,11 +13,12 @@ #include "util/quote.h" #include "util/run-command.h" #include "util/parse-events.h" +#include "util/debug.h" #include <api/fs/debugfs.h> #include <pthread.h> const char perf_usage_string[] = - "perf [--version] [--help] COMMAND [ARGS]"; + "perf [--version] [--help] [OPTIONS] COMMAND [ARGS]"; const char perf_more_info_string[] = "See 'perf help COMMAND' for more information on a specific command."; @@ -212,6 +213,16 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) printf("%s ", p->cmd); } exit(0); + } else if (!strcmp(cmd, "--debug")) { + if (*argc < 2) { + fprintf(stderr, "No variable specified for --debug.\n"); + usage(perf_usage_string); + } + if (perf_debug_option((*argv)[1])) + usage(perf_usage_string); + + (*argv)++; + (*argc)--; } else { fprintf(stderr, "Unknown option: %s\n", cmd); usage(perf_usage_string); @@ -458,6 +469,7 @@ int main(int argc, const char **argv) /* The page_size is placed in util object. */ page_size = sysconf(_SC_PAGE_SIZE); + cacheline_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); cmd = perf_extract_argv0_path(argv[0]); if (!cmd) @@ -481,14 +493,18 @@ int main(int argc, const char **argv) fprintf(stderr, "cannot handle %s internally", cmd); goto out; } -#ifdef HAVE_LIBAUDIT_SUPPORT if (!prefixcmp(cmd, "trace")) { +#ifdef HAVE_LIBAUDIT_SUPPORT set_buildid_dir(); setup_path(); argv[0] = "trace"; return cmd_trace(argc, argv, NULL); - } +#else + fprintf(stderr, + "trace command not available: missing audit-libs devel package at build time.\n"); + goto out; #endif + } /* Look for flags.. */ argv++; argc--; diff --git a/tools/perf/perf.h b/tools/perf/perf.h index 5c11ecad02a9..510c65f72858 100644 --- a/tools/perf/perf.h +++ b/tools/perf/perf.h @@ -1,182 +1,18 @@ #ifndef _PERF_PERF_H #define _PERF_PERF_H -#include <asm/unistd.h> - -#if defined(__i386__) -#define mb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory") -#define wmb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory") -#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 -#ifndef __NR_futex -# define __NR_futex 240 -#endif -#endif - -#if defined(__x86_64__) -#define mb() asm volatile("mfence" ::: "memory") -#define wmb() asm volatile("sfence" ::: "memory") -#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 -#ifndef __NR_futex -# define __NR_futex 202 -#endif -#endif - -#ifdef __powerpc__ -#include "../../arch/powerpc/include/uapi/asm/unistd.h" -#define mb() asm volatile ("sync" ::: "memory") -#define wmb() asm volatile ("sync" ::: "memory") -#define rmb() asm volatile ("sync" ::: "memory") -#define CPUINFO_PROC "cpu" -#endif - -#ifdef __s390__ -#define mb() asm volatile("bcr 15,0" ::: "memory") -#define wmb() asm volatile("bcr 15,0" ::: "memory") -#define rmb() asm volatile("bcr 15,0" ::: "memory") -#endif - -#ifdef __sh__ -#if defined(__SH4A__) || defined(__SH5__) -# define mb() asm volatile("synco" ::: "memory") -# define wmb() asm volatile("synco" ::: "memory") -# define rmb() asm volatile("synco" ::: "memory") -#else -# define mb() asm volatile("" ::: "memory") -# define wmb() asm volatile("" ::: "memory") -# define rmb() asm volatile("" ::: "memory") -#endif -#define CPUINFO_PROC "cpu type" -#endif - -#ifdef __hppa__ -#define mb() asm volatile("" ::: "memory") -#define wmb() asm volatile("" ::: "memory") -#define rmb() asm volatile("" ::: "memory") -#define CPUINFO_PROC "cpu" -#endif - -#ifdef __sparc__ -#ifdef __LP64__ -#define mb() asm volatile("ba,pt %%xcc, 1f\n" \ - "membar #StoreLoad\n" \ - "1:\n":::"memory") -#else -#define mb() asm volatile("":::"memory") -#endif -#define wmb() asm volatile("":::"memory") -#define rmb() asm volatile("":::"memory") -#define CPUINFO_PROC "cpu" -#endif - -#ifdef __alpha__ -#define mb() asm volatile("mb" ::: "memory") -#define wmb() asm volatile("wmb" ::: "memory") -#define rmb() asm volatile("mb" ::: "memory") -#define CPUINFO_PROC "cpu model" -#endif - -#ifdef __ia64__ -#define mb() asm volatile ("mf" ::: "memory") -#define wmb() asm volatile ("mf" ::: "memory") -#define rmb() asm volatile ("mf" ::: "memory") -#define cpu_relax() asm volatile ("hint @pause" ::: "memory") -#define CPUINFO_PROC "model name" -#endif - -#ifdef __arm__ -/* - * Use the __kuser_memory_barrier helper in the CPU helper page. See - * arch/arm/kernel/entry-armv.S in the kernel source for details. - */ -#define mb() ((void(*)(void))0xffff0fa0)() -#define wmb() ((void(*)(void))0xffff0fa0)() -#define rmb() ((void(*)(void))0xffff0fa0)() -#define CPUINFO_PROC "Processor" -#endif - -#ifdef __aarch64__ -#define mb() asm volatile("dmb ish" ::: "memory") -#define wmb() asm volatile("dmb ishst" ::: "memory") -#define rmb() asm volatile("dmb ishld" ::: "memory") -#define cpu_relax() asm volatile("yield" ::: "memory") -#endif - -#ifdef __mips__ -#define mb() asm volatile( \ - ".set mips2\n\t" \ - "sync\n\t" \ - ".set mips0" \ - : /* no output */ \ - : /* no input */ \ - : "memory") -#define wmb() mb() -#define rmb() mb() -#define CPUINFO_PROC "cpu model" -#endif - -#ifdef __arc__ -#define mb() asm volatile("" ::: "memory") -#define wmb() asm volatile("" ::: "memory") -#define rmb() asm volatile("" ::: "memory") -#define CPUINFO_PROC "Processor" -#endif - -#ifdef __metag__ -#define mb() asm volatile("" ::: "memory") -#define wmb() asm volatile("" ::: "memory") -#define rmb() asm volatile("" ::: "memory") -#define CPUINFO_PROC "CPU" -#endif - -#ifdef __xtensa__ -#define mb() asm volatile("memw" ::: "memory") -#define wmb() asm volatile("memw" ::: "memory") -#define rmb() asm volatile("" ::: "memory") -#define CPUINFO_PROC "core ID" -#endif - -#ifdef __tile__ -#define mb() asm volatile ("mf" ::: "memory") -#define wmb() asm volatile ("mf" ::: "memory") -#define rmb() asm volatile ("mf" ::: "memory") -#define cpu_relax() asm volatile ("mfspr zero, PASS" ::: "memory") -#define CPUINFO_PROC "model name" -#endif - -#define barrier() asm volatile ("" ::: "memory") - -#ifndef cpu_relax -#define cpu_relax() barrier() -#endif - -#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x)) - - #include <time.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/syscall.h> - -#include <linux/perf_event.h> -#include "util/types.h" #include <stdbool.h> +#include <linux/types.h> +#include <linux/perf_event.h> -/* - * prctl(PR_TASK_PERF_EVENTS_DISABLE) will (cheaply) disable all - * counters in the current task. - */ -#define PR_TASK_PERF_EVENTS_DISABLE 31 -#define PR_TASK_PERF_EVENTS_ENABLE 32 +extern bool test_attr__enabled; +void test_attr__init(void); +void test_attr__open(struct perf_event_attr *attr, pid_t pid, int cpu, + int fd, int group_fd, unsigned long flags); + +#define HAVE_ATTR_TEST +#include "perf-sys.h" #ifndef NSEC_PER_SEC # define NSEC_PER_SEC 1000000000ULL @@ -193,67 +29,8 @@ static inline unsigned long long rdclock(void) return ts.tv_sec * 1000000000ULL + ts.tv_nsec; } -/* - * Pick up some kernel type conventions: - */ -#define __user -#define asmlinkage - -#define unlikely(x) __builtin_expect(!!(x), 0) -#define min(x, y) ({ \ - typeof(x) _min1 = (x); \ - typeof(y) _min2 = (y); \ - (void) (&_min1 == &_min2); \ - _min1 < _min2 ? _min1 : _min2; }) - -extern bool test_attr__enabled; -void test_attr__init(void); -void test_attr__open(struct perf_event_attr *attr, pid_t pid, int cpu, - int fd, int group_fd, unsigned long flags); - -static inline int -sys_perf_event_open(struct perf_event_attr *attr, - pid_t pid, int cpu, int group_fd, - unsigned long flags) -{ - int fd; - - fd = syscall(__NR_perf_event_open, attr, pid, cpu, - group_fd, flags); - - if (unlikely(test_attr__enabled)) - test_attr__open(attr, pid, cpu, fd, group_fd, flags); - - return fd; -} - -#define MAX_COUNTERS 256 #define MAX_NR_CPUS 256 -struct ip_callchain { - u64 nr; - u64 ips[0]; -}; - -struct branch_flags { - u64 mispred:1; - u64 predicted:1; - u64 in_tx:1; - u64 abort:1; - u64 reserved:60; -}; - -struct branch_entry { - u64 from; - u64 to; - struct branch_flags flags; -}; - -struct branch_stack { - u64 nr; - struct branch_entry entries[0]; -}; - extern const char *input_name; extern bool perf_host, perf_guest; extern const char perf_version_string[]; @@ -262,13 +39,6 @@ void pthread__unblock_sigwinch(void); #include "util/target.h" -enum perf_call_graph_mode { - CALLCHAIN_NONE, - CALLCHAIN_FP, - CALLCHAIN_DWARF, - CALLCHAIN_MAX -}; - struct record_opts { struct target target; int call_graph; diff --git a/tools/perf/scripts/perl/bin/failed-syscalls-record b/tools/perf/scripts/perl/bin/failed-syscalls-record index 8104895a7b67..74685f318379 100644 --- a/tools/perf/scripts/perl/bin/failed-syscalls-record +++ b/tools/perf/scripts/perl/bin/failed-syscalls-record @@ -1,2 +1,3 @@ #!/bin/bash -perf record -e raw_syscalls:sys_exit $@ +(perf record -e raw_syscalls:sys_exit $@ || \ + perf record -e syscalls:sys_exit $@) 2> /dev/null diff --git a/tools/perf/scripts/perl/failed-syscalls.pl b/tools/perf/scripts/perl/failed-syscalls.pl index 94bc25a347eb..55e7ae4c5c88 100644 --- a/tools/perf/scripts/perl/failed-syscalls.pl +++ b/tools/perf/scripts/perl/failed-syscalls.pl @@ -26,6 +26,11 @@ sub raw_syscalls::sys_exit } } +sub syscalls::sys_exit +{ + raw_syscalls::sys_exit(@_) +} + sub trace_end { printf("\nfailed syscalls by comm:\n\n"); diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py index de7211e4fa47..38dfb720fb6f 100644 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py +++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py @@ -107,12 +107,13 @@ def taskState(state): class EventHeaders: def __init__(self, common_cpu, common_secs, common_nsecs, - common_pid, common_comm): + common_pid, common_comm, common_callchain): self.cpu = common_cpu self.secs = common_secs self.nsecs = common_nsecs self.pid = common_pid self.comm = common_comm + self.callchain = common_callchain def ts(self): return (self.secs * (10 ** 9)) + self.nsecs diff --git a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record index 8104895a7b67..74685f318379 100644 --- a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record +++ b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record @@ -1,2 +1,3 @@ #!/bin/bash -perf record -e raw_syscalls:sys_exit $@ +(perf record -e raw_syscalls:sys_exit $@ || \ + perf record -e syscalls:sys_exit $@) 2> /dev/null diff --git a/tools/perf/scripts/python/bin/sctop-record b/tools/perf/scripts/python/bin/sctop-record index 4efbfaa7f6a5..d6940841e54f 100644 --- a/tools/perf/scripts/python/bin/sctop-record +++ b/tools/perf/scripts/python/bin/sctop-record @@ -1,2 +1,3 @@ #!/bin/bash -perf record -e raw_syscalls:sys_enter $@ +(perf record -e raw_syscalls:sys_enter $@ || \ + perf record -e syscalls:sys_enter $@) 2> /dev/null diff --git a/tools/perf/scripts/python/bin/syscall-counts-by-pid-record b/tools/perf/scripts/python/bin/syscall-counts-by-pid-record index 4efbfaa7f6a5..d6940841e54f 100644 --- a/tools/perf/scripts/python/bin/syscall-counts-by-pid-record +++ b/tools/perf/scripts/python/bin/syscall-counts-by-pid-record @@ -1,2 +1,3 @@ #!/bin/bash -perf record -e raw_syscalls:sys_enter $@ +(perf record -e raw_syscalls:sys_enter $@ || \ + perf record -e syscalls:sys_enter $@) 2> /dev/null diff --git a/tools/perf/scripts/python/bin/syscall-counts-record b/tools/perf/scripts/python/bin/syscall-counts-record index 4efbfaa7f6a5..d6940841e54f 100644 --- a/tools/perf/scripts/python/bin/syscall-counts-record +++ b/tools/perf/scripts/python/bin/syscall-counts-record @@ -1,2 +1,3 @@ #!/bin/bash -perf record -e raw_syscalls:sys_enter $@ +(perf record -e raw_syscalls:sys_enter $@ || \ + perf record -e syscalls:sys_enter $@) 2> /dev/null diff --git a/tools/perf/scripts/python/check-perf-trace.py b/tools/perf/scripts/python/check-perf-trace.py index 4647a7694cf6..334599c6032c 100644 --- a/tools/perf/scripts/python/check-perf-trace.py +++ b/tools/perf/scripts/python/check-perf-trace.py @@ -27,7 +27,7 @@ def trace_end(): def irq__softirq_entry(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - vec): + common_callchain, vec): print_header(event_name, common_cpu, common_secs, common_nsecs, common_pid, common_comm) @@ -38,7 +38,7 @@ def irq__softirq_entry(event_name, context, common_cpu, def kmem__kmalloc(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - call_site, ptr, bytes_req, bytes_alloc, + common_callchain, call_site, ptr, bytes_req, bytes_alloc, gfp_flags): print_header(event_name, common_cpu, common_secs, common_nsecs, common_pid, common_comm) diff --git a/tools/perf/scripts/python/failed-syscalls-by-pid.py b/tools/perf/scripts/python/failed-syscalls-by-pid.py index 85805fac4116..cafeff3d74db 100644 --- a/tools/perf/scripts/python/failed-syscalls-by-pid.py +++ b/tools/perf/scripts/python/failed-syscalls-by-pid.py @@ -39,7 +39,7 @@ def trace_end(): def raw_syscalls__sys_exit(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - id, ret): + common_callchain, id, ret): if (for_comm and common_comm != for_comm) or \ (for_pid and common_pid != for_pid ): return @@ -50,6 +50,11 @@ def raw_syscalls__sys_exit(event_name, context, common_cpu, except TypeError: syscalls[common_comm][common_pid][id][ret] = 1 +def syscalls__sys_exit(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + id, ret): + raw_syscalls__sys_exit(**locals()) + def print_error_totals(): if for_comm is not None: print "\nsyscall errors for %s:\n\n" % (for_comm), diff --git a/tools/perf/scripts/python/futex-contention.py b/tools/perf/scripts/python/futex-contention.py index 11e70a388d41..0f5cf437b602 100644 --- a/tools/perf/scripts/python/futex-contention.py +++ b/tools/perf/scripts/python/futex-contention.py @@ -21,7 +21,7 @@ thread_blocktime = {} lock_waits = {} # long-lived stats on (tid,lock) blockage elapsed time process_names = {} # long-lived pid-to-execname mapping -def syscalls__sys_enter_futex(event, ctxt, cpu, s, ns, tid, comm, +def syscalls__sys_enter_futex(event, ctxt, cpu, s, ns, tid, comm, callchain, nr, uaddr, op, val, utime, uaddr2, val3): cmd = op & FUTEX_CMD_MASK if cmd != FUTEX_WAIT: @@ -31,7 +31,7 @@ def syscalls__sys_enter_futex(event, ctxt, cpu, s, ns, tid, comm, thread_thislock[tid] = uaddr thread_blocktime[tid] = nsecs(s, ns) -def syscalls__sys_exit_futex(event, ctxt, cpu, s, ns, tid, comm, +def syscalls__sys_exit_futex(event, ctxt, cpu, s, ns, tid, comm, callchain, nr, ret): if thread_blocktime.has_key(tid): elapsed = nsecs(s, ns) - thread_blocktime[tid] diff --git a/tools/perf/scripts/python/net_dropmonitor.py b/tools/perf/scripts/python/net_dropmonitor.py index b5740599aabd..0b6ce8c253e8 100755 --- a/tools/perf/scripts/python/net_dropmonitor.py +++ b/tools/perf/scripts/python/net_dropmonitor.py @@ -66,7 +66,7 @@ def trace_end(): print_drop_table() # called from perf, when it finds a correspoinding event -def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, +def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr, location, protocol): slocation = str(location) try: diff --git a/tools/perf/scripts/python/netdev-times.py b/tools/perf/scripts/python/netdev-times.py index 9aa0a32972e8..4d21ef2d601d 100644 --- a/tools/perf/scripts/python/netdev-times.py +++ b/tools/perf/scripts/python/netdev-times.py @@ -224,75 +224,75 @@ def trace_end(): (len(rx_skb_list), of_count_rx_skb_list) # called from perf, when it finds a correspoinding event -def irq__softirq_entry(name, context, cpu, sec, nsec, pid, comm, vec): +def irq__softirq_entry(name, context, cpu, sec, nsec, pid, comm, callchain, vec): if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX": return event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec) all_event_list.append(event_info) -def irq__softirq_exit(name, context, cpu, sec, nsec, pid, comm, vec): +def irq__softirq_exit(name, context, cpu, sec, nsec, pid, comm, callchain, vec): if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX": return event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec) all_event_list.append(event_info) -def irq__softirq_raise(name, context, cpu, sec, nsec, pid, comm, vec): +def irq__softirq_raise(name, context, cpu, sec, nsec, pid, comm, callchain, vec): if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX": return event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec) all_event_list.append(event_info) def irq__irq_handler_entry(name, context, cpu, sec, nsec, pid, comm, - irq, irq_name): + callchain, irq, irq_name): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, irq, irq_name) all_event_list.append(event_info) -def irq__irq_handler_exit(name, context, cpu, sec, nsec, pid, comm, irq, ret): +def irq__irq_handler_exit(name, context, cpu, sec, nsec, pid, comm, callchain, irq, ret): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, irq, ret) all_event_list.append(event_info) -def napi__napi_poll(name, context, cpu, sec, nsec, pid, comm, napi, dev_name): +def napi__napi_poll(name, context, cpu, sec, nsec, pid, comm, callchain, napi, dev_name): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, napi, dev_name) all_event_list.append(event_info) -def net__netif_receive_skb(name, context, cpu, sec, nsec, pid, comm, skbaddr, +def net__netif_receive_skb(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr, skblen, dev_name): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, skbaddr, skblen, dev_name) all_event_list.append(event_info) -def net__netif_rx(name, context, cpu, sec, nsec, pid, comm, skbaddr, +def net__netif_rx(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr, skblen, dev_name): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, skbaddr, skblen, dev_name) all_event_list.append(event_info) -def net__net_dev_queue(name, context, cpu, sec, nsec, pid, comm, +def net__net_dev_queue(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr, skblen, dev_name): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, skbaddr, skblen, dev_name) all_event_list.append(event_info) -def net__net_dev_xmit(name, context, cpu, sec, nsec, pid, comm, +def net__net_dev_xmit(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr, skblen, rc, dev_name): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, skbaddr, skblen, rc ,dev_name) all_event_list.append(event_info) -def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, +def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr, protocol, location): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, skbaddr, protocol, location) all_event_list.append(event_info) -def skb__consume_skb(name, context, cpu, sec, nsec, pid, comm, skbaddr): +def skb__consume_skb(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, skbaddr) all_event_list.append(event_info) -def skb__skb_copy_datagram_iovec(name, context, cpu, sec, nsec, pid, comm, +def skb__skb_copy_datagram_iovec(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr, skblen): event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, skbaddr, skblen) diff --git a/tools/perf/scripts/python/sched-migration.py b/tools/perf/scripts/python/sched-migration.py index 74d55ec08aed..de66cb3b72c9 100644 --- a/tools/perf/scripts/python/sched-migration.py +++ b/tools/perf/scripts/python/sched-migration.py @@ -369,93 +369,92 @@ def trace_end(): def sched__sched_stat_runtime(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, runtime, vruntime): + common_callchain, comm, pid, runtime, vruntime): pass def sched__sched_stat_iowait(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, delay): + common_callchain, comm, pid, delay): pass def sched__sched_stat_sleep(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, delay): + common_callchain, comm, pid, delay): pass def sched__sched_stat_wait(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, delay): + common_callchain, comm, pid, delay): pass def sched__sched_process_fork(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - parent_comm, parent_pid, child_comm, child_pid): + common_callchain, parent_comm, parent_pid, child_comm, child_pid): pass def sched__sched_process_wait(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, prio): + common_callchain, comm, pid, prio): pass def sched__sched_process_exit(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, prio): + common_callchain, comm, pid, prio): pass def sched__sched_process_free(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, prio): + common_callchain, comm, pid, prio): pass def sched__sched_migrate_task(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, prio, orig_cpu, + common_callchain, comm, pid, prio, orig_cpu, dest_cpu): headers = EventHeaders(common_cpu, common_secs, common_nsecs, - common_pid, common_comm) + common_pid, common_comm, common_callchain) parser.migrate(headers, pid, prio, orig_cpu, dest_cpu) def sched__sched_switch(event_name, context, common_cpu, - common_secs, common_nsecs, common_pid, common_comm, + common_secs, common_nsecs, common_pid, common_comm, common_callchain, prev_comm, prev_pid, prev_prio, prev_state, next_comm, next_pid, next_prio): headers = EventHeaders(common_cpu, common_secs, common_nsecs, - common_pid, common_comm) + common_pid, common_comm, common_callchain) parser.sched_switch(headers, prev_comm, prev_pid, prev_prio, prev_state, next_comm, next_pid, next_prio) def sched__sched_wakeup_new(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, prio, success, + common_callchain, comm, pid, prio, success, target_cpu): headers = EventHeaders(common_cpu, common_secs, common_nsecs, - common_pid, common_comm) + common_pid, common_comm, common_callchain) parser.wake_up(headers, comm, pid, success, target_cpu, 1) def sched__sched_wakeup(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, prio, success, + common_callchain, comm, pid, prio, success, target_cpu): headers = EventHeaders(common_cpu, common_secs, common_nsecs, - common_pid, common_comm) + common_pid, common_comm, common_callchain) parser.wake_up(headers, comm, pid, success, target_cpu, 0) def sched__sched_wait_task(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid, prio): + common_callchain, comm, pid, prio): pass def sched__sched_kthread_stop_ret(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - ret): + common_callchain, ret): pass def sched__sched_kthread_stop(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - comm, pid): + common_callchain, comm, pid): pass -def trace_unhandled(event_name, context, common_cpu, common_secs, common_nsecs, - common_pid, common_comm): +def trace_unhandled(event_name, context, event_fields_dict): pass diff --git a/tools/perf/scripts/python/sctop.py b/tools/perf/scripts/python/sctop.py index 42c267e292fa..61621b93affb 100644 --- a/tools/perf/scripts/python/sctop.py +++ b/tools/perf/scripts/python/sctop.py @@ -44,7 +44,7 @@ def trace_begin(): def raw_syscalls__sys_enter(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - id, args): + common_callchain, id, args): if for_comm is not None: if common_comm != for_comm: return @@ -53,6 +53,11 @@ def raw_syscalls__sys_enter(event_name, context, common_cpu, except TypeError: syscalls[id] = 1 +def syscalls__sys_enter(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + id, args): + raw_syscalls__sys_enter(**locals()) + def print_syscall_totals(interval): while 1: clear_term() diff --git a/tools/perf/scripts/python/syscall-counts-by-pid.py b/tools/perf/scripts/python/syscall-counts-by-pid.py index c64d1c55d745..daf314cc5dd3 100644 --- a/tools/perf/scripts/python/syscall-counts-by-pid.py +++ b/tools/perf/scripts/python/syscall-counts-by-pid.py @@ -38,7 +38,7 @@ def trace_end(): def raw_syscalls__sys_enter(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - id, args): + common_callchain, id, args): if (for_comm and common_comm != for_comm) or \ (for_pid and common_pid != for_pid ): @@ -48,6 +48,11 @@ def raw_syscalls__sys_enter(event_name, context, common_cpu, except TypeError: syscalls[common_comm][common_pid][id] = 1 +def syscalls__sys_enter(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + id, args): + raw_syscalls__sys_enter(**locals()) + def print_syscall_totals(): if for_comm is not None: print "\nsyscall events for %s:\n\n" % (for_comm), diff --git a/tools/perf/scripts/python/syscall-counts.py b/tools/perf/scripts/python/syscall-counts.py index b435d3f188e8..e66a7730aeb5 100644 --- a/tools/perf/scripts/python/syscall-counts.py +++ b/tools/perf/scripts/python/syscall-counts.py @@ -35,7 +35,7 @@ def trace_end(): def raw_syscalls__sys_enter(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, - id, args): + common_callchain, id, args): if for_comm is not None: if common_comm != for_comm: return @@ -44,6 +44,11 @@ def raw_syscalls__sys_enter(event_name, context, common_cpu, except TypeError: syscalls[id] = 1 +def syscalls__sys_enter(event_name, context, common_cpu, + common_secs, common_nsecs, common_pid, common_comm, + id, args): + raw_syscalls__sys_enter(**locals()) + def print_syscall_totals(): if for_comm is not None: print "\nsyscall events for %s:\n\n" % (for_comm), diff --git a/tools/perf/tests/attr.c b/tools/perf/tests/attr.c index 00218f503b2e..2dfc9ad0e6f2 100644 --- a/tools/perf/tests/attr.c +++ b/tools/perf/tests/attr.c @@ -1,4 +1,3 @@ - /* * The struct perf_event_attr test support. * @@ -19,14 +18,8 @@ * permissions. All the event text files are stored there. */ -/* - * Powerpc needs __SANE_USERSPACE_TYPES__ before <linux/types.h> to select - * 'int-ll64.h' and avoid compile warnings when printing __u64 with %llu. - */ -#define __SANE_USERSPACE_TYPES__ #include <stdlib.h> #include <stdio.h> -#include <inttypes.h> #include <linux/types.h> #include <linux/kernel.h> #include "../perf.h" diff --git a/tools/perf/tests/attr/base-record b/tools/perf/tests/attr/base-record index e9bd6391f2ae..f710b92ccff6 100644 --- a/tools/perf/tests/attr/base-record +++ b/tools/perf/tests/attr/base-record @@ -1,7 +1,8 @@ [event] fd=1 group_fd=-1 -flags=0 +# 0 or PERF_FLAG_FD_CLOEXEC flag +flags=0|8 cpu=* type=0|1 size=96 diff --git a/tools/perf/tests/attr/base-stat b/tools/perf/tests/attr/base-stat index 91cd48b399f3..dc3ada2470c0 100644 --- a/tools/perf/tests/attr/base-stat +++ b/tools/perf/tests/attr/base-stat @@ -1,7 +1,8 @@ [event] fd=1 group_fd=-1 -flags=0 +# 0 or PERF_FLAG_FD_CLOEXEC flag +flags=0|8 cpu=* type=0 size=96 diff --git a/tools/perf/tests/bp_signal.c b/tools/perf/tests/bp_signal.c index aba095489193..a02b035fd5aa 100644 --- a/tools/perf/tests/bp_signal.c +++ b/tools/perf/tests/bp_signal.c @@ -25,6 +25,7 @@ #include "tests.h" #include "debug.h" #include "perf.h" +#include "cloexec.h" static int fd1; static int fd2; @@ -78,7 +79,8 @@ static int bp_event(void *fn, int setup_signal) pe.exclude_kernel = 1; pe.exclude_hv = 1; - fd = sys_perf_event_open(&pe, 0, -1, -1, 0); + fd = sys_perf_event_open(&pe, 0, -1, -1, + perf_event_open_cloexec_flag()); if (fd < 0) { pr_debug("failed opening event %llx\n", pe.config); return TEST_FAIL; diff --git a/tools/perf/tests/bp_signal_overflow.c b/tools/perf/tests/bp_signal_overflow.c index 44ac82179708..e76537724491 100644 --- a/tools/perf/tests/bp_signal_overflow.c +++ b/tools/perf/tests/bp_signal_overflow.c @@ -24,6 +24,7 @@ #include "tests.h" #include "debug.h" #include "perf.h" +#include "cloexec.h" static int overflows; @@ -91,7 +92,8 @@ int test__bp_signal_overflow(void) pe.exclude_kernel = 1; pe.exclude_hv = 1; - fd = sys_perf_event_open(&pe, 0, -1, -1, 0); + fd = sys_perf_event_open(&pe, 0, -1, -1, + perf_event_open_cloexec_flag()); if (fd < 0) { pr_debug("failed opening event %llx\n", pe.config); return TEST_FAIL; diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index b11bf8a08430..6f8b01bc6033 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -3,6 +3,8 @@ * * Builtin regression testing command: ever growing number of sanity tests */ +#include <unistd.h> +#include <string.h> #include "builtin.h" #include "intlist.h" #include "tests.h" @@ -50,10 +52,18 @@ static struct test { .func = test__pmu, }, { - .desc = "Test dso data interface", + .desc = "Test dso data read", .func = test__dso_data, }, { + .desc = "Test dso data cache", + .func = test__dso_data_cache, + }, + { + .desc = "Test dso data reopen", + .func = test__dso_data_reopen, + }, + { .desc = "roundtrip evsel->name check", .func = test__perf_evsel__roundtrip_name_test, }, @@ -115,7 +125,7 @@ static struct test { .desc = "Test parsing with no sample_id_all bit set", .func = test__parse_no_sample_id_all, }, -#if defined(__x86_64__) || defined(__i386__) +#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) #ifdef HAVE_DWARF_UNWIND_SUPPORT { .desc = "Test dwarf unwind", @@ -124,6 +134,26 @@ static struct test { #endif #endif { + .desc = "Test filtering hist entries", + .func = test__hists_filter, + }, + { + .desc = "Test mmap thread lookup", + .func = test__mmap_thread_lookup, + }, + { + .desc = "Test thread mg sharing", + .func = test__thread_mg_share, + }, + { + .desc = "Test output sorting of hist entries", + .func = test__hists_output, + }, + { + .desc = "Test cumulation of child hist entries", + .func = test__hists_cumulate, + }, + { .func = NULL, }, }; @@ -152,6 +182,34 @@ static bool perf_test__matches(int curr, int argc, const char *argv[]) return false; } +static int run_test(struct test *test) +{ + int status, err = -1, child = fork(); + + if (child < 0) { + pr_err("failed to fork test: %s\n", strerror(errno)); + return -1; + } + + if (!child) { + pr_debug("test child forked, pid %d\n", getpid()); + err = test->func(); + exit(err); + } + + wait(&status); + + if (WIFEXITED(status)) { + err = WEXITSTATUS(status); + pr_debug("test child finished with %d\n", err); + } else if (WIFSIGNALED(status)) { + err = -1; + pr_debug("test child interrupted\n"); + } + + return err; +} + static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) { int i = 0; @@ -180,7 +238,7 @@ static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) } pr_debug("\n--- start ---\n"); - err = tests[curr].func(); + err = run_test(&tests[curr]); pr_debug("---- end ----\n%s:", tests[curr].desc); switch (err) { diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c index bfb186900ac0..67f2d6323558 100644 --- a/tools/perf/tests/code-reading.c +++ b/tools/perf/tests/code-reading.c @@ -1,8 +1,7 @@ -#include <sys/types.h> +#include <linux/types.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> -#include <inttypes.h> #include <ctype.h> #include <string.h> @@ -257,7 +256,7 @@ static int process_sample_event(struct machine *machine, return -1; } - thread = machine__findnew_thread(machine, sample.pid, sample.pid); + thread = machine__findnew_thread(machine, sample.pid, sample.tid); if (!thread) { pr_debug("machine__findnew_thread failed\n"); return -1; diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 9cc81a3eb9b4..caaf37f079b1 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -1,22 +1,28 @@ -#include "util.h" - #include <stdlib.h> -#include <sys/types.h> +#include <linux/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> - +#include <sys/time.h> +#include <sys/resource.h> +#include <api/fs/fs.h> +#include "util.h" #include "machine.h" #include "symbol.h" #include "tests.h" +#include "debug.h" static char *test_file(int size) { - static char buf_templ[] = "/tmp/test-XXXXXX"; +#define TEMPL "/tmp/perf-test-XXXXXX" + static char buf_templ[sizeof(TEMPL)]; char *templ = buf_templ; int fd, i; unsigned char *buf; + strcpy(buf_templ, TEMPL); +#undef TEMPL + fd = mkstemp(templ); if (fd < 0) { perror("mkstemp failed"); @@ -150,3 +156,204 @@ int test__dso_data(void) unlink(file); return 0; } + +static long open_files_cnt(void) +{ + char path[PATH_MAX]; + struct dirent *dent; + DIR *dir; + long nr = 0; + + scnprintf(path, PATH_MAX, "%s/self/fd", procfs__mountpoint()); + pr_debug("fd path: %s\n", path); + + dir = opendir(path); + TEST_ASSERT_VAL("failed to open fd directory", dir); + + while ((dent = readdir(dir)) != NULL) { + if (!strcmp(dent->d_name, ".") || + !strcmp(dent->d_name, "..")) + continue; + + nr++; + } + + closedir(dir); + return nr - 1; +} + +static struct dso **dsos; + +static int dsos__create(int cnt, int size) +{ + int i; + + dsos = malloc(sizeof(dsos) * cnt); + TEST_ASSERT_VAL("failed to alloc dsos array", dsos); + + for (i = 0; i < cnt; i++) { + char *file; + + file = test_file(size); + TEST_ASSERT_VAL("failed to get dso file", file); + + dsos[i] = dso__new(file); + TEST_ASSERT_VAL("failed to get dso", dsos[i]); + } + + return 0; +} + +static void dsos__delete(int cnt) +{ + int i; + + for (i = 0; i < cnt; i++) { + struct dso *dso = dsos[i]; + + unlink(dso->name); + dso__delete(dso); + } + + free(dsos); +} + +static int set_fd_limit(int n) +{ + struct rlimit rlim; + + if (getrlimit(RLIMIT_NOFILE, &rlim)) + return -1; + + pr_debug("file limit %ld, new %d\n", (long) rlim.rlim_cur, n); + + rlim.rlim_cur = n; + return setrlimit(RLIMIT_NOFILE, &rlim); +} + +int test__dso_data_cache(void) +{ + struct machine machine; + long nr_end, nr = open_files_cnt(); + int dso_cnt, limit, i, fd; + + memset(&machine, 0, sizeof(machine)); + + /* set as system limit */ + limit = nr * 4; + TEST_ASSERT_VAL("failed to set file limit", !set_fd_limit(limit)); + + /* and this is now our dso open FDs limit + 1 extra */ + dso_cnt = limit / 2 + 1; + TEST_ASSERT_VAL("failed to create dsos\n", + !dsos__create(dso_cnt, TEST_FILE_SIZE)); + + for (i = 0; i < (dso_cnt - 1); i++) { + struct dso *dso = dsos[i]; + + /* + * Open dsos via dso__data_fd or dso__data_read_offset. + * Both opens the data file and keep it open. + */ + if (i % 2) { + fd = dso__data_fd(dso, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + } else { + #define BUFSIZE 10 + u8 buf[BUFSIZE]; + ssize_t n; + + n = dso__data_read_offset(dso, &machine, 0, buf, BUFSIZE); + TEST_ASSERT_VAL("failed to read dso", n == BUFSIZE); + } + } + + /* open +1 dso over the allowed limit */ + fd = dso__data_fd(dsos[i], &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* should force the first one to be closed */ + TEST_ASSERT_VAL("failed to close dsos[0]", dsos[0]->data.fd == -1); + + /* cleanup everything */ + dsos__delete(dso_cnt); + + /* Make sure we did not leak any file descriptor. */ + nr_end = open_files_cnt(); + pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); + TEST_ASSERT_VAL("failed leadking files", nr == nr_end); + return 0; +} + +int test__dso_data_reopen(void) +{ + struct machine machine; + long nr_end, nr = open_files_cnt(); + int fd, fd_extra; + +#define dso_0 (dsos[0]) +#define dso_1 (dsos[1]) +#define dso_2 (dsos[2]) + + memset(&machine, 0, sizeof(machine)); + + /* + * Test scenario: + * - create 3 dso objects + * - set process file descriptor limit to current + * files count + 3 + * - test that the first dso gets closed when we + * reach the files count limit + */ + + /* Make sure we are able to open 3 fds anyway */ + TEST_ASSERT_VAL("failed to set file limit", + !set_fd_limit((nr + 3))); + + TEST_ASSERT_VAL("failed to create dsos\n", !dsos__create(3, TEST_FILE_SIZE)); + + /* open dso_0 */ + fd = dso__data_fd(dso_0, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* open dso_1 */ + fd = dso__data_fd(dso_1, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* + * open extra file descriptor and we just + * reached the files count limit + */ + fd_extra = open("/dev/null", O_RDONLY); + TEST_ASSERT_VAL("failed to open extra fd", fd_extra > 0); + + /* open dso_2 */ + fd = dso__data_fd(dso_2, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* + * dso_0 should get closed, because we reached + * the file descriptor limit + */ + TEST_ASSERT_VAL("failed to close dso_0", dso_0->data.fd == -1); + + /* open dso_0 */ + fd = dso__data_fd(dso_0, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* + * dso_1 should get closed, because we reached + * the file descriptor limit + */ + TEST_ASSERT_VAL("failed to close dso_1", dso_1->data.fd == -1); + + /* cleanup everything */ + close(fd_extra); + dsos__delete(3); + + /* Make sure we did not leak any file descriptor. */ + nr_end = open_files_cnt(); + pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); + TEST_ASSERT_VAL("failed leadking files", nr == nr_end); + return 0; +} diff --git a/tools/perf/tests/dwarf-unwind.c b/tools/perf/tests/dwarf-unwind.c index c059ee81c038..96adb730b744 100644 --- a/tools/perf/tests/dwarf-unwind.c +++ b/tools/perf/tests/dwarf-unwind.c @@ -1,5 +1,5 @@ #include <linux/compiler.h> -#include <sys/types.h> +#include <linux/types.h> #include <unistd.h> #include "tests.h" #include "debug.h" @@ -15,7 +15,7 @@ static int mmap_handler(struct perf_tool *tool __maybe_unused, struct perf_sample *sample __maybe_unused, struct machine *machine) { - return machine__process_mmap_event(machine, event, NULL); + return machine__process_mmap2_event(machine, event, NULL); } static int init_live_machine(struct machine *machine) diff --git a/tools/perf/tests/evsel-roundtrip-name.c b/tools/perf/tests/evsel-roundtrip-name.c index 465cdbc345cf..b8d8341b383e 100644 --- a/tools/perf/tests/evsel-roundtrip-name.c +++ b/tools/perf/tests/evsel-roundtrip-name.c @@ -2,6 +2,7 @@ #include "evsel.h" #include "parse-events.h" #include "tests.h" +#include "debug.h" static int perf_evsel__roundtrip_cache_name_test(void) { diff --git a/tools/perf/tests/evsel-tp-sched.c b/tools/perf/tests/evsel-tp-sched.c index 4774f7fbb758..52162425c969 100644 --- a/tools/perf/tests/evsel-tp-sched.c +++ b/tools/perf/tests/evsel-tp-sched.c @@ -1,6 +1,7 @@ #include <traceevent/event-parse.h> #include "evsel.h" #include "tests.h" +#include "debug.h" static int perf_evsel__test_field(struct perf_evsel *evsel, const char *name, int size, bool should_be_signed) @@ -74,9 +75,6 @@ int test__perf_evsel__tp_sched_test(void) if (perf_evsel__test_field(evsel, "prio", 4, true)) ret = -1; - if (perf_evsel__test_field(evsel, "success", 4, true)) - ret = -1; - if (perf_evsel__test_field(evsel, "target_cpu", 4, true)) ret = -1; diff --git a/tools/perf/tests/hists_common.c b/tools/perf/tests/hists_common.c new file mode 100644 index 000000000000..a62c09134516 --- /dev/null +++ b/tools/perf/tests/hists_common.c @@ -0,0 +1,209 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "tests/hists_common.h" + +static struct { + u32 pid; + const char *comm; +} fake_threads[] = { + { FAKE_PID_PERF1, "perf" }, + { FAKE_PID_PERF2, "perf" }, + { FAKE_PID_BASH, "bash" }, +}; + +static struct { + u32 pid; + u64 start; + const char *filename; +} fake_mmap_info[] = { + { FAKE_PID_PERF1, FAKE_MAP_PERF, "perf" }, + { FAKE_PID_PERF1, FAKE_MAP_LIBC, "libc" }, + { FAKE_PID_PERF1, FAKE_MAP_KERNEL, "[kernel]" }, + { FAKE_PID_PERF2, FAKE_MAP_PERF, "perf" }, + { FAKE_PID_PERF2, FAKE_MAP_LIBC, "libc" }, + { FAKE_PID_PERF2, FAKE_MAP_KERNEL, "[kernel]" }, + { FAKE_PID_BASH, FAKE_MAP_BASH, "bash" }, + { FAKE_PID_BASH, FAKE_MAP_LIBC, "libc" }, + { FAKE_PID_BASH, FAKE_MAP_KERNEL, "[kernel]" }, +}; + +struct fake_sym { + u64 start; + u64 length; + const char *name; +}; + +static struct fake_sym perf_syms[] = { + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "main" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "run_command" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "cmd_record" }, +}; + +static struct fake_sym bash_syms[] = { + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "main" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "xmalloc" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "xfree" }, +}; + +static struct fake_sym libc_syms[] = { + { 700, 100, "malloc" }, + { 800, 100, "free" }, + { 900, 100, "realloc" }, + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "malloc" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "free" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "realloc" }, +}; + +static struct fake_sym kernel_syms[] = { + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "schedule" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "page_fault" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "sys_perf_event_open" }, +}; + +static struct { + const char *dso_name; + struct fake_sym *syms; + size_t nr_syms; +} fake_symbols[] = { + { "perf", perf_syms, ARRAY_SIZE(perf_syms) }, + { "bash", bash_syms, ARRAY_SIZE(bash_syms) }, + { "libc", libc_syms, ARRAY_SIZE(libc_syms) }, + { "[kernel]", kernel_syms, ARRAY_SIZE(kernel_syms) }, +}; + +struct machine *setup_fake_machine(struct machines *machines) +{ + struct machine *machine = machines__find(machines, HOST_KERNEL_ID); + size_t i; + + if (machine == NULL) { + pr_debug("Not enough memory for machine setup\n"); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(fake_threads); i++) { + struct thread *thread; + + thread = machine__findnew_thread(machine, fake_threads[i].pid, + fake_threads[i].pid); + if (thread == NULL) + goto out; + + thread__set_comm(thread, fake_threads[i].comm, 0); + } + + for (i = 0; i < ARRAY_SIZE(fake_mmap_info); i++) { + union perf_event fake_mmap_event = { + .mmap = { + .header = { .misc = PERF_RECORD_MISC_USER, }, + .pid = fake_mmap_info[i].pid, + .tid = fake_mmap_info[i].pid, + .start = fake_mmap_info[i].start, + .len = FAKE_MAP_LENGTH, + .pgoff = 0ULL, + }, + }; + + strcpy(fake_mmap_event.mmap.filename, + fake_mmap_info[i].filename); + + machine__process_mmap_event(machine, &fake_mmap_event, NULL); + } + + for (i = 0; i < ARRAY_SIZE(fake_symbols); i++) { + size_t k; + struct dso *dso; + + dso = __dsos__findnew(&machine->user_dsos, + fake_symbols[i].dso_name); + if (dso == NULL) + goto out; + + /* emulate dso__load() */ + dso__set_loaded(dso, MAP__FUNCTION); + + for (k = 0; k < fake_symbols[i].nr_syms; k++) { + struct symbol *sym; + struct fake_sym *fsym = &fake_symbols[i].syms[k]; + + sym = symbol__new(fsym->start, fsym->length, + STB_GLOBAL, fsym->name); + if (sym == NULL) + goto out; + + symbols__insert(&dso->symbols[MAP__FUNCTION], sym); + } + } + + return machine; + +out: + pr_debug("Not enough memory for machine setup\n"); + machine__delete_threads(machine); + machine__delete(machine); + return NULL; +} + +void print_hists_in(struct hists *hists) +{ + int i = 0; + struct rb_root *root; + struct rb_node *node; + + if (sort__need_collapse) + root = &hists->entries_collapsed; + else + root = hists->entries_in; + + pr_info("----- %s --------\n", __func__); + node = rb_first(root); + while (node) { + struct hist_entry *he; + + he = rb_entry(node, struct hist_entry, rb_node_in); + + if (!he->filtered) { + pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n", + i, thread__comm_str(he->thread), + he->ms.map->dso->short_name, + he->ms.sym->name, he->stat.period); + } + + i++; + node = rb_next(node); + } +} + +void print_hists_out(struct hists *hists) +{ + int i = 0; + struct rb_root *root; + struct rb_node *node; + + root = &hists->entries; + + pr_info("----- %s --------\n", __func__); + node = rb_first(root); + while (node) { + struct hist_entry *he; + + he = rb_entry(node, struct hist_entry, rb_node); + + if (!he->filtered) { + pr_info("%2d: entry: %8s:%5d [%-8s] %20s: period = %"PRIu64"/%"PRIu64"\n", + i, thread__comm_str(he->thread), he->thread->tid, + he->ms.map->dso->short_name, + he->ms.sym->name, he->stat.period, + he->stat_acc ? he->stat_acc->period : 0); + } + + i++; + node = rb_next(node); + } +} diff --git a/tools/perf/tests/hists_common.h b/tools/perf/tests/hists_common.h new file mode 100644 index 000000000000..888254e8665c --- /dev/null +++ b/tools/perf/tests/hists_common.h @@ -0,0 +1,75 @@ +#ifndef __PERF_TESTS__HISTS_COMMON_H__ +#define __PERF_TESTS__HISTS_COMMON_H__ + +struct machine; +struct machines; + +#define FAKE_PID_PERF1 100 +#define FAKE_PID_PERF2 200 +#define FAKE_PID_BASH 300 + +#define FAKE_MAP_PERF 0x400000 +#define FAKE_MAP_BASH 0x400000 +#define FAKE_MAP_LIBC 0x500000 +#define FAKE_MAP_KERNEL 0xf00000 +#define FAKE_MAP_LENGTH 0x100000 + +#define FAKE_SYM_OFFSET1 700 +#define FAKE_SYM_OFFSET2 800 +#define FAKE_SYM_OFFSET3 900 +#define FAKE_SYM_LENGTH 100 + +#define FAKE_IP_PERF_MAIN FAKE_MAP_PERF + FAKE_SYM_OFFSET1 +#define FAKE_IP_PERF_RUN_COMMAND FAKE_MAP_PERF + FAKE_SYM_OFFSET2 +#define FAKE_IP_PERF_CMD_RECORD FAKE_MAP_PERF + FAKE_SYM_OFFSET3 +#define FAKE_IP_BASH_MAIN FAKE_MAP_BASH + FAKE_SYM_OFFSET1 +#define FAKE_IP_BASH_XMALLOC FAKE_MAP_BASH + FAKE_SYM_OFFSET2 +#define FAKE_IP_BASH_XFREE FAKE_MAP_BASH + FAKE_SYM_OFFSET3 +#define FAKE_IP_LIBC_MALLOC FAKE_MAP_LIBC + FAKE_SYM_OFFSET1 +#define FAKE_IP_LIBC_FREE FAKE_MAP_LIBC + FAKE_SYM_OFFSET2 +#define FAKE_IP_LIBC_REALLOC FAKE_MAP_LIBC + FAKE_SYM_OFFSET3 +#define FAKE_IP_KERNEL_SCHEDULE FAKE_MAP_KERNEL + FAKE_SYM_OFFSET1 +#define FAKE_IP_KERNEL_PAGE_FAULT FAKE_MAP_KERNEL + FAKE_SYM_OFFSET2 +#define FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN FAKE_MAP_KERNEL + FAKE_SYM_OFFSET3 + +/* + * The setup_fake_machine() provides a test environment which consists + * of 3 processes that have 3 mappings and in turn, have 3 symbols + * respectively. See below table: + * + * Command: Pid Shared Object Symbol + * ............. ............. ................... + * perf: 100 perf main + * perf: 100 perf run_command + * perf: 100 perf cmd_record + * perf: 100 libc malloc + * perf: 100 libc free + * perf: 100 libc realloc + * perf: 100 [kernel] schedule + * perf: 100 [kernel] page_fault + * perf: 100 [kernel] sys_perf_event_open + * perf: 200 perf main + * perf: 200 perf run_command + * perf: 200 perf cmd_record + * perf: 200 libc malloc + * perf: 200 libc free + * perf: 200 libc realloc + * perf: 200 [kernel] schedule + * perf: 200 [kernel] page_fault + * perf: 200 [kernel] sys_perf_event_open + * bash: 300 bash main + * bash: 300 bash xmalloc + * bash: 300 bash xfree + * bash: 300 libc malloc + * bash: 300 libc free + * bash: 300 libc realloc + * bash: 300 [kernel] schedule + * bash: 300 [kernel] page_fault + * bash: 300 [kernel] sys_perf_event_open + */ +struct machine *setup_fake_machine(struct machines *machines); + +void print_hists_in(struct hists *hists); +void print_hists_out(struct hists *hists); + +#endif /* __PERF_TESTS__HISTS_COMMON_H__ */ diff --git a/tools/perf/tests/hists_cumulate.c b/tools/perf/tests/hists_cumulate.c new file mode 100644 index 000000000000..0ac240db2e24 --- /dev/null +++ b/tools/perf/tests/hists_cumulate.c @@ -0,0 +1,726 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "util/parse-events.h" +#include "tests/tests.h" +#include "tests/hists_common.h" + +struct sample { + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_samples[] = { + /* perf [kernel] schedule() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [perf] cmd_record() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_CMD_RECORD, }, + /* perf [libc] malloc() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, + /* perf [libc] free() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_FREE, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [kernel] page_fault() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, + /* bash [bash] main() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, + /* bash [bash] xmalloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, + /* bash [kernel] page_fault() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, +}; + +/* + * Will be casted to struct ip_callchain which has all 64 bit entries + * of nr and ips[]. + */ +static u64 fake_callchains[][10] = { + /* schedule => run_command => main */ + { 3, FAKE_IP_KERNEL_SCHEDULE, FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, }, + /* main */ + { 1, FAKE_IP_PERF_MAIN, }, + /* cmd_record => run_command => main */ + { 3, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, }, + /* malloc => cmd_record => run_command => main */ + { 4, FAKE_IP_LIBC_MALLOC, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, + FAKE_IP_PERF_MAIN, }, + /* free => cmd_record => run_command => main */ + { 4, FAKE_IP_LIBC_FREE, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, + FAKE_IP_PERF_MAIN, }, + /* main */ + { 1, FAKE_IP_PERF_MAIN, }, + /* page_fault => sys_perf_event_open => run_command => main */ + { 4, FAKE_IP_KERNEL_PAGE_FAULT, FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN, + FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, }, + /* main */ + { 1, FAKE_IP_BASH_MAIN, }, + /* xmalloc => malloc => xmalloc => malloc => xmalloc => main */ + { 6, FAKE_IP_BASH_XMALLOC, FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_XMALLOC, + FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_XMALLOC, FAKE_IP_BASH_MAIN, }, + /* page_fault => malloc => main */ + { 3, FAKE_IP_KERNEL_PAGE_FAULT, FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_MAIN, }, +}; + +static int add_hist_entries(struct hists *hists, struct machine *machine) +{ + struct addr_location al; + struct perf_evsel *evsel = hists_to_evsel(hists); + struct perf_sample sample = { .period = 1000, }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + struct hist_entry_iter iter = { + .hide_unresolved = false, + }; + + if (symbol_conf.cumulate_callchain) + iter.ops = &hist_iter_cumulative; + else + iter.ops = &hist_iter_normal; + + sample.pid = fake_samples[i].pid; + sample.tid = fake_samples[i].pid; + sample.ip = fake_samples[i].ip; + sample.callchain = (struct ip_callchain *)fake_callchains[i]; + + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + if (hist_entry_iter__add(&iter, &al, evsel, &sample, + PERF_MAX_STACK_DEPTH, NULL) < 0) + goto out; + + fake_samples[i].thread = al.thread; + fake_samples[i].map = al.map; + fake_samples[i].sym = al.sym; + } + + return TEST_OK; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return TEST_FAIL; +} + +static void del_hist_entries(struct hists *hists) +{ + struct hist_entry *he; + struct rb_root *root_in; + struct rb_root *root_out; + struct rb_node *node; + + if (sort__need_collapse) + root_in = &hists->entries_collapsed; + else + root_in = hists->entries_in; + + root_out = &hists->entries; + + while (!RB_EMPTY_ROOT(root_out)) { + node = rb_first(root_out); + + he = rb_entry(node, struct hist_entry, rb_node); + rb_erase(node, root_out); + rb_erase(&he->rb_node_in, root_in); + hist_entry__free(he); + } +} + +typedef int (*test_fn_t)(struct perf_evsel *, struct machine *); + +#define COMM(he) (thread__comm_str(he->thread)) +#define DSO(he) (he->ms.map->dso->short_name) +#define SYM(he) (he->ms.sym->name) +#define CPU(he) (he->cpu) +#define PID(he) (he->thread->tid) +#define DEPTH(he) (he->callchain->max_depth) +#define CDSO(cl) (cl->ms.map->dso->short_name) +#define CSYM(cl) (cl->ms.sym->name) + +struct result { + u64 children; + u64 self; + const char *comm; + const char *dso; + const char *sym; +}; + +struct callchain_result { + u64 nr; + struct { + const char *dso; + const char *sym; + } node[10]; +}; + +static int do_test(struct hists *hists, struct result *expected, size_t nr_expected, + struct callchain_result *expected_callchain, size_t nr_callchain) +{ + char buf[32]; + size_t i, c; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + struct callchain_node *cnode; + struct callchain_list *clist; + + /* + * adding and deleting hist entries must be done outside of this + * function since TEST_ASSERT_VAL() returns in case of failure. + */ + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("use callchain: %d, cumulate callchain: %d\n", + symbol_conf.use_callchain, + symbol_conf.cumulate_callchain); + print_hists_out(hists); + } + + root = &hists->entries; + for (node = rb_first(root), i = 0; + node && (he = rb_entry(node, struct hist_entry, rb_node)); + node = rb_next(node), i++) { + scnprintf(buf, sizeof(buf), "Invalid hist entry #%zd", i); + + TEST_ASSERT_VAL("Incorrect number of hist entry", + i < nr_expected); + TEST_ASSERT_VAL(buf, he->stat.period == expected[i].self && + !strcmp(COMM(he), expected[i].comm) && + !strcmp(DSO(he), expected[i].dso) && + !strcmp(SYM(he), expected[i].sym)); + + if (symbol_conf.cumulate_callchain) + TEST_ASSERT_VAL(buf, he->stat_acc->period == expected[i].children); + + if (!symbol_conf.use_callchain) + continue; + + /* check callchain entries */ + root = &he->callchain->node.rb_root; + cnode = rb_entry(rb_first(root), struct callchain_node, rb_node); + + c = 0; + list_for_each_entry(clist, &cnode->val, list) { + scnprintf(buf, sizeof(buf), "Invalid callchain entry #%zd/%zd", i, c); + + TEST_ASSERT_VAL("Incorrect number of callchain entry", + c < expected_callchain[i].nr); + TEST_ASSERT_VAL(buf, + !strcmp(CDSO(clist), expected_callchain[i].node[c].dso) && + !strcmp(CSYM(clist), expected_callchain[i].node[c].sym)); + c++; + } + /* TODO: handle multiple child nodes properly */ + TEST_ASSERT_VAL("Incorrect number of callchain entry", + c <= expected_callchain[i].nr); + } + TEST_ASSERT_VAL("Incorrect number of hist entry", + i == nr_expected); + TEST_ASSERT_VAL("Incorrect number of callchain entry", + !symbol_conf.use_callchain || nr_expected == nr_callchain); + return 0; +} + +/* NO callchain + NO children */ +static int test1(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + /* + * expected output: + * + * Overhead Command Shared Object Symbol + * ======== ======= ============= ============== + * 20.00% perf perf [.] main + * 10.00% bash [kernel] [k] page_fault + * 10.00% bash bash [.] main + * 10.00% bash bash [.] xmalloc + * 10.00% perf [kernel] [k] page_fault + * 10.00% perf [kernel] [k] schedule + * 10.00% perf libc [.] free + * 10.00% perf libc [.] malloc + * 10.00% perf perf [.] cmd_record + */ + struct result expected[] = { + { 0, 2000, "perf", "perf", "main" }, + { 0, 1000, "bash", "[kernel]", "page_fault" }, + { 0, 1000, "bash", "bash", "main" }, + { 0, 1000, "bash", "bash", "xmalloc" }, + { 0, 1000, "perf", "[kernel]", "page_fault" }, + { 0, 1000, "perf", "[kernel]", "schedule" }, + { 0, 1000, "perf", "libc", "free" }, + { 0, 1000, "perf", "libc", "malloc" }, + { 0, 1000, "perf", "perf", "cmd_record" }, + }; + + symbol_conf.use_callchain = false; + symbol_conf.cumulate_callchain = false; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), NULL, 0); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* callcain + NO children */ +static int test2(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + /* + * expected output: + * + * Overhead Command Shared Object Symbol + * ======== ======= ============= ============== + * 20.00% perf perf [.] main + * | + * --- main + * + * 10.00% bash [kernel] [k] page_fault + * | + * --- page_fault + * malloc + * main + * + * 10.00% bash bash [.] main + * | + * --- main + * + * 10.00% bash bash [.] xmalloc + * | + * --- xmalloc + * malloc + * xmalloc <--- NOTE: there's a cycle + * malloc + * xmalloc + * main + * + * 10.00% perf [kernel] [k] page_fault + * | + * --- page_fault + * sys_perf_event_open + * run_command + * main + * + * 10.00% perf [kernel] [k] schedule + * | + * --- schedule + * run_command + * main + * + * 10.00% perf libc [.] free + * | + * --- free + * cmd_record + * run_command + * main + * + * 10.00% perf libc [.] malloc + * | + * --- malloc + * cmd_record + * run_command + * main + * + * 10.00% perf perf [.] cmd_record + * | + * --- cmd_record + * run_command + * main + * + */ + struct result expected[] = { + { 0, 2000, "perf", "perf", "main" }, + { 0, 1000, "bash", "[kernel]", "page_fault" }, + { 0, 1000, "bash", "bash", "main" }, + { 0, 1000, "bash", "bash", "xmalloc" }, + { 0, 1000, "perf", "[kernel]", "page_fault" }, + { 0, 1000, "perf", "[kernel]", "schedule" }, + { 0, 1000, "perf", "libc", "free" }, + { 0, 1000, "perf", "libc", "malloc" }, + { 0, 1000, "perf", "perf", "cmd_record" }, + }; + struct callchain_result expected_callchain[] = { + { + 1, { { "perf", "main" }, }, + }, + { + 3, { { "[kernel]", "page_fault" }, + { "libc", "malloc" }, + { "bash", "main" }, }, + }, + { + 1, { { "bash", "main" }, }, + }, + { + 6, { { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "bash", "main" }, }, + }, + { + 4, { { "[kernel]", "page_fault" }, + { "[kernel]", "sys_perf_event_open" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 3, { { "[kernel]", "schedule" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "free" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "malloc" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 3, { { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + }; + + symbol_conf.use_callchain = true; + symbol_conf.cumulate_callchain = false; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), + expected_callchain, ARRAY_SIZE(expected_callchain)); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* NO callchain + children */ +static int test3(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + /* + * expected output: + * + * Children Self Command Shared Object Symbol + * ======== ======== ======= ============= ======================= + * 70.00% 20.00% perf perf [.] main + * 50.00% 0.00% perf perf [.] run_command + * 30.00% 10.00% bash bash [.] main + * 30.00% 10.00% perf perf [.] cmd_record + * 20.00% 0.00% bash libc [.] malloc + * 10.00% 10.00% bash [kernel] [k] page_fault + * 10.00% 10.00% perf [kernel] [k] schedule + * 10.00% 0.00% perf [kernel] [k] sys_perf_event_open + * 10.00% 10.00% perf [kernel] [k] page_fault + * 10.00% 10.00% perf libc [.] free + * 10.00% 10.00% perf libc [.] malloc + * 10.00% 10.00% bash bash [.] xmalloc + */ + struct result expected[] = { + { 7000, 2000, "perf", "perf", "main" }, + { 5000, 0, "perf", "perf", "run_command" }, + { 3000, 1000, "bash", "bash", "main" }, + { 3000, 1000, "perf", "perf", "cmd_record" }, + { 2000, 0, "bash", "libc", "malloc" }, + { 1000, 1000, "bash", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "[kernel]", "schedule" }, + { 1000, 0, "perf", "[kernel]", "sys_perf_event_open" }, + { 1000, 1000, "perf", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "libc", "free" }, + { 1000, 1000, "perf", "libc", "malloc" }, + { 1000, 1000, "bash", "bash", "xmalloc" }, + }; + + symbol_conf.use_callchain = false; + symbol_conf.cumulate_callchain = true; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), NULL, 0); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* callchain + children */ +static int test4(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + /* + * expected output: + * + * Children Self Command Shared Object Symbol + * ======== ======== ======= ============= ======================= + * 70.00% 20.00% perf perf [.] main + * | + * --- main + * + * 50.00% 0.00% perf perf [.] run_command + * | + * --- run_command + * main + * + * 30.00% 10.00% bash bash [.] main + * | + * --- main + * + * 30.00% 10.00% perf perf [.] cmd_record + * | + * --- cmd_record + * run_command + * main + * + * 20.00% 0.00% bash libc [.] malloc + * | + * --- malloc + * | + * |--50.00%-- xmalloc + * | main + * --50.00%-- main + * + * 10.00% 10.00% bash [kernel] [k] page_fault + * | + * --- page_fault + * malloc + * main + * + * 10.00% 10.00% perf [kernel] [k] schedule + * | + * --- schedule + * run_command + * main + * + * 10.00% 0.00% perf [kernel] [k] sys_perf_event_open + * | + * --- sys_perf_event_open + * run_command + * main + * + * 10.00% 10.00% perf [kernel] [k] page_fault + * | + * --- page_fault + * sys_perf_event_open + * run_command + * main + * + * 10.00% 10.00% perf libc [.] free + * | + * --- free + * cmd_record + * run_command + * main + * + * 10.00% 10.00% perf libc [.] malloc + * | + * --- malloc + * cmd_record + * run_command + * main + * + * 10.00% 10.00% bash bash [.] xmalloc + * | + * --- xmalloc + * malloc + * xmalloc <--- NOTE: there's a cycle + * malloc + * xmalloc + * main + * + */ + struct result expected[] = { + { 7000, 2000, "perf", "perf", "main" }, + { 5000, 0, "perf", "perf", "run_command" }, + { 3000, 1000, "bash", "bash", "main" }, + { 3000, 1000, "perf", "perf", "cmd_record" }, + { 2000, 0, "bash", "libc", "malloc" }, + { 1000, 1000, "bash", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "[kernel]", "schedule" }, + { 1000, 0, "perf", "[kernel]", "sys_perf_event_open" }, + { 1000, 1000, "perf", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "libc", "free" }, + { 1000, 1000, "perf", "libc", "malloc" }, + { 1000, 1000, "bash", "bash", "xmalloc" }, + }; + struct callchain_result expected_callchain[] = { + { + 1, { { "perf", "main" }, }, + }, + { + 2, { { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 1, { { "bash", "main" }, }, + }, + { + 3, { { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "bash", "main" }, + { "bash", "main" }, }, + }, + { + 3, { { "[kernel]", "page_fault" }, + { "libc", "malloc" }, + { "bash", "main" }, }, + }, + { + 3, { { "[kernel]", "schedule" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 3, { { "[kernel]", "sys_perf_event_open" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "[kernel]", "page_fault" }, + { "[kernel]", "sys_perf_event_open" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "free" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "malloc" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 6, { { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "bash", "main" }, }, + }, + }; + + symbol_conf.use_callchain = true; + symbol_conf.cumulate_callchain = true; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), + expected_callchain, ARRAY_SIZE(expected_callchain)); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +int test__hists_cumulate(void) +{ + int err = TEST_FAIL; + struct machines machines; + struct machine *machine; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + size_t i; + test_fn_t testcases[] = { + test1, + test2, + test3, + test4, + }; + + TEST_ASSERT_VAL("No memory", evlist); + + err = parse_events(evlist, "cpu-clock"); + if (err) + goto out; + + machines__init(&machines); + + /* setup threads/dso/map/symbols also */ + machine = setup_fake_machine(&machines); + if (!machine) + goto out; + + if (verbose > 1) + machine__fprintf(machine, stderr); + + evsel = perf_evlist__first(evlist); + + for (i = 0; i < ARRAY_SIZE(testcases); i++) { + err = testcases[i](evsel, machine); + if (err < 0) + break; + } + +out: + /* tear down everything */ + perf_evlist__delete(evlist); + machines__exit(&machines); + + return err; +} diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c new file mode 100644 index 000000000000..821f581fd930 --- /dev/null +++ b/tools/perf/tests/hists_filter.c @@ -0,0 +1,289 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "util/parse-events.h" +#include "tests/tests.h" +#include "tests/hists_common.h" + +struct sample { + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_samples[] = { + /* perf [kernel] schedule() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [libc] malloc() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, /* will be merged */ + /* perf [perf] cmd_record() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, }, + /* perf [kernel] page_fault() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, + /* bash [bash] main() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, + /* bash [bash] xmalloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, + /* bash [libc] malloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, + /* bash [kernel] page_fault() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, +}; + +static int add_hist_entries(struct perf_evlist *evlist, + struct machine *machine __maybe_unused) +{ + struct perf_evsel *evsel; + struct addr_location al; + struct perf_sample sample = { .period = 100, }; + size_t i; + + /* + * each evsel will have 10 samples but the 4th sample + * (perf [perf] main) will be collapsed to an existing entry + * so total 9 entries will be in the tree. + */ + evlist__for_each(evlist, evsel) { + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + struct hist_entry_iter iter = { + .ops = &hist_iter_normal, + .hide_unresolved = false, + }; + + /* make sure it has no filter at first */ + evsel->hists.thread_filter = NULL; + evsel->hists.dso_filter = NULL; + evsel->hists.symbol_filter_str = NULL; + + sample.pid = fake_samples[i].pid; + sample.tid = fake_samples[i].pid; + sample.ip = fake_samples[i].ip; + + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + if (hist_entry_iter__add(&iter, &al, evsel, &sample, + PERF_MAX_STACK_DEPTH, NULL) < 0) + goto out; + + fake_samples[i].thread = al.thread; + fake_samples[i].map = al.map; + fake_samples[i].sym = al.sym; + } + } + + return 0; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return TEST_FAIL; +} + +int test__hists_filter(void) +{ + int err = TEST_FAIL; + struct machines machines; + struct machine *machine; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + + TEST_ASSERT_VAL("No memory", evlist); + + err = parse_events(evlist, "cpu-clock"); + if (err) + goto out; + err = parse_events(evlist, "task-clock"); + if (err) + goto out; + + /* default sort order (comm,dso,sym) will be used */ + if (setup_sorting() < 0) + goto out; + + machines__init(&machines); + + /* setup threads/dso/map/symbols also */ + machine = setup_fake_machine(&machines); + if (!machine) + goto out; + + if (verbose > 1) + machine__fprintf(machine, stderr); + + /* process sample events */ + err = add_hist_entries(evlist, machine); + if (err < 0) + goto out; + + evlist__for_each(evlist, evsel) { + struct hists *hists = &evsel->hists; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("Normal histogram\n"); + print_hists_out(hists); + } + + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + TEST_ASSERT_VAL("Invalid nr hist entries", + hists->nr_entries == 9); + TEST_ASSERT_VAL("Invalid total period", + hists->stats.total_period == 1000); + TEST_ASSERT_VAL("Unmatched nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == + hists->stats.nr_non_filtered_samples); + TEST_ASSERT_VAL("Unmatched nr hist entries", + hists->nr_entries == hists->nr_non_filtered_entries); + TEST_ASSERT_VAL("Unmatched total period", + hists->stats.total_period == + hists->stats.total_non_filtered_period); + + /* now applying thread filter for 'bash' */ + evsel->hists.thread_filter = fake_samples[9].thread; + hists__filter_by_thread(hists); + + if (verbose > 2) { + pr_info("Histogram for thread filter\n"); + print_hists_out(hists); + } + + /* normal stats should be invariant */ + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + TEST_ASSERT_VAL("Invalid nr hist entries", + hists->nr_entries == 9); + TEST_ASSERT_VAL("Invalid total period", + hists->stats.total_period == 1000); + + /* but filter stats are changed */ + TEST_ASSERT_VAL("Unmatched nr samples for thread filter", + hists->stats.nr_non_filtered_samples == 4); + TEST_ASSERT_VAL("Unmatched nr hist entries for thread filter", + hists->nr_non_filtered_entries == 4); + TEST_ASSERT_VAL("Unmatched total period for thread filter", + hists->stats.total_non_filtered_period == 400); + + /* remove thread filter first */ + evsel->hists.thread_filter = NULL; + hists__filter_by_thread(hists); + + /* now applying dso filter for 'kernel' */ + evsel->hists.dso_filter = fake_samples[0].map->dso; + hists__filter_by_dso(hists); + + if (verbose > 2) { + pr_info("Histogram for dso filter\n"); + print_hists_out(hists); + } + + /* normal stats should be invariant */ + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + TEST_ASSERT_VAL("Invalid nr hist entries", + hists->nr_entries == 9); + TEST_ASSERT_VAL("Invalid total period", + hists->stats.total_period == 1000); + + /* but filter stats are changed */ + TEST_ASSERT_VAL("Unmatched nr samples for dso filter", + hists->stats.nr_non_filtered_samples == 3); + TEST_ASSERT_VAL("Unmatched nr hist entries for dso filter", + hists->nr_non_filtered_entries == 3); + TEST_ASSERT_VAL("Unmatched total period for dso filter", + hists->stats.total_non_filtered_period == 300); + + /* remove dso filter first */ + evsel->hists.dso_filter = NULL; + hists__filter_by_dso(hists); + + /* + * now applying symbol filter for 'main'. Also note that + * there's 3 samples that have 'main' symbol but the 4th + * entry of fake_samples was collapsed already so it won't + * be counted as a separate entry but the sample count and + * total period will be remained. + */ + evsel->hists.symbol_filter_str = "main"; + hists__filter_by_symbol(hists); + + if (verbose > 2) { + pr_info("Histogram for symbol filter\n"); + print_hists_out(hists); + } + + /* normal stats should be invariant */ + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + TEST_ASSERT_VAL("Invalid nr hist entries", + hists->nr_entries == 9); + TEST_ASSERT_VAL("Invalid total period", + hists->stats.total_period == 1000); + + /* but filter stats are changed */ + TEST_ASSERT_VAL("Unmatched nr samples for symbol filter", + hists->stats.nr_non_filtered_samples == 3); + TEST_ASSERT_VAL("Unmatched nr hist entries for symbol filter", + hists->nr_non_filtered_entries == 2); + TEST_ASSERT_VAL("Unmatched total period for symbol filter", + hists->stats.total_non_filtered_period == 300); + + /* now applying all filters at once. */ + evsel->hists.thread_filter = fake_samples[1].thread; + evsel->hists.dso_filter = fake_samples[1].map->dso; + hists__filter_by_thread(hists); + hists__filter_by_dso(hists); + + if (verbose > 2) { + pr_info("Histogram for all filters\n"); + print_hists_out(hists); + } + + /* normal stats should be invariant */ + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + TEST_ASSERT_VAL("Invalid nr hist entries", + hists->nr_entries == 9); + TEST_ASSERT_VAL("Invalid total period", + hists->stats.total_period == 1000); + + /* but filter stats are changed */ + TEST_ASSERT_VAL("Unmatched nr samples for all filter", + hists->stats.nr_non_filtered_samples == 2); + TEST_ASSERT_VAL("Unmatched nr hist entries for all filter", + hists->nr_non_filtered_entries == 1); + TEST_ASSERT_VAL("Unmatched total period for all filter", + hists->stats.total_non_filtered_period == 200); + } + + + err = TEST_OK; + +out: + /* tear down everything */ + perf_evlist__delete(evlist); + reset_output_field(); + machines__exit(&machines); + + return err; +} diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c index 7ccbc7b6ae77..d4b34b0f50a2 100644 --- a/tools/perf/tests/hists_link.c +++ b/tools/perf/tests/hists_link.c @@ -8,145 +8,7 @@ #include "machine.h" #include "thread.h" #include "parse-events.h" - -static struct { - u32 pid; - const char *comm; -} fake_threads[] = { - { 100, "perf" }, - { 200, "perf" }, - { 300, "bash" }, -}; - -static struct { - u32 pid; - u64 start; - const char *filename; -} fake_mmap_info[] = { - { 100, 0x40000, "perf" }, - { 100, 0x50000, "libc" }, - { 100, 0xf0000, "[kernel]" }, - { 200, 0x40000, "perf" }, - { 200, 0x50000, "libc" }, - { 200, 0xf0000, "[kernel]" }, - { 300, 0x40000, "bash" }, - { 300, 0x50000, "libc" }, - { 300, 0xf0000, "[kernel]" }, -}; - -struct fake_sym { - u64 start; - u64 length; - const char *name; -}; - -static struct fake_sym perf_syms[] = { - { 700, 100, "main" }, - { 800, 100, "run_command" }, - { 900, 100, "cmd_record" }, -}; - -static struct fake_sym bash_syms[] = { - { 700, 100, "main" }, - { 800, 100, "xmalloc" }, - { 900, 100, "xfree" }, -}; - -static struct fake_sym libc_syms[] = { - { 700, 100, "malloc" }, - { 800, 100, "free" }, - { 900, 100, "realloc" }, -}; - -static struct fake_sym kernel_syms[] = { - { 700, 100, "schedule" }, - { 800, 100, "page_fault" }, - { 900, 100, "sys_perf_event_open" }, -}; - -static struct { - const char *dso_name; - struct fake_sym *syms; - size_t nr_syms; -} fake_symbols[] = { - { "perf", perf_syms, ARRAY_SIZE(perf_syms) }, - { "bash", bash_syms, ARRAY_SIZE(bash_syms) }, - { "libc", libc_syms, ARRAY_SIZE(libc_syms) }, - { "[kernel]", kernel_syms, ARRAY_SIZE(kernel_syms) }, -}; - -static struct machine *setup_fake_machine(struct machines *machines) -{ - struct machine *machine = machines__find(machines, HOST_KERNEL_ID); - size_t i; - - if (machine == NULL) { - pr_debug("Not enough memory for machine setup\n"); - return NULL; - } - - for (i = 0; i < ARRAY_SIZE(fake_threads); i++) { - struct thread *thread; - - thread = machine__findnew_thread(machine, fake_threads[i].pid, - fake_threads[i].pid); - if (thread == NULL) - goto out; - - thread__set_comm(thread, fake_threads[i].comm, 0); - } - - for (i = 0; i < ARRAY_SIZE(fake_mmap_info); i++) { - union perf_event fake_mmap_event = { - .mmap = { - .header = { .misc = PERF_RECORD_MISC_USER, }, - .pid = fake_mmap_info[i].pid, - .tid = fake_mmap_info[i].pid, - .start = fake_mmap_info[i].start, - .len = 0x1000ULL, - .pgoff = 0ULL, - }, - }; - - strcpy(fake_mmap_event.mmap.filename, - fake_mmap_info[i].filename); - - machine__process_mmap_event(machine, &fake_mmap_event, NULL); - } - - for (i = 0; i < ARRAY_SIZE(fake_symbols); i++) { - size_t k; - struct dso *dso; - - dso = __dsos__findnew(&machine->user_dsos, - fake_symbols[i].dso_name); - if (dso == NULL) - goto out; - - /* emulate dso__load() */ - dso__set_loaded(dso, MAP__FUNCTION); - - for (k = 0; k < fake_symbols[i].nr_syms; k++) { - struct symbol *sym; - struct fake_sym *fsym = &fake_symbols[i].syms[k]; - - sym = symbol__new(fsym->start, fsym->length, - STB_GLOBAL, fsym->name); - if (sym == NULL) - goto out; - - symbols__insert(&dso->symbols[MAP__FUNCTION], sym); - } - } - - return machine; - -out: - pr_debug("Not enough memory for machine setup\n"); - machine__delete_threads(machine); - machine__delete(machine); - return NULL; -} +#include "hists_common.h" struct sample { u32 pid; @@ -156,43 +18,44 @@ struct sample { struct symbol *sym; }; +/* For the numbers, see hists_common.c */ static struct sample fake_common_samples[] = { /* perf [kernel] schedule() */ - { .pid = 100, .ip = 0xf0000 + 700, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, /* perf [perf] main() */ - { .pid = 200, .ip = 0x40000 + 700, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, /* perf [perf] cmd_record() */ - { .pid = 200, .ip = 0x40000 + 900, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, }, /* bash [bash] xmalloc() */ - { .pid = 300, .ip = 0x40000 + 800, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, /* bash [libc] malloc() */ - { .pid = 300, .ip = 0x50000 + 700, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, }; static struct sample fake_samples[][5] = { { /* perf [perf] run_command() */ - { .pid = 100, .ip = 0x40000 + 800, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_RUN_COMMAND, }, /* perf [libc] malloc() */ - { .pid = 100, .ip = 0x50000 + 700, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, /* perf [kernel] page_fault() */ - { .pid = 100, .ip = 0xf0000 + 800, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, /* perf [kernel] sys_perf_event_open() */ - { .pid = 200, .ip = 0xf0000 + 900, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN, }, /* bash [libc] free() */ - { .pid = 300, .ip = 0x50000 + 800, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_FREE, }, }, { /* perf [libc] free() */ - { .pid = 200, .ip = 0x50000 + 800, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_LIBC_FREE, }, /* bash [libc] malloc() */ - { .pid = 300, .ip = 0x50000 + 700, }, /* will be merged */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, /* will be merged */ /* bash [bash] xfee() */ - { .pid = 300, .ip = 0x40000 + 900, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XFREE, }, /* bash [libc] realloc() */ - { .pid = 300, .ip = 0x50000 + 900, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_REALLOC, }, /* bash [kernel] page_fault() */ - { .pid = 300, .ip = 0xf0000 + 800, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, }, }; @@ -201,7 +64,7 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) struct perf_evsel *evsel; struct addr_location al; struct hist_entry *he; - struct perf_sample sample = { .cpu = 0, }; + struct perf_sample sample = { .period = 1, }; size_t i = 0, k; /* @@ -218,13 +81,14 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) }; sample.pid = fake_common_samples[k].pid; + sample.tid = fake_common_samples[k].pid; sample.ip = fake_common_samples[k].ip; if (perf_event__preprocess_sample(&event, machine, &al, &sample) < 0) goto out; he = __hists__add_entry(&evsel->hists, &al, NULL, - NULL, NULL, 1, 1, 0); + NULL, NULL, 1, 1, 0, true); if (he == NULL) goto out; @@ -241,13 +105,14 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) }; sample.pid = fake_samples[i][k].pid; + sample.tid = fake_samples[i][k].pid; sample.ip = fake_samples[i][k].ip; if (perf_event__preprocess_sample(&event, machine, &al, &sample) < 0) goto out; he = __hists__add_entry(&evsel->hists, &al, NULL, - NULL, NULL, 1, 1, 0); + NULL, NULL, 1, 1, 0, true); if (he == NULL) goto out; @@ -403,33 +268,6 @@ static int validate_link(struct hists *leader, struct hists *other) return __validate_link(leader, 0) || __validate_link(other, 1); } -static void print_hists(struct hists *hists) -{ - int i = 0; - struct rb_root *root; - struct rb_node *node; - - if (sort__need_collapse) - root = &hists->entries_collapsed; - else - root = hists->entries_in; - - pr_info("----- %s --------\n", __func__); - node = rb_first(root); - while (node) { - struct hist_entry *he; - - he = rb_entry(node, struct hist_entry, rb_node_in); - - pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n", - i, thread__comm_str(he->thread), he->ms.map->dso->short_name, - he->ms.sym->name, he->stat.period); - - i++; - node = rb_next(node); - } -} - int test__hists_link(void) { int err = -1; @@ -471,7 +309,7 @@ int test__hists_link(void) hists__collapse_resort(&evsel->hists, NULL); if (verbose > 2) - print_hists(&evsel->hists); + print_hists_in(&evsel->hists); } first = perf_evlist__first(evlist); @@ -494,6 +332,7 @@ int test__hists_link(void) out: /* tear down everything */ perf_evlist__delete(evlist); + reset_output_field(); machines__exit(&machines); return err; diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c new file mode 100644 index 000000000000..e3bbd6c54c1b --- /dev/null +++ b/tools/perf/tests/hists_output.c @@ -0,0 +1,621 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "util/parse-events.h" +#include "tests/tests.h" +#include "tests/hists_common.h" + +struct sample { + u32 cpu; + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_samples[] = { + /* perf [kernel] schedule() */ + { .cpu = 0, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, + /* perf [perf] main() */ + { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [perf] cmd_record() */ + { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_CMD_RECORD, }, + /* perf [libc] malloc() */ + { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, + /* perf [libc] free() */ + { .cpu = 2, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_FREE, }, + /* perf [perf] main() */ + { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [kernel] page_fault() */ + { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, + /* bash [bash] main() */ + { .cpu = 3, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, + /* bash [bash] xmalloc() */ + { .cpu = 0, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, + /* bash [kernel] page_fault() */ + { .cpu = 1, .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, +}; + +static int add_hist_entries(struct hists *hists, struct machine *machine) +{ + struct addr_location al; + struct perf_evsel *evsel = hists_to_evsel(hists); + struct perf_sample sample = { .period = 100, }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + struct hist_entry_iter iter = { + .ops = &hist_iter_normal, + .hide_unresolved = false, + }; + + sample.cpu = fake_samples[i].cpu; + sample.pid = fake_samples[i].pid; + sample.tid = fake_samples[i].pid; + sample.ip = fake_samples[i].ip; + + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + if (hist_entry_iter__add(&iter, &al, evsel, &sample, + PERF_MAX_STACK_DEPTH, NULL) < 0) + goto out; + + fake_samples[i].thread = al.thread; + fake_samples[i].map = al.map; + fake_samples[i].sym = al.sym; + } + + return TEST_OK; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return TEST_FAIL; +} + +static void del_hist_entries(struct hists *hists) +{ + struct hist_entry *he; + struct rb_root *root_in; + struct rb_root *root_out; + struct rb_node *node; + + if (sort__need_collapse) + root_in = &hists->entries_collapsed; + else + root_in = hists->entries_in; + + root_out = &hists->entries; + + while (!RB_EMPTY_ROOT(root_out)) { + node = rb_first(root_out); + + he = rb_entry(node, struct hist_entry, rb_node); + rb_erase(node, root_out); + rb_erase(&he->rb_node_in, root_in); + hist_entry__free(he); + } +} + +typedef int (*test_fn_t)(struct perf_evsel *, struct machine *); + +#define COMM(he) (thread__comm_str(he->thread)) +#define DSO(he) (he->ms.map->dso->short_name) +#define SYM(he) (he->ms.sym->name) +#define CPU(he) (he->cpu) +#define PID(he) (he->thread->tid) + +/* default sort keys (no field) */ +static int test1(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = NULL; + sort_order = NULL; /* equivalent to sort_order = "comm,dso,sym" */ + + setup_sorting(); + + /* + * expected output: + * + * Overhead Command Shared Object Symbol + * ======== ======= ============= ============== + * 20.00% perf perf [.] main + * 10.00% bash [kernel] [k] page_fault + * 10.00% bash bash [.] main + * 10.00% bash bash [.] xmalloc + * 10.00% perf [kernel] [k] page_fault + * 10.00% perf [kernel] [k] schedule + * 10.00% perf libc [.] free + * 10.00% perf libc [.] malloc + * 10.00% perf perf [.] cmd_record + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "main") && he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "xmalloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "schedule") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "free") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "malloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "cmd_record") && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* mixed fields and sort keys */ +static int test2(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "overhead,cpu"; + sort_order = "pid"; + + setup_sorting(); + + /* + * expected output: + * + * Overhead CPU Command: Pid + * ======== === ============= + * 30.00% 1 perf : 100 + * 10.00% 0 perf : 100 + * 10.00% 2 perf : 100 + * 20.00% 2 perf : 200 + * 10.00% 0 bash : 300 + * 10.00% 1 bash : 300 + * 10.00% 3 bash : 300 + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && he->stat.period == 300); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 0 && PID(he) == 100 && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* fields only (no sort key) */ +static int test3(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "comm,overhead,dso"; + sort_order = NULL; + + setup_sorting(); + + /* + * expected output: + * + * Command Overhead Shared Object + * ======= ======== ============= + * bash 20.00% bash + * bash 10.00% [kernel] + * perf 30.00% perf + * perf 20.00% [kernel] + * perf 20.00% libc + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && + he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + he->stat.period == 300); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + he->stat.period == 200); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* handle duplicate 'dso' field */ +static int test4(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "dso,sym,comm,overhead,dso"; + sort_order = "sym"; + + setup_sorting(); + + /* + * expected output: + * + * Shared Object Symbol Command Overhead + * ============= ============== ======= ======== + * perf [.] cmd_record perf 10.00% + * libc [.] free perf 10.00% + * bash [.] main bash 10.00% + * perf [.] main perf 20.00% + * libc [.] malloc perf 10.00% + * [kernel] [k] page_fault bash 10.00% + * [kernel] [k] page_fault perf 10.00% + * [kernel] [k] schedule perf 10.00% + * bash [.] xmalloc bash 10.00% + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "cmd_record") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "free") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "main") && + !strcmp(COMM(he), "bash") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "main") && + !strcmp(COMM(he), "perf") && he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "malloc") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") && + !strcmp(COMM(he), "bash") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "schedule") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "xmalloc") && + !strcmp(COMM(he), "bash") && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* full sort keys w/o overhead field */ +static int test5(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "cpu,pid,comm,dso,sym"; + sort_order = "dso,pid"; + + setup_sorting(); + + /* + * expected output: + * + * CPU Command: Pid Command Shared Object Symbol + * === ============= ======= ============= ============== + * 0 perf: 100 perf [kernel] [k] schedule + * 2 perf: 200 perf [kernel] [k] page_fault + * 1 bash: 300 bash [kernel] [k] page_fault + * 0 bash: 300 bash bash [.] xmalloc + * 3 bash: 300 bash bash [.] main + * 1 perf: 100 perf libc [.] malloc + * 2 perf: 100 perf libc [.] free + * 1 perf: 100 perf perf [.] cmd_record + * 1 perf: 100 perf perf [.] main + * 2 perf: 200 perf perf [.] main + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 0 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "schedule") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 2 && PID(he) == 200 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 300 && + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 0 && PID(he) == 300 && + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "xmalloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 3 && PID(he) == 300 && + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "malloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 2 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "free") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "cmd_record") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 2 && PID(he) == 200 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +int test__hists_output(void) +{ + int err = TEST_FAIL; + struct machines machines; + struct machine *machine; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + size_t i; + test_fn_t testcases[] = { + test1, + test2, + test3, + test4, + test5, + }; + + TEST_ASSERT_VAL("No memory", evlist); + + err = parse_events(evlist, "cpu-clock"); + if (err) + goto out; + + machines__init(&machines); + + /* setup threads/dso/map/symbols also */ + machine = setup_fake_machine(&machines); + if (!machine) + goto out; + + if (verbose > 1) + machine__fprintf(machine, stderr); + + evsel = perf_evlist__first(evlist); + + for (i = 0; i < ARRAY_SIZE(testcases); i++) { + err = testcases[i](evsel, machine); + if (err < 0) + break; + } + +out: + /* tear down everything */ + perf_evlist__delete(evlist); + machines__exit(&machines); + + return err; +} diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c index 497957f269d8..7a5ab7b0b8f6 100644 --- a/tools/perf/tests/keep-tracking.c +++ b/tools/perf/tests/keep-tracking.c @@ -1,4 +1,4 @@ -#include <sys/types.h> +#include <linux/types.h> #include <unistd.h> #include <sys/prctl.h> diff --git a/tools/perf/tests/make b/tools/perf/tests/make index 2f92d6e7ee00..69a71ff84e01 100644 --- a/tools/perf/tests/make +++ b/tools/perf/tests/make @@ -205,8 +205,7 @@ $(run): ( eval $$cmd ) >> $@ 2>&1; \ echo " test: $(call test,$@)" >> $@ 2>&1; \ $(call test,$@) && \ - rm -f $@ \ - rm -rf $$TMP_DEST + rm -rf $@ $$TMP_DEST || (cat $@ ; false) $(run_O): $(call clean) @@ -217,9 +216,7 @@ $(run_O): ( eval $$cmd ) >> $@ 2>&1 && \ echo " test: $(call test_O,$@)" >> $@ 2>&1; \ $(call test_O,$@) && \ - rm -f $@ && \ - rm -rf $$TMP_O \ - rm -rf $$TMP_DEST + rm -rf $@ $$TMP_O $$TMP_DEST || (cat $@ ; false) tarpkg: @cmd="$(PERF)/tests/perf-targz-src-pkg $(PERF)"; \ diff --git a/tools/perf/tests/mmap-thread-lookup.c b/tools/perf/tests/mmap-thread-lookup.c new file mode 100644 index 000000000000..4a456fef66ca --- /dev/null +++ b/tools/perf/tests/mmap-thread-lookup.c @@ -0,0 +1,233 @@ +#include <unistd.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include "debug.h" +#include "tests.h" +#include "machine.h" +#include "thread_map.h" +#include "symbol.h" +#include "thread.h" + +#define THREADS 4 + +static int go_away; + +struct thread_data { + pthread_t pt; + pid_t tid; + void *map; + int ready[2]; +}; + +static struct thread_data threads[THREADS]; + +static int thread_init(struct thread_data *td) +{ + void *map; + + map = mmap(NULL, page_size, + PROT_READ|PROT_WRITE|PROT_EXEC, + MAP_SHARED|MAP_ANONYMOUS, -1, 0); + + if (map == MAP_FAILED) { + perror("mmap failed"); + return -1; + } + + td->map = map; + td->tid = syscall(SYS_gettid); + + pr_debug("tid = %d, map = %p\n", td->tid, map); + return 0; +} + +static void *thread_fn(void *arg) +{ + struct thread_data *td = arg; + ssize_t ret; + int go; + + if (thread_init(td)) + return NULL; + + /* Signal thread_create thread is initialized. */ + ret = write(td->ready[1], &go, sizeof(int)); + if (ret != sizeof(int)) { + pr_err("failed to notify\n"); + return NULL; + } + + while (!go_away) { + /* Waiting for main thread to kill us. */ + usleep(100); + } + + munmap(td->map, page_size); + return NULL; +} + +static int thread_create(int i) +{ + struct thread_data *td = &threads[i]; + int err, go; + + if (pipe(td->ready)) + return -1; + + err = pthread_create(&td->pt, NULL, thread_fn, td); + if (!err) { + /* Wait for thread initialization. */ + ssize_t ret = read(td->ready[0], &go, sizeof(int)); + err = ret != sizeof(int); + } + + close(td->ready[0]); + close(td->ready[1]); + return err; +} + +static int threads_create(void) +{ + struct thread_data *td0 = &threads[0]; + int i, err = 0; + + go_away = 0; + + /* 0 is main thread */ + if (thread_init(td0)) + return -1; + + for (i = 1; !err && i < THREADS; i++) + err = thread_create(i); + + return err; +} + +static int threads_destroy(void) +{ + struct thread_data *td0 = &threads[0]; + int i, err = 0; + + /* cleanup the main thread */ + munmap(td0->map, page_size); + + go_away = 1; + + for (i = 1; !err && i < THREADS; i++) + err = pthread_join(threads[i].pt, NULL); + + return err; +} + +typedef int (*synth_cb)(struct machine *machine); + +static int synth_all(struct machine *machine) +{ + return perf_event__synthesize_threads(NULL, + perf_event__process, + machine, 0); +} + +static int synth_process(struct machine *machine) +{ + struct thread_map *map; + int err; + + map = thread_map__new_by_pid(getpid()); + + err = perf_event__synthesize_thread_map(NULL, map, + perf_event__process, + machine, 0); + + thread_map__delete(map); + return err; +} + +static int mmap_events(synth_cb synth) +{ + struct machines machines; + struct machine *machine; + int err, i; + + /* + * The threads_create will not return before all threads + * are spawned and all created memory map. + * + * They will loop until threads_destroy is called, so we + * can safely run synthesizing function. + */ + TEST_ASSERT_VAL("failed to create threads", !threads_create()); + + machines__init(&machines); + machine = &machines.host; + + dump_trace = verbose > 1 ? 1 : 0; + + err = synth(machine); + + dump_trace = 0; + + TEST_ASSERT_VAL("failed to destroy threads", !threads_destroy()); + TEST_ASSERT_VAL("failed to synthesize maps", !err); + + /* + * All data is synthesized, try to find map for each + * thread object. + */ + for (i = 0; i < THREADS; i++) { + struct thread_data *td = &threads[i]; + struct addr_location al; + struct thread *thread; + + thread = machine__findnew_thread(machine, getpid(), td->tid); + + pr_debug("looking for map %p\n", td->map); + + thread__find_addr_map(thread, machine, + PERF_RECORD_MISC_USER, MAP__FUNCTION, + (unsigned long) (td->map + 1), &al); + + if (!al.map) { + pr_debug("failed, couldn't find map\n"); + err = -1; + break; + } + + pr_debug("map %p, addr %" PRIx64 "\n", al.map, al.map->start); + } + + machine__delete_threads(machine); + machines__exit(&machines); + return err; +} + +/* + * This test creates 'THREADS' number of threads (including + * main thread) and each thread creates memory map. + * + * When threads are created, we synthesize them with both + * (separate tests): + * perf_event__synthesize_thread_map (process based) + * perf_event__synthesize_threads (global) + * + * We test we can find all memory maps via: + * thread__find_addr_map + * + * by using all thread objects. + */ +int test__mmap_thread_lookup(void) +{ + /* perf_event__synthesize_threads synthesize */ + TEST_ASSERT_VAL("failed with sythesizing all", + !mmap_events(synth_all)); + + /* perf_event__synthesize_thread_map synthesize */ + TEST_ASSERT_VAL("failed with sythesizing process", + !mmap_events(synth_process)); + + return 0; +} diff --git a/tools/perf/tests/open-syscall-tp-fields.c b/tools/perf/tests/open-syscall-tp-fields.c index c505ef2af245..0785b64ffd6c 100644 --- a/tools/perf/tests/open-syscall-tp-fields.c +++ b/tools/perf/tests/open-syscall-tp-fields.c @@ -3,6 +3,7 @@ #include "evsel.h" #include "thread_map.h" #include "tests.h" +#include "debug.h" int test__syscall_open_tp_fields(void) { diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c index 8605ff5572ae..5941927a4b7f 100644 --- a/tools/perf/tests/parse-events.c +++ b/tools/perf/tests/parse-events.c @@ -5,6 +5,7 @@ #include <api/fs/fs.h> #include <api/fs/debugfs.h> #include "tests.h" +#include "debug.h" #include <linux/hw_breakpoint.h> #define PERF_TP_SAMPLE_TYPE (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | \ @@ -1174,188 +1175,240 @@ static int test__all_tracepoints(struct perf_evlist *evlist) struct evlist_test { const char *name; __u32 type; + const int id; int (*check)(struct perf_evlist *evlist); }; static struct evlist_test test__events[] = { - [0] = { + { .name = "syscalls:sys_enter_open", .check = test__checkevent_tracepoint, + .id = 0, }, - [1] = { + { .name = "syscalls:*", .check = test__checkevent_tracepoint_multi, + .id = 1, }, - [2] = { + { .name = "r1a", .check = test__checkevent_raw, + .id = 2, }, - [3] = { + { .name = "1:1", .check = test__checkevent_numeric, + .id = 3, }, - [4] = { + { .name = "instructions", .check = test__checkevent_symbolic_name, + .id = 4, }, - [5] = { + { .name = "cycles/period=100000,config2/", .check = test__checkevent_symbolic_name_config, + .id = 5, }, - [6] = { + { .name = "faults", .check = test__checkevent_symbolic_alias, + .id = 6, }, - [7] = { + { .name = "L1-dcache-load-miss", .check = test__checkevent_genhw, + .id = 7, }, - [8] = { + { .name = "mem:0", .check = test__checkevent_breakpoint, + .id = 8, }, - [9] = { + { .name = "mem:0:x", .check = test__checkevent_breakpoint_x, + .id = 9, }, - [10] = { + { .name = "mem:0:r", .check = test__checkevent_breakpoint_r, + .id = 10, }, - [11] = { + { .name = "mem:0:w", .check = test__checkevent_breakpoint_w, + .id = 11, }, - [12] = { + { .name = "syscalls:sys_enter_open:k", .check = test__checkevent_tracepoint_modifier, + .id = 12, }, - [13] = { + { .name = "syscalls:*:u", .check = test__checkevent_tracepoint_multi_modifier, + .id = 13, }, - [14] = { + { .name = "r1a:kp", .check = test__checkevent_raw_modifier, + .id = 14, }, - [15] = { + { .name = "1:1:hp", .check = test__checkevent_numeric_modifier, + .id = 15, }, - [16] = { + { .name = "instructions:h", .check = test__checkevent_symbolic_name_modifier, + .id = 16, }, - [17] = { + { .name = "faults:u", .check = test__checkevent_symbolic_alias_modifier, + .id = 17, }, - [18] = { + { .name = "L1-dcache-load-miss:kp", .check = test__checkevent_genhw_modifier, + .id = 18, }, - [19] = { + { .name = "mem:0:u", .check = test__checkevent_breakpoint_modifier, + .id = 19, }, - [20] = { + { .name = "mem:0:x:k", .check = test__checkevent_breakpoint_x_modifier, + .id = 20, }, - [21] = { + { .name = "mem:0:r:hp", .check = test__checkevent_breakpoint_r_modifier, + .id = 21, }, - [22] = { + { .name = "mem:0:w:up", .check = test__checkevent_breakpoint_w_modifier, + .id = 22, }, - [23] = { + { .name = "r1,syscalls:sys_enter_open:k,1:1:hp", .check = test__checkevent_list, + .id = 23, }, - [24] = { + { .name = "instructions:G", .check = test__checkevent_exclude_host_modifier, + .id = 24, }, - [25] = { + { .name = "instructions:H", .check = test__checkevent_exclude_guest_modifier, + .id = 25, }, - [26] = { + { .name = "mem:0:rw", .check = test__checkevent_breakpoint_rw, + .id = 26, }, - [27] = { + { .name = "mem:0:rw:kp", .check = test__checkevent_breakpoint_rw_modifier, + .id = 27, }, - [28] = { + { .name = "{instructions:k,cycles:upp}", .check = test__group1, + .id = 28, }, - [29] = { + { .name = "{faults:k,cache-references}:u,cycles:k", .check = test__group2, + .id = 29, }, - [30] = { + { .name = "group1{syscalls:sys_enter_open:H,cycles:kppp},group2{cycles,1:3}:G,instructions:u", .check = test__group3, + .id = 30, }, - [31] = { + { .name = "{cycles:u,instructions:kp}:p", .check = test__group4, + .id = 31, }, - [32] = { + { .name = "{cycles,instructions}:G,{cycles:G,instructions:G},cycles", .check = test__group5, + .id = 32, }, - [33] = { + { .name = "*:*", .check = test__all_tracepoints, + .id = 33, }, - [34] = { + { .name = "{cycles,cache-misses:G}:H", .check = test__group_gh1, + .id = 34, }, - [35] = { + { .name = "{cycles,cache-misses:H}:G", .check = test__group_gh2, + .id = 35, }, - [36] = { + { .name = "{cycles:G,cache-misses:H}:u", .check = test__group_gh3, + .id = 36, }, - [37] = { + { .name = "{cycles:G,cache-misses:H}:uG", .check = test__group_gh4, + .id = 37, }, - [38] = { + { .name = "{cycles,cache-misses,branch-misses}:S", .check = test__leader_sample1, + .id = 38, }, - [39] = { + { .name = "{instructions,branch-misses}:Su", .check = test__leader_sample2, + .id = 39, }, - [40] = { + { .name = "instructions:uDp", .check = test__checkevent_pinned_modifier, + .id = 40, }, - [41] = { + { .name = "{cycles,cache-misses,branch-misses}:D", .check = test__pinned_group, + .id = 41, + }, +#if defined(__s390x__) + { + .name = "kvm-s390:kvm_s390_create_vm", + .check = test__checkevent_tracepoint, + .id = 100, }, +#endif }; static struct evlist_test test__events_pmu[] = { - [0] = { + { .name = "cpu/config=10,config1,config2=3,period=1000/u", .check = test__checkevent_pmu, + .id = 0, }, - [1] = { + { .name = "cpu/config=1,name=krava/u,cpu/config=2/u", .check = test__checkevent_pmu_name, + .id = 1, }, }; @@ -1402,7 +1455,7 @@ static int test_events(struct evlist_test *events, unsigned cnt) for (i = 0; i < cnt; i++) { struct evlist_test *e = &events[i]; - pr_debug("running test %d '%s'\n", i, e->name); + pr_debug("running test %d '%s'\n", e->id, e->name); ret1 = test_event(e); if (ret1) ret2 = ret1; diff --git a/tools/perf/tests/parse-no-sample-id-all.c b/tools/perf/tests/parse-no-sample-id-all.c index e117b6c6a248..2c63ea658541 100644 --- a/tools/perf/tests/parse-no-sample-id-all.c +++ b/tools/perf/tests/parse-no-sample-id-all.c @@ -1,4 +1,4 @@ -#include <sys/types.h> +#include <linux/types.h> #include <stddef.h> #include "tests.h" @@ -7,6 +7,7 @@ #include "evlist.h" #include "header.h" #include "util.h" +#include "debug.h" static int process_event(struct perf_evlist **pevlist, union perf_event *event) { diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c index 47146d388dbf..f238442b238a 100644 --- a/tools/perf/tests/perf-time-to-tsc.c +++ b/tools/perf/tests/perf-time-to-tsc.c @@ -1,7 +1,6 @@ #include <stdio.h> -#include <sys/types.h> #include <unistd.h> -#include <inttypes.h> +#include <linux/types.h> #include <sys/prctl.h> #include "parse-events.h" @@ -9,10 +8,9 @@ #include "evsel.h" #include "thread_map.h" #include "cpumap.h" +#include "tsc.h" #include "tests.h" -#include "../arch/x86/util/tsc.h" - #define CHECK__(x) { \ while ((x) < 0) { \ pr_debug(#x " failed!\n"); \ @@ -27,15 +25,6 @@ } \ } -static u64 rdtsc(void) -{ - unsigned int low, high; - - asm volatile("rdtsc" : "=a" (low), "=d" (high)); - - return low | ((u64)high) << 32; -} - /** * test__perf_time_to_tsc - test converting perf time to TSC. * diff --git a/tools/perf/tests/rdpmc.c b/tools/perf/tests/rdpmc.c index 46649c25fa5e..c04d1f268576 100644 --- a/tools/perf/tests/rdpmc.c +++ b/tools/perf/tests/rdpmc.c @@ -2,10 +2,11 @@ #include <stdlib.h> #include <signal.h> #include <sys/mman.h> -#include "types.h" +#include <linux/types.h> #include "perf.h" #include "debug.h" #include "tests.h" +#include "cloexec.h" #if defined(__x86_64__) || defined(__i386__) @@ -104,7 +105,8 @@ static int __test__rdpmc(void) sa.sa_sigaction = segfault_handler; sigaction(SIGSEGV, &sa, NULL); - fd = sys_perf_event_open(&attr, 0, -1, -1, 0); + fd = sys_perf_event_open(&attr, 0, -1, -1, + perf_event_open_cloexec_flag()); if (fd < 0) { pr_err("Error: sys_perf_event_open() syscall returned " "with %d (%s)\n", fd, strerror(errno)); diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c index 0014d3c8c21c..ca292f9a4ae2 100644 --- a/tools/perf/tests/sample-parsing.c +++ b/tools/perf/tests/sample-parsing.c @@ -1,9 +1,10 @@ #include <stdbool.h> -#include <inttypes.h> +#include <linux/types.h> #include "util.h" #include "event.h" #include "evsel.h" +#include "debug.h" #include "tests.h" diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index a24795ca002d..ed64790a395f 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -28,6 +28,8 @@ int test__syscall_open_tp_fields(void); int test__pmu(void); int test__attr(void); int test__dso_data(void); +int test__dso_data_cache(void); +int test__dso_data_reopen(void); int test__parse_events(void); int test__hists_link(void); int test__python_use(void); @@ -41,8 +43,13 @@ int test__sample_parsing(void); int test__keep_tracking(void); int test__parse_no_sample_id_all(void); int test__dwarf_unwind(void); +int test__hists_filter(void); +int test__mmap_thread_lookup(void); +int test__thread_mg_share(void); +int test__hists_output(void); +int test__hists_cumulate(void); -#if defined(__x86_64__) || defined(__i386__) +#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) #ifdef HAVE_DWARF_UNWIND_SUPPORT struct thread; struct perf_sample; diff --git a/tools/perf/tests/thread-mg-share.c b/tools/perf/tests/thread-mg-share.c new file mode 100644 index 000000000000..b028499dd3cf --- /dev/null +++ b/tools/perf/tests/thread-mg-share.c @@ -0,0 +1,91 @@ +#include "tests.h" +#include "machine.h" +#include "thread.h" +#include "map.h" +#include "debug.h" + +int test__thread_mg_share(void) +{ + struct machines machines; + struct machine *machine; + + /* thread group */ + struct thread *leader; + struct thread *t1, *t2, *t3; + struct map_groups *mg; + + /* other process */ + struct thread *other, *other_leader; + struct map_groups *other_mg; + + /* + * This test create 2 processes abstractions (struct thread) + * with several threads and checks they properly share and + * maintain map groups info (struct map_groups). + * + * thread group (pid: 0, tids: 0, 1, 2, 3) + * other group (pid: 4, tids: 4, 5) + */ + + machines__init(&machines); + machine = &machines.host; + + /* create process with 4 threads */ + leader = machine__findnew_thread(machine, 0, 0); + t1 = machine__findnew_thread(machine, 0, 1); + t2 = machine__findnew_thread(machine, 0, 2); + t3 = machine__findnew_thread(machine, 0, 3); + + /* and create 1 separated process, without thread leader */ + other = machine__findnew_thread(machine, 4, 5); + + TEST_ASSERT_VAL("failed to create threads", + leader && t1 && t2 && t3 && other); + + mg = leader->mg; + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 4); + + /* test the map groups pointer is shared */ + TEST_ASSERT_VAL("map groups don't match", mg == t1->mg); + TEST_ASSERT_VAL("map groups don't match", mg == t2->mg); + TEST_ASSERT_VAL("map groups don't match", mg == t3->mg); + + /* + * Verify the other leader was created by previous call. + * It should have shared map groups with no change in + * refcnt. + */ + other_leader = machine__find_thread(machine, 4, 4); + TEST_ASSERT_VAL("failed to find other leader", other_leader); + + other_mg = other->mg; + TEST_ASSERT_VAL("wrong refcnt", other_mg->refcnt == 2); + + TEST_ASSERT_VAL("map groups don't match", other_mg == other_leader->mg); + + /* release thread group */ + thread__delete(leader); + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 3); + + thread__delete(t1); + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 2); + + thread__delete(t2); + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 1); + + thread__delete(t3); + + /* release other group */ + thread__delete(other_leader); + TEST_ASSERT_VAL("wrong refcnt", other_mg->refcnt == 1); + + thread__delete(other); + + /* + * Cannot call machine__delete_threads(machine) now, + * because we've already released all the threads. + */ + + machines__exit(&machines); + return 0; +} diff --git a/tools/perf/ui/browser.c b/tools/perf/ui/browser.c index d11541d4d7d7..6680fa5cb9dd 100644 --- a/tools/perf/ui/browser.c +++ b/tools/perf/ui/browser.c @@ -150,7 +150,7 @@ unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser) while (nd != NULL) { ui_browser__gotorc(browser, row, 0); browser->write(browser, nd, row); - if (++row == browser->height) + if (++row == browser->rows) break; nd = rb_next(nd); } @@ -166,7 +166,7 @@ bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row) void ui_browser__refresh_dimensions(struct ui_browser *browser) { browser->width = SLtt_Screen_Cols - 1; - browser->height = SLtt_Screen_Rows - 2; + browser->height = browser->rows = SLtt_Screen_Rows - 2; browser->y = 1; browser->x = 0; } @@ -194,7 +194,7 @@ int ui_browser__warning(struct ui_browser *browser, int timeout, ui_helpline__vpush(format, args); va_end(args); } else { - while ((key == ui__question_window("Warning!", text, + while ((key = ui__question_window("Warning!", text, "Press any key...", timeout)) == K_RESIZE) ui_browser__handle_resize(browser); @@ -250,7 +250,10 @@ int ui_browser__show(struct ui_browser *browser, const char *title, int err; va_list ap; - ui_browser__refresh_dimensions(browser); + if (browser->refresh_dimensions == NULL) + browser->refresh_dimensions = ui_browser__refresh_dimensions; + + browser->refresh_dimensions(browser); pthread_mutex_lock(&ui__lock); __ui_browser__show_title(browser, title); @@ -279,7 +282,7 @@ static void ui_browser__scrollbar_set(struct ui_browser *browser) { int height = browser->height, h = 0, pct = 0, col = browser->width, - row = browser->y - 1; + row = 0; if (browser->nr_entries > 1) { pct = ((browser->index * (browser->height - 1)) / @@ -367,7 +370,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) if (key == K_RESIZE) { ui__refresh_dimensions(false); - ui_browser__refresh_dimensions(browser); + browser->refresh_dimensions(browser); __ui_browser__show_title(browser, browser->title); ui_helpline__puts(browser->helpline); continue; @@ -389,7 +392,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) if (browser->index == browser->nr_entries - 1) break; ++browser->index; - if (browser->index == browser->top_idx + browser->height) { + if (browser->index == browser->top_idx + browser->rows) { ++browser->top_idx; browser->seek(browser, +1, SEEK_CUR); } @@ -405,10 +408,10 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) break; case K_PGDN: case ' ': - if (browser->top_idx + browser->height > browser->nr_entries - 1) + if (browser->top_idx + browser->rows > browser->nr_entries - 1) break; - offset = browser->height; + offset = browser->rows; if (browser->index + offset > browser->nr_entries - 1) offset = browser->nr_entries - 1 - browser->index; browser->index += offset; @@ -419,10 +422,10 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) if (browser->top_idx == 0) break; - if (browser->top_idx < browser->height) + if (browser->top_idx < browser->rows) offset = browser->top_idx; else - offset = browser->height; + offset = browser->rows; browser->index -= offset; browser->top_idx -= offset; @@ -432,7 +435,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) ui_browser__reset_index(browser); break; case K_END: - offset = browser->height - 1; + offset = browser->rows - 1; if (offset >= browser->nr_entries) offset = browser->nr_entries - 1; @@ -462,7 +465,7 @@ unsigned int ui_browser__list_head_refresh(struct ui_browser *browser) if (!browser->filter || !browser->filter(browser, pos)) { ui_browser__gotorc(browser, row, 0); browser->write(browser, pos, row); - if (++row == browser->height) + if (++row == browser->rows) break; } } @@ -587,7 +590,7 @@ unsigned int ui_browser__argv_refresh(struct ui_browser *browser) if (!browser->filter || !browser->filter(browser, *pos)) { ui_browser__gotorc(browser, row, 0); browser->write(browser, pos, row); - if (++row == browser->height) + if (++row == browser->rows) break; } @@ -623,7 +626,7 @@ static void __ui_browser__line_arrow_up(struct ui_browser *browser, SLsmg_set_char_set(1); - if (start < browser->top_idx + browser->height) { + if (start < browser->top_idx + browser->rows) { row = start - browser->top_idx; ui_browser__gotorc(browser, row, column); SLsmg_write_char(SLSMG_LLCORN_CHAR); @@ -633,7 +636,7 @@ static void __ui_browser__line_arrow_up(struct ui_browser *browser, if (row-- == 0) goto out; } else - row = browser->height - 1; + row = browser->rows - 1; if (end > browser->top_idx) end_row = end - browser->top_idx; @@ -675,8 +678,8 @@ static void __ui_browser__line_arrow_down(struct ui_browser *browser, } else row = 0; - if (end >= browser->top_idx + browser->height) - end_row = browser->height - 1; + if (end >= browser->top_idx + browser->rows) + end_row = browser->rows - 1; else end_row = end - browser->top_idx; @@ -684,7 +687,7 @@ static void __ui_browser__line_arrow_down(struct ui_browser *browser, SLsmg_draw_vline(end_row - row + 1); ui_browser__gotorc(browser, end_row, column); - if (end < browser->top_idx + browser->height) { + if (end < browser->top_idx + browser->rows) { SLsmg_write_char(SLSMG_LLCORN_CHAR); ui_browser__gotorc(browser, end_row, column + 1); SLsmg_write_char(SLSMG_HLINE_CHAR); diff --git a/tools/perf/ui/browser.h b/tools/perf/ui/browser.h index 118cca29dd26..92ae72113965 100644 --- a/tools/perf/ui/browser.h +++ b/tools/perf/ui/browser.h @@ -1,9 +1,7 @@ #ifndef _PERF_UI_BROWSER_H_ #define _PERF_UI_BROWSER_H_ 1 -#include <stdbool.h> -#include <sys/types.h> -#include "../types.h" +#include <linux/types.h> #define HE_COLORSET_TOP 50 #define HE_COLORSET_MEDIUM 51 @@ -16,11 +14,12 @@ struct ui_browser { u64 index, top_idx; void *top, *entries; - u16 y, x, width, height; + u16 y, x, width, height, rows; int current_color; void *priv; const char *title; char *helpline; + void (*refresh_dimensions)(struct ui_browser *browser); unsigned int (*refresh)(struct ui_browser *browser); void (*write)(struct ui_browser *browser, void *entry, int row); void (*seek)(struct ui_browser *browser, off_t offset, int whence); diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index 7ec871af3f6f..a94b11fc5e00 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -17,6 +17,7 @@ #include "../util.h" #include "../ui.h" #include "map.h" +#include "annotate.h" struct hist_browser { struct ui_browser b; @@ -25,26 +26,86 @@ struct hist_browser { struct map_symbol *selection; int print_seq; bool show_dso; + bool show_headers; float min_pcnt; - u64 nr_pcnt_entries; + u64 nr_non_filtered_entries; + u64 nr_callchain_rows; }; extern void hist_browser__init_hpp(void); -static int hists__browser_title(struct hists *hists, char *bf, size_t size, - const char *ev_name); +static int hists__browser_title(struct hists *hists, char *bf, size_t size); +static void hist_browser__update_nr_entries(struct hist_browser *hb); -static void hist_browser__refresh_dimensions(struct hist_browser *browser) +static struct rb_node *hists__filter_entries(struct rb_node *nd, + float min_pcnt); + +static bool hist_browser__has_filter(struct hist_browser *hb) +{ + return hists__has_filter(hb->hists) || hb->min_pcnt; +} + +static u32 hist_browser__nr_entries(struct hist_browser *hb) +{ + u32 nr_entries; + + if (hist_browser__has_filter(hb)) + nr_entries = hb->nr_non_filtered_entries; + else + nr_entries = hb->hists->nr_entries; + + return nr_entries + hb->nr_callchain_rows; +} + +static void hist_browser__update_rows(struct hist_browser *hb) +{ + struct ui_browser *browser = &hb->b; + u16 header_offset = hb->show_headers ? 1 : 0, index_row; + + browser->rows = browser->height - header_offset; + /* + * Verify if we were at the last line and that line isn't + * visibe because we now show the header line(s). + */ + index_row = browser->index - browser->top_idx; + if (index_row >= browser->rows) + browser->index -= index_row - browser->rows + 1; +} + +static void hist_browser__refresh_dimensions(struct ui_browser *browser) { + struct hist_browser *hb = container_of(browser, struct hist_browser, b); + /* 3 == +/- toggle symbol before actual hist_entry rendering */ - browser->b.width = 3 + (hists__sort_list_width(browser->hists) + - sizeof("[k]")); + browser->width = 3 + (hists__sort_list_width(hb->hists) + sizeof("[k]")); + /* + * FIXME: Just keeping existing behaviour, but this really should be + * before updating browser->width, as it will invalidate the + * calculation above. Fix this and the fallout in another + * changeset. + */ + ui_browser__refresh_dimensions(browser); + hist_browser__update_rows(hb); +} + +static void hist_browser__gotorc(struct hist_browser *browser, int row, int column) +{ + u16 header_offset = browser->show_headers ? 1 : 0; + + ui_browser__gotorc(&browser->b, row + header_offset, column); } static void hist_browser__reset(struct hist_browser *browser) { - browser->b.nr_entries = browser->hists->nr_entries; - hist_browser__refresh_dimensions(browser); + /* + * The hists__remove_entry_filter() already folds non-filtered + * entries so we can assume it has 0 callchain rows. + */ + browser->nr_callchain_rows = 0; + + hist_browser__update_nr_entries(browser); + browser->b.nr_entries = hist_browser__nr_entries(browser); + hist_browser__refresh_dimensions(&browser->b); ui_browser__reset_index(&browser->b); } @@ -198,14 +259,16 @@ static bool hist_browser__toggle_fold(struct hist_browser *browser) struct hist_entry *he = browser->he_selection; hist_entry__init_have_children(he); - browser->hists->nr_entries -= he->nr_rows; + browser->b.nr_entries -= he->nr_rows; + browser->nr_callchain_rows -= he->nr_rows; if (he->ms.unfolded) he->nr_rows = callchain__count_rows(&he->sorted_chain); else he->nr_rows = 0; - browser->hists->nr_entries += he->nr_rows; - browser->b.nr_entries = browser->hists->nr_entries; + + browser->b.nr_entries += he->nr_rows; + browser->nr_callchain_rows += he->nr_rows; return true; } @@ -280,23 +343,27 @@ static void hist_entry__set_folding(struct hist_entry *he, bool unfold) he->nr_rows = 0; } -static void hists__set_folding(struct hists *hists, bool unfold) +static void +__hist_browser__set_folding(struct hist_browser *browser, bool unfold) { struct rb_node *nd; + struct hists *hists = browser->hists; - hists->nr_entries = 0; - - for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { + for (nd = rb_first(&hists->entries); + (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL; + nd = rb_next(nd)) { struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); hist_entry__set_folding(he, unfold); - hists->nr_entries += 1 + he->nr_rows; + browser->nr_callchain_rows += he->nr_rows; } } static void hist_browser__set_folding(struct hist_browser *browser, bool unfold) { - hists__set_folding(browser->hists, unfold); - browser->b.nr_entries = browser->hists->nr_entries; + browser->nr_callchain_rows = 0; + __hist_browser__set_folding(browser, unfold); + + browser->b.nr_entries = hist_browser__nr_entries(browser); /* Go to the start, we may be way after valid entries after a collapse */ ui_browser__reset_index(&browser->b); } @@ -310,9 +377,7 @@ static void ui_browser__warn_lost_events(struct ui_browser *browser) "Or reduce the sampling frequency."); } -static void hist_browser__update_pcnt_entries(struct hist_browser *hb); - -static int hist_browser__run(struct hist_browser *browser, const char *ev_name, +static int hist_browser__run(struct hist_browser *browser, struct hist_browser_timer *hbt) { int key; @@ -320,12 +385,9 @@ static int hist_browser__run(struct hist_browser *browser, const char *ev_name, int delay_secs = hbt ? hbt->refresh : 0; browser->b.entries = &browser->hists->entries; - browser->b.nr_entries = browser->hists->nr_entries; - if (browser->min_pcnt) - browser->b.nr_entries = browser->nr_pcnt_entries; + browser->b.nr_entries = hist_browser__nr_entries(browser); - hist_browser__refresh_dimensions(browser); - hists__browser_title(browser->hists, title, sizeof(title), ev_name); + hists__browser_title(browser->hists, title, sizeof(title)); if (ui_browser__show(&browser->b, title, "Press '?' for help on key bindings") < 0) @@ -339,13 +401,10 @@ static int hist_browser__run(struct hist_browser *browser, const char *ev_name, u64 nr_entries; hbt->timer(hbt->arg); - if (browser->min_pcnt) { - hist_browser__update_pcnt_entries(browser); - nr_entries = browser->nr_pcnt_entries; - } else { - nr_entries = browser->hists->nr_entries; - } + if (hist_browser__has_filter(browser)) + hist_browser__update_nr_entries(browser); + nr_entries = hist_browser__nr_entries(browser); ui_browser__update_nr_entries(&browser->b, nr_entries); if (browser->hists->stats.nr_lost_warned != @@ -355,7 +414,7 @@ static int hist_browser__run(struct hist_browser *browser, const char *ev_name, ui_browser__warn_lost_events(&browser->b); } - hists__browser_title(browser->hists, title, sizeof(title), ev_name); + hists__browser_title(browser->hists, title, sizeof(title)); ui_browser__show_title(&browser->b, title); continue; } @@ -364,10 +423,10 @@ static int hist_browser__run(struct hist_browser *browser, const char *ev_name, struct hist_entry *h = rb_entry(browser->b.top, struct hist_entry, rb_node); ui_helpline__pop(); - ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", + ui_helpline__fpush("%d: nr_ent=(%d,%d), rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", seq++, browser->b.nr_entries, browser->hists->nr_entries, - browser->b.height, + browser->b.rows, browser->b.index, browser->b.top_idx, h->row_offset, h->nr_rows); @@ -381,6 +440,10 @@ static int hist_browser__run(struct hist_browser *browser, const char *ev_name, /* Expand the whole world. */ hist_browser__set_folding(browser, true); break; + case 'H': + browser->show_headers = !browser->show_headers; + hist_browser__update_rows(browser); + break; case K_ENTER: if (hist_browser__toggle_fold(browser)) break; @@ -480,13 +543,13 @@ static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browse } ui_browser__set_color(&browser->b, color); - ui_browser__gotorc(&browser->b, row, 0); + hist_browser__gotorc(browser, row, 0); slsmg_write_nstring(" ", offset + extra_offset); slsmg_printf("%c ", folded_sign); slsmg_write_nstring(str, width); free(alloc_str); - if (++row == browser->b.height) + if (++row == browser->b.rows) goto out; do_next: if (folded_sign == '+') @@ -499,7 +562,7 @@ do_next: new_level, row, row_offset, is_current_entry); } - if (row == browser->b.height) + if (row == browser->b.rows) goto out; node = next; } @@ -539,13 +602,13 @@ static int hist_browser__show_callchain_node(struct hist_browser *browser, s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso); - ui_browser__gotorc(&browser->b, row, 0); + hist_browser__gotorc(browser, row, 0); ui_browser__set_color(&browser->b, color); slsmg_write_nstring(" ", offset); slsmg_printf("%c ", folded_sign); slsmg_write_nstring(s, width - 2); - if (++row == browser->b.height) + if (++row == browser->b.rows) goto out; } @@ -574,7 +637,7 @@ static int hist_browser__show_callchain(struct hist_browser *browser, row += hist_browser__show_callchain_node(browser, node, level, row, row_offset, is_current_entry); - if (row == browser->b.height) + if (row == browser->b.rows) break; } @@ -587,35 +650,6 @@ struct hpp_arg { bool current_entry; }; -static int __hpp__overhead_callback(struct perf_hpp *hpp, bool front) -{ - struct hpp_arg *arg = hpp->ptr; - - if (arg->current_entry && arg->b->navkeypressed) - ui_browser__set_color(arg->b, HE_COLORSET_SELECTED); - else - ui_browser__set_color(arg->b, HE_COLORSET_NORMAL); - - if (front) { - if (!symbol_conf.use_callchain) - return 0; - - slsmg_printf("%c ", arg->folded_sign); - return 2; - } - - return 0; -} - -static int __hpp__color_callback(struct perf_hpp *hpp, bool front __maybe_unused) -{ - struct hpp_arg *arg = hpp->ptr; - - if (!arg->current_entry || !arg->b->navkeypressed) - ui_browser__set_color(arg->b, HE_COLORSET_NORMAL); - return 0; -} - static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...) { struct hpp_arg *arg = hpp->ptr; @@ -636,7 +670,7 @@ static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...) return ret; } -#define __HPP_COLOR_PERCENT_FN(_type, _field, _cb) \ +#define __HPP_COLOR_PERCENT_FN(_type, _field) \ static u64 __hpp_get_##_field(struct hist_entry *he) \ { \ return he->stat._field; \ @@ -647,22 +681,43 @@ hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ struct perf_hpp *hpp, \ struct hist_entry *he) \ { \ - return __hpp__fmt(hpp, he, __hpp_get_##_field, _cb, " %6.2f%%", \ + return __hpp__fmt(hpp, he, __hpp_get_##_field, " %6.2f%%", \ __hpp__slsmg_color_printf, true); \ } -__HPP_COLOR_PERCENT_FN(overhead, period, __hpp__overhead_callback) -__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys, __hpp__color_callback) -__HPP_COLOR_PERCENT_FN(overhead_us, period_us, __hpp__color_callback) -__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys, __hpp__color_callback) -__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us, __hpp__color_callback) +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +static u64 __hpp_get_acc_##_field(struct hist_entry *he) \ +{ \ + return he->stat_acc->_field; \ +} \ + \ +static int \ +hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + if (!symbol_conf.cumulate_callchain) { \ + int ret = scnprintf(hpp->buf, hpp->size, "%8s", "N/A"); \ + slsmg_printf("%s", hpp->buf); \ + \ + return ret; \ + } \ + return __hpp__fmt(hpp, he, __hpp_get_acc_##_field, " %6.2f%%", \ + __hpp__slsmg_color_printf, true); \ +} + +__HPP_COLOR_PERCENT_FN(overhead, period) +__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys) +__HPP_COLOR_PERCENT_FN(overhead_us, period_us) +__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys) +__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us) +__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period) #undef __HPP_COLOR_PERCENT_FN +#undef __HPP_COLOR_ACC_PERCENT_FN void hist_browser__init_hpp(void) { - perf_hpp__init(); - perf_hpp__format[PERF_HPP__OVERHEAD].color = hist_browser__hpp_color_overhead; perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color = @@ -673,6 +728,8 @@ void hist_browser__init_hpp(void) hist_browser__hpp_color_overhead_guest_sys; perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color = hist_browser__hpp_color_overhead_guest_us; + perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color = + hist_browser__hpp_color_overhead_acc; } static int hist_browser__show_entry(struct hist_browser *browser, @@ -700,7 +757,7 @@ static int hist_browser__show_entry(struct hist_browser *browser, if (row_offset == 0) { struct hpp_arg arg = { - .b = &browser->b, + .b = &browser->b, .folded_sign = folded_sign, .current_entry = current_entry, }; @@ -710,14 +767,30 @@ static int hist_browser__show_entry(struct hist_browser *browser, .ptr = &arg, }; - ui_browser__gotorc(&browser->b, row, 0); + hist_browser__gotorc(browser, row, 0); perf_hpp__for_each_format(fmt) { - if (!first) { + if (perf_hpp__should_skip(fmt)) + continue; + + if (current_entry && browser->b.navkeypressed) { + ui_browser__set_color(&browser->b, + HE_COLORSET_SELECTED); + } else { + ui_browser__set_color(&browser->b, + HE_COLORSET_NORMAL); + } + + if (first) { + if (symbol_conf.use_callchain) { + slsmg_printf("%c ", folded_sign); + width -= 2; + } + first = false; + } else { slsmg_printf(" "); width -= 2; } - first = false; if (fmt->color) { width -= fmt->color(fmt, &hpp, entry); @@ -731,14 +804,14 @@ static int hist_browser__show_entry(struct hist_browser *browser, if (!browser->b.navkeypressed) width += 1; - hist_entry__sort_snprintf(entry, s, sizeof(s), browser->hists); - slsmg_write_nstring(s, width); + slsmg_write_nstring("", width); + ++row; ++printed; } else --row_offset; - if (folded_sign == '-' && row != browser->b.height) { + if (folded_sign == '-' && row != browser->b.rows) { printed += hist_browser__show_callchain(browser, &entry->sorted_chain, 1, row, &row_offset, ¤t_entry); @@ -749,6 +822,56 @@ static int hist_browser__show_entry(struct hist_browser *browser, return printed; } +static int advance_hpp_check(struct perf_hpp *hpp, int inc) +{ + advance_hpp(hpp, inc); + return hpp->size <= 0; +} + +static int hists__scnprintf_headers(char *buf, size_t size, struct hists *hists) +{ + struct perf_hpp dummy_hpp = { + .buf = buf, + .size = size, + }; + struct perf_hpp_fmt *fmt; + size_t ret = 0; + + if (symbol_conf.use_callchain) { + ret = scnprintf(buf, size, " "); + if (advance_hpp_check(&dummy_hpp, ret)) + return ret; + } + + perf_hpp__for_each_format(fmt) { + if (perf_hpp__should_skip(fmt)) + continue; + + /* We need to add the length of the columns header. */ + perf_hpp__reset_width(fmt, hists); + + ret = fmt->header(fmt, &dummy_hpp, hists_to_evsel(hists)); + if (advance_hpp_check(&dummy_hpp, ret)) + break; + + ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " "); + if (advance_hpp_check(&dummy_hpp, ret)) + break; + } + + return ret; +} + +static void hist_browser__show_headers(struct hist_browser *browser) +{ + char headers[1024]; + + hists__scnprintf_headers(headers, sizeof(headers), browser->hists); + ui_browser__gotorc(&browser->b, 0, 0); + ui_browser__set_color(&browser->b, HE_COLORSET_ROOT); + slsmg_write_nstring(headers, browser->b.width + 1); +} + static void ui_browser__hists_init_top(struct ui_browser *browser) { if (browser->top == NULL) { @@ -762,43 +885,44 @@ static void ui_browser__hists_init_top(struct ui_browser *browser) static unsigned int hist_browser__refresh(struct ui_browser *browser) { unsigned row = 0; + u16 header_offset = 0; struct rb_node *nd; struct hist_browser *hb = container_of(browser, struct hist_browser, b); + if (hb->show_headers) { + hist_browser__show_headers(hb); + header_offset = 1; + } + ui_browser__hists_init_top(browser); for (nd = browser->top; nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hb->hists->stats.total_period; + float percent; if (h->filtered) continue; + percent = hist_entry__get_percent_limit(h); if (percent < hb->min_pcnt) continue; row += hist_browser__show_entry(hb, h, row); - if (row == browser->height) + if (row == browser->rows) break; } - return row; + return row + header_offset; } static struct rb_node *hists__filter_entries(struct rb_node *nd, - struct hists *hists, float min_pcnt) { while (nd != NULL) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hists->stats.total_period; - - if (percent < min_pcnt) - return NULL; + float percent = hist_entry__get_percent_limit(h); - if (!h->filtered) + if (!h->filtered && percent >= min_pcnt) return nd; nd = rb_next(nd); @@ -808,13 +932,11 @@ static struct rb_node *hists__filter_entries(struct rb_node *nd, } static struct rb_node *hists__filter_prev_entries(struct rb_node *nd, - struct hists *hists, float min_pcnt) { while (nd != NULL) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hists->stats.total_period; + float percent = hist_entry__get_percent_limit(h); if (!h->filtered && percent >= min_pcnt) return nd; @@ -843,14 +965,14 @@ static void ui_browser__hists_seek(struct ui_browser *browser, switch (whence) { case SEEK_SET: nd = hists__filter_entries(rb_first(browser->entries), - hb->hists, hb->min_pcnt); + hb->min_pcnt); break; case SEEK_CUR: nd = browser->top; goto do_offset; case SEEK_END: nd = hists__filter_prev_entries(rb_last(browser->entries), - hb->hists, hb->min_pcnt); + hb->min_pcnt); first = false; break; default: @@ -893,8 +1015,7 @@ do_offset: break; } } - nd = hists__filter_entries(rb_next(nd), hb->hists, - hb->min_pcnt); + nd = hists__filter_entries(rb_next(nd), hb->min_pcnt); if (nd == NULL) break; --offset; @@ -927,7 +1048,7 @@ do_offset: } } - nd = hists__filter_prev_entries(rb_prev(nd), hb->hists, + nd = hists__filter_prev_entries(rb_prev(nd), hb->min_pcnt); if (nd == NULL) break; @@ -1066,27 +1187,35 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser, struct hist_entry *he, FILE *fp) { char s[8192]; - double percent; int printed = 0; char folded_sign = ' '; + struct perf_hpp hpp = { + .buf = s, + .size = sizeof(s), + }; + struct perf_hpp_fmt *fmt; + bool first = true; + int ret; if (symbol_conf.use_callchain) folded_sign = hist_entry__folded(he); - hist_entry__sort_snprintf(he, s, sizeof(s), browser->hists); - percent = (he->stat.period * 100.0) / browser->hists->stats.total_period; - if (symbol_conf.use_callchain) printed += fprintf(fp, "%c ", folded_sign); - printed += fprintf(fp, " %5.2f%%", percent); - - if (symbol_conf.show_nr_samples) - printed += fprintf(fp, " %11u", he->stat.nr_events); + perf_hpp__for_each_format(fmt) { + if (perf_hpp__should_skip(fmt)) + continue; - if (symbol_conf.show_total_period) - printed += fprintf(fp, " %12" PRIu64, he->stat.period); + if (!first) { + ret = scnprintf(hpp.buf, hpp.size, " "); + advance_hpp(&hpp, ret); + } else + first = false; + ret = fmt->entry(fmt, &hpp, he); + advance_hpp(&hpp, ret); + } printed += fprintf(fp, "%s\n", rtrim(s)); if (folded_sign == '-') @@ -1098,7 +1227,6 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser, static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp) { struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries), - browser->hists, browser->min_pcnt); int printed = 0; @@ -1106,8 +1234,7 @@ static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp) struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); printed += hist_browser__fprintf_entry(browser, h, fp); - nd = hists__filter_entries(rb_next(nd), browser->hists, - browser->min_pcnt); + nd = hists__filter_entries(rb_next(nd), browser->min_pcnt); } return printed; @@ -1154,8 +1281,10 @@ static struct hist_browser *hist_browser__new(struct hists *hists) if (browser) { browser->hists = hists; browser->b.refresh = hist_browser__refresh; + browser->b.refresh_dimensions = hist_browser__refresh_dimensions; browser->b.seek = ui_browser__hists_seek; browser->b.use_navkeypressed = true; + browser->show_headers = symbol_conf.show_hist_headers; } return browser; @@ -1176,8 +1305,7 @@ static struct thread *hist_browser__selected_thread(struct hist_browser *browser return browser->he_selection->thread; } -static int hists__browser_title(struct hists *hists, char *bf, size_t size, - const char *ev_name) +static int hists__browser_title(struct hists *hists, char *bf, size_t size) { char unit; int printed; @@ -1186,9 +1314,15 @@ static int hists__browser_title(struct hists *hists, char *bf, size_t size, unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE]; u64 nr_events = hists->stats.total_period; struct perf_evsel *evsel = hists_to_evsel(hists); + const char *ev_name = perf_evsel__name(evsel); char buf[512]; size_t buflen = sizeof(buf); + if (symbol_conf.filter_relative) { + nr_samples = hists->stats.nr_non_filtered_samples; + nr_events = hists->stats.total_non_filtered_period; + } + if (perf_evsel__is_group_event(evsel)) { struct perf_evsel *pos; @@ -1196,8 +1330,13 @@ static int hists__browser_title(struct hists *hists, char *bf, size_t size, ev_name = buf; for_each_group_member(pos, evsel) { - nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; - nr_events += pos->hists.stats.total_period; + if (symbol_conf.filter_relative) { + nr_samples += pos->hists.stats.nr_non_filtered_samples; + nr_events += pos->hists.stats.total_non_filtered_period; + } else { + nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; + nr_events += pos->hists.stats.total_period; + } } } @@ -1324,22 +1463,26 @@ close_file_and_continue: return ret; } -static void hist_browser__update_pcnt_entries(struct hist_browser *hb) +static void hist_browser__update_nr_entries(struct hist_browser *hb) { u64 nr_entries = 0; struct rb_node *nd = rb_first(&hb->hists->entries); - while (nd) { + if (hb->min_pcnt == 0) { + hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries; + return; + } + + while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) { nr_entries++; - nd = hists__filter_entries(rb_next(nd), hb->hists, - hb->min_pcnt); + nd = rb_next(nd); } - hb->nr_pcnt_entries = nr_entries; + hb->nr_non_filtered_entries = nr_entries; } static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, - const char *helpline, const char *ev_name, + const char *helpline, bool left_exits, struct hist_browser_timer *hbt, float min_pcnt, @@ -1370,6 +1513,8 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, "C Collapse all callchains\n" \ "d Zoom into current DSO\n" \ "E Expand all callchains\n" \ + "F Toggle percentage of filtered entries\n" \ + "H Display column headers\n" \ /* help messages are sorted by lexical order of the hotkey */ const char report_help[] = HIST_BROWSER_HELP_COMMON @@ -1391,7 +1536,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, if (min_pcnt) { browser->min_pcnt = min_pcnt; - hist_browser__update_pcnt_entries(browser); + hist_browser__update_nr_entries(browser); } fstack = pstack__new(2); @@ -1413,7 +1558,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, nr_options = 0; - key = hist_browser__run(browser, ev_name, hbt); + key = hist_browser__run(browser, hbt); if (browser->he_selection != NULL) { thread = hist_browser__selected_thread(browser); @@ -1475,6 +1620,9 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, if (env->arch) tui__header_window(env); continue; + case 'F': + symbol_conf.filter_relative ^= 1; + continue; case K_F1: case 'h': case '?': @@ -1539,13 +1687,18 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, 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++; + !browser->selection->map->dso->annotate_warned) { + struct annotation *notes; + + notes = symbol__annotation(browser->selection->sym); + + if (notes->src && + asprintf(&options[nr_options], "Annotate %s", + browser->selection->sym->name) > 0) + annotate = nr_options++; + } } if (thread != NULL && @@ -1602,6 +1755,7 @@ retry_popup_menu: if (choice == annotate || choice == annotate_t || choice == annotate_f) { struct hist_entry *he; + struct annotation *notes; int err; do_annotate: if (!objdump_path && perf_session_env__lookup_objdump(env)) @@ -1625,6 +1779,10 @@ do_annotate: he->ms.map = he->branch_info->to.map; } + notes = symbol__annotation(he->ms.sym); + if (!notes->src) + continue; + /* * Don't let this be freed, say, by hists__decay_entry. */ @@ -1652,14 +1810,14 @@ zoom_dso: zoom_out_dso: ui_helpline__pop(); browser->hists->dso_filter = NULL; - sort_dso.elide = false; + perf_hpp__set_elide(HISTC_DSO, false); } else { if (dso == NULL) continue; ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", dso->kernel ? "the Kernel" : dso->short_name); browser->hists->dso_filter = dso; - sort_dso.elide = true; + perf_hpp__set_elide(HISTC_DSO, true); pstack__push(fstack, &browser->hists->dso_filter); } hists__filter_by_dso(hists); @@ -1671,13 +1829,13 @@ zoom_thread: zoom_out_thread: ui_helpline__pop(); browser->hists->thread_filter = NULL; - sort_thread.elide = false; + perf_hpp__set_elide(HISTC_THREAD, false); } else { ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", thread->comm_set ? thread__comm_str(thread) : "", thread->tid); browser->hists->thread_filter = thread; - sort_thread.elide = true; + perf_hpp__set_elide(HISTC_THREAD, false); pstack__push(fstack, &browser->hists->thread_filter); } hists__filter_by_thread(hists); @@ -1778,7 +1936,7 @@ static int perf_evsel_menu__run(struct perf_evsel_menu *menu, { struct perf_evlist *evlist = menu->b.priv; struct perf_evsel *pos; - const char *ev_name, *title = "Available samples"; + const char *title = "Available samples"; int delay_secs = hbt ? hbt->refresh : 0; int key; @@ -1811,9 +1969,8 @@ browse_hists: */ if (hbt) hbt->timer(hbt->arg); - ev_name = perf_evsel__name(pos); key = perf_evsel__hists_browse(pos, nr_events, help, - ev_name, true, hbt, + true, hbt, menu->min_pcnt, menu->env); ui_browser__show_title(&menu->b, title); @@ -1917,10 +2074,9 @@ int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help, single_entry: if (nr_entries == 1) { struct perf_evsel *first = perf_evlist__first(evlist); - const char *ev_name = perf_evsel__name(first); return perf_evsel__hists_browse(first, nr_entries, help, - ev_name, false, hbt, min_pcnt, + false, hbt, min_pcnt, env); } diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c index e395ef9b0ae0..6ca60e482cdc 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -43,23 +43,36 @@ static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, struct perf_hpp *hpp, \ struct hist_entry *he) \ { \ - return __hpp__fmt(hpp, he, he_get_##_field, NULL, " %6.2f%%", \ + return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \ __percent_color_snprintf, true); \ } +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +static u64 he_get_acc_##_field(struct hist_entry *he) \ +{ \ + return he->stat_acc->_field; \ +} \ + \ +static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, " %6.2f%%", \ + __percent_color_snprintf, true); \ +} + __HPP_COLOR_PERCENT_FN(overhead, period) __HPP_COLOR_PERCENT_FN(overhead_sys, period_sys) __HPP_COLOR_PERCENT_FN(overhead_us, period_us) __HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys) __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us) +__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period) #undef __HPP_COLOR_PERCENT_FN void perf_gtk__init_hpp(void) { - perf_hpp__init(); - perf_hpp__format[PERF_HPP__OVERHEAD].color = perf_gtk__hpp_color_overhead; perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color = @@ -70,6 +83,8 @@ void perf_gtk__init_hpp(void) perf_gtk__hpp_color_overhead_guest_sys; perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color = perf_gtk__hpp_color_overhead_guest_us; + perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color = + perf_gtk__hpp_color_overhead_acc; } static void callchain_list__sym_name(struct callchain_list *cl, @@ -153,7 +168,6 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, struct perf_hpp_fmt *fmt; GType col_types[MAX_COLUMNS]; GtkCellRenderer *renderer; - struct sort_entry *se; GtkTreeStore *store; struct rb_node *nd; GtkWidget *view; @@ -172,16 +186,6 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, perf_hpp__for_each_format(fmt) col_types[nr_cols++] = G_TYPE_STRING; - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (se->elide) - continue; - - if (se == &sort_sym) - sym_col = nr_cols; - - col_types[nr_cols++] = G_TYPE_STRING; - } - store = gtk_tree_store_newv(nr_cols, col_types); view = gtk_tree_view_new(); @@ -191,6 +195,16 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, col_idx = 0; perf_hpp__for_each_format(fmt) { + if (perf_hpp__should_skip(fmt)) + continue; + + /* + * XXX no way to determine where symcol column is.. + * Just use last column for now. + */ + if (perf_hpp__is_sort_entry(fmt)) + sym_col = col_idx; + fmt->header(fmt, &hpp, hists_to_evsel(hists)); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), @@ -199,16 +213,6 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, 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); - } - for (col_idx = 0; col_idx < nr_cols; col_idx++) { GtkTreeViewColumn *column; @@ -228,12 +232,13 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, 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; - float percent = h->stat.period * 100.0 / - hists->stats.total_period; + u64 total = hists__total_period(h->hists); + float percent; if (h->filtered) continue; + percent = hist_entry__get_percent_limit(h); if (percent < min_pcnt) continue; @@ -242,6 +247,9 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, col_idx = 0; perf_hpp__for_each_format(fmt) { + if (perf_hpp__should_skip(fmt)) + continue; + if (fmt->color) fmt->color(fmt, &hpp, h); else @@ -250,23 +258,10 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, gtk_tree_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_tree_store_set(store, &iter, col_idx++, s, -1); - } - if (symbol_conf.use_callchain && sort__has_sym) { - u64 total; - if (callchain_param.mode == CHAIN_GRAPH_REL) - total = h->stat.period; - else - total = hists->stats.total_period; + total = symbol_conf.cumulate_callchain ? + h->stat_acc->period : h->stat.period; perf_gtk__add_callchain(&h->sorted_chain, store, &iter, sym_col, total); diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c index 0f403b83e9d1..498adb23c02e 100644 --- a/tools/perf/ui/hist.c +++ b/tools/perf/ui/hist.c @@ -16,30 +16,25 @@ }) int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, - hpp_field_fn get_field, hpp_callback_fn callback, - const char *fmt, hpp_snprint_fn print_fn, bool fmt_percent) + hpp_field_fn get_field, const char *fmt, + hpp_snprint_fn print_fn, bool fmt_percent) { - int ret = 0; + int ret; struct hists *hists = he->hists; struct perf_evsel *evsel = hists_to_evsel(hists); char *buf = hpp->buf; size_t size = hpp->size; - if (callback) { - ret = callback(hpp, true); - advance_hpp(hpp, ret); - } - if (fmt_percent) { double percent = 0.0; + u64 total = hists__total_period(hists); - if (hists->stats.total_period) - percent = 100.0 * get_field(he) / - hists->stats.total_period; + if (total) + percent = 100.0 * get_field(he) / total; - ret += hpp__call_print_fn(hpp, print_fn, fmt, percent); + ret = hpp__call_print_fn(hpp, print_fn, fmt, percent); } else - ret += hpp__call_print_fn(hpp, print_fn, fmt, get_field(he)); + ret = hpp__call_print_fn(hpp, print_fn, fmt, get_field(he)); if (perf_evsel__is_group_event(evsel)) { int prev_idx, idx_delta; @@ -50,7 +45,7 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, list_for_each_entry(pair, &he->pairs.head, pairs.node) { u64 period = get_field(pair); - u64 total = pair->hists->stats.total_period; + u64 total = hists__total_period(pair->hists); if (!total) continue; @@ -99,13 +94,6 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, } } - if (callback) { - int __ret = callback(hpp, false); - - advance_hpp(hpp, __ret); - ret += __ret; - } - /* * Restore original buf and size as it's where caller expects * the result will be saved. @@ -116,6 +104,92 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, return ret; } +int __hpp__fmt_acc(struct perf_hpp *hpp, struct hist_entry *he, + hpp_field_fn get_field, const char *fmt, + hpp_snprint_fn print_fn, bool fmt_percent) +{ + if (!symbol_conf.cumulate_callchain) { + return snprintf(hpp->buf, hpp->size, "%*s", + fmt_percent ? 8 : 12, "N/A"); + } + + return __hpp__fmt(hpp, he, get_field, fmt, print_fn, fmt_percent); +} + +static int field_cmp(u64 field_a, u64 field_b) +{ + if (field_a > field_b) + return 1; + if (field_a < field_b) + return -1; + return 0; +} + +static int __hpp__sort(struct hist_entry *a, struct hist_entry *b, + hpp_field_fn get_field) +{ + s64 ret; + int i, nr_members; + struct perf_evsel *evsel; + struct hist_entry *pair; + u64 *fields_a, *fields_b; + + ret = field_cmp(get_field(a), get_field(b)); + if (ret || !symbol_conf.event_group) + return ret; + + evsel = hists_to_evsel(a->hists); + if (!perf_evsel__is_group_event(evsel)) + return ret; + + nr_members = evsel->nr_members; + fields_a = calloc(sizeof(*fields_a), nr_members); + fields_b = calloc(sizeof(*fields_b), nr_members); + + if (!fields_a || !fields_b) + goto out; + + list_for_each_entry(pair, &a->pairs.head, pairs.node) { + evsel = hists_to_evsel(pair->hists); + fields_a[perf_evsel__group_idx(evsel)] = get_field(pair); + } + + list_for_each_entry(pair, &b->pairs.head, pairs.node) { + evsel = hists_to_evsel(pair->hists); + fields_b[perf_evsel__group_idx(evsel)] = get_field(pair); + } + + for (i = 1; i < nr_members; i++) { + ret = field_cmp(fields_a[i], fields_b[i]); + if (ret) + break; + } + +out: + free(fields_a); + free(fields_b); + + return ret; +} + +static int __hpp__sort_acc(struct hist_entry *a, struct hist_entry *b, + hpp_field_fn get_field) +{ + s64 ret = 0; + + if (symbol_conf.cumulate_callchain) { + /* + * Put caller above callee when they have equal period. + */ + ret = field_cmp(get_field(a), get_field(b)); + if (ret) + return ret; + + ret = b->callchain->max_depth - a->callchain->max_depth; + } + return ret; +} + #define __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ static int hpp__header_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ struct perf_hpp *hpp, \ @@ -179,7 +253,7 @@ static u64 he_get_##_field(struct hist_entry *he) \ static int hpp__color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ struct perf_hpp *hpp, struct hist_entry *he) \ { \ - return __hpp__fmt(hpp, he, he_get_##_field, NULL, " %6.2f%%", \ + return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \ hpp_color_scnprintf, true); \ } @@ -188,10 +262,44 @@ static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \ struct perf_hpp *hpp, struct hist_entry *he) \ { \ const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%"; \ - return __hpp__fmt(hpp, he, he_get_##_field, NULL, fmt, \ + return __hpp__fmt(hpp, he, he_get_##_field, fmt, \ hpp_entry_scnprintf, true); \ } +#define __HPP_SORT_FN(_type, _field) \ +static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \ +{ \ + return __hpp__sort(a, b, he_get_##_field); \ +} + +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +static u64 he_get_acc_##_field(struct hist_entry *he) \ +{ \ + return he->stat_acc->_field; \ +} \ + \ +static int hpp__color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, " %6.2f%%", \ + hpp_color_scnprintf, true); \ +} + +#define __HPP_ENTRY_ACC_PERCENT_FN(_type, _field) \ +static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%"; \ + return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, fmt, \ + hpp_entry_scnprintf, true); \ +} + +#define __HPP_SORT_ACC_FN(_type, _field) \ +static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \ +{ \ + return __hpp__sort_acc(a, b, he_get_acc_##_field); \ +} + #define __HPP_ENTRY_RAW_FN(_type, _field) \ static u64 he_get_raw_##_field(struct hist_entry *he) \ { \ @@ -202,44 +310,85 @@ static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \ struct perf_hpp *hpp, struct hist_entry *he) \ { \ const char *fmt = symbol_conf.field_sep ? " %"PRIu64 : " %11"PRIu64; \ - return __hpp__fmt(hpp, he, he_get_raw_##_field, NULL, fmt, \ + return __hpp__fmt(hpp, he, he_get_raw_##_field, fmt, \ hpp_entry_scnprintf, false); \ } +#define __HPP_SORT_RAW_FN(_type, _field) \ +static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \ +{ \ + return __hpp__sort(a, b, he_get_raw_##_field); \ +} + + #define HPP_PERCENT_FNS(_type, _str, _field, _min_width, _unit_width) \ __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ __HPP_WIDTH_FN(_type, _min_width, _unit_width) \ __HPP_COLOR_PERCENT_FN(_type, _field) \ -__HPP_ENTRY_PERCENT_FN(_type, _field) +__HPP_ENTRY_PERCENT_FN(_type, _field) \ +__HPP_SORT_FN(_type, _field) + +#define HPP_PERCENT_ACC_FNS(_type, _str, _field, _min_width, _unit_width)\ +__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ +__HPP_WIDTH_FN(_type, _min_width, _unit_width) \ +__HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +__HPP_ENTRY_ACC_PERCENT_FN(_type, _field) \ +__HPP_SORT_ACC_FN(_type, _field) #define HPP_RAW_FNS(_type, _str, _field, _min_width, _unit_width) \ __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ __HPP_WIDTH_FN(_type, _min_width, _unit_width) \ -__HPP_ENTRY_RAW_FN(_type, _field) +__HPP_ENTRY_RAW_FN(_type, _field) \ +__HPP_SORT_RAW_FN(_type, _field) +__HPP_HEADER_FN(overhead_self, "Self", 8, 8) HPP_PERCENT_FNS(overhead, "Overhead", period, 8, 8) HPP_PERCENT_FNS(overhead_sys, "sys", period_sys, 8, 8) HPP_PERCENT_FNS(overhead_us, "usr", period_us, 8, 8) HPP_PERCENT_FNS(overhead_guest_sys, "guest sys", period_guest_sys, 9, 8) HPP_PERCENT_FNS(overhead_guest_us, "guest usr", period_guest_us, 9, 8) +HPP_PERCENT_ACC_FNS(overhead_acc, "Children", period, 8, 8) HPP_RAW_FNS(samples, "Samples", nr_events, 12, 12) HPP_RAW_FNS(period, "Period", period, 12, 12) +static int64_t hpp__nop_cmp(struct hist_entry *a __maybe_unused, + struct hist_entry *b __maybe_unused) +{ + return 0; +} + #define HPP__COLOR_PRINT_FNS(_name) \ { \ .header = hpp__header_ ## _name, \ .width = hpp__width_ ## _name, \ .color = hpp__color_ ## _name, \ - .entry = hpp__entry_ ## _name \ + .entry = hpp__entry_ ## _name, \ + .cmp = hpp__nop_cmp, \ + .collapse = hpp__nop_cmp, \ + .sort = hpp__sort_ ## _name, \ + } + +#define HPP__COLOR_ACC_PRINT_FNS(_name) \ + { \ + .header = hpp__header_ ## _name, \ + .width = hpp__width_ ## _name, \ + .color = hpp__color_ ## _name, \ + .entry = hpp__entry_ ## _name, \ + .cmp = hpp__nop_cmp, \ + .collapse = hpp__nop_cmp, \ + .sort = hpp__sort_ ## _name, \ } #define HPP__PRINT_FNS(_name) \ { \ .header = hpp__header_ ## _name, \ .width = hpp__width_ ## _name, \ - .entry = hpp__entry_ ## _name \ + .entry = hpp__entry_ ## _name, \ + .cmp = hpp__nop_cmp, \ + .collapse = hpp__nop_cmp, \ + .sort = hpp__sort_ ## _name, \ } struct perf_hpp_fmt perf_hpp__format[] = { @@ -248,28 +397,63 @@ struct perf_hpp_fmt perf_hpp__format[] = { HPP__COLOR_PRINT_FNS(overhead_us), HPP__COLOR_PRINT_FNS(overhead_guest_sys), HPP__COLOR_PRINT_FNS(overhead_guest_us), + HPP__COLOR_ACC_PRINT_FNS(overhead_acc), HPP__PRINT_FNS(samples), HPP__PRINT_FNS(period) }; LIST_HEAD(perf_hpp__list); +LIST_HEAD(perf_hpp__sort_list); #undef HPP__COLOR_PRINT_FNS +#undef HPP__COLOR_ACC_PRINT_FNS #undef HPP__PRINT_FNS #undef HPP_PERCENT_FNS +#undef HPP_PERCENT_ACC_FNS #undef HPP_RAW_FNS #undef __HPP_HEADER_FN #undef __HPP_WIDTH_FN #undef __HPP_COLOR_PERCENT_FN #undef __HPP_ENTRY_PERCENT_FN +#undef __HPP_COLOR_ACC_PERCENT_FN +#undef __HPP_ENTRY_ACC_PERCENT_FN #undef __HPP_ENTRY_RAW_FN +#undef __HPP_SORT_FN +#undef __HPP_SORT_ACC_FN +#undef __HPP_SORT_RAW_FN void perf_hpp__init(void) { + struct list_head *list; + int i; + + for (i = 0; i < PERF_HPP__MAX_INDEX; i++) { + struct perf_hpp_fmt *fmt = &perf_hpp__format[i]; + + INIT_LIST_HEAD(&fmt->list); + + /* sort_list may be linked by setup_sorting() */ + if (fmt->sort_list.next == NULL) + INIT_LIST_HEAD(&fmt->sort_list); + } + + /* + * If user specified field order, no need to setup default fields. + */ + if (field_order) + return; + + if (symbol_conf.cumulate_callchain) { + perf_hpp__column_enable(PERF_HPP__OVERHEAD_ACC); + + perf_hpp__format[PERF_HPP__OVERHEAD].header = + hpp__header_overhead_self; + } + perf_hpp__column_enable(PERF_HPP__OVERHEAD); if (symbol_conf.show_cpu_utilization) { @@ -287,6 +471,17 @@ void perf_hpp__init(void) if (symbol_conf.show_total_period) perf_hpp__column_enable(PERF_HPP__PERIOD); + + /* prepend overhead field for backward compatiblity. */ + list = &perf_hpp__format[PERF_HPP__OVERHEAD].sort_list; + if (list_empty(list)) + list_add(list, &perf_hpp__sort_list); + + if (symbol_conf.cumulate_callchain) { + list = &perf_hpp__format[PERF_HPP__OVERHEAD_ACC].sort_list; + if (list_empty(list)) + list_add(list, &perf_hpp__sort_list); + } } void perf_hpp__column_register(struct perf_hpp_fmt *format) @@ -294,29 +489,110 @@ void perf_hpp__column_register(struct perf_hpp_fmt *format) list_add_tail(&format->list, &perf_hpp__list); } +void perf_hpp__column_unregister(struct perf_hpp_fmt *format) +{ + list_del(&format->list); +} + +void perf_hpp__register_sort_field(struct perf_hpp_fmt *format) +{ + list_add_tail(&format->sort_list, &perf_hpp__sort_list); +} + void perf_hpp__column_enable(unsigned col) { BUG_ON(col >= PERF_HPP__MAX_INDEX); perf_hpp__column_register(&perf_hpp__format[col]); } -int hist_entry__sort_snprintf(struct hist_entry *he, char *s, size_t size, - struct hists *hists) +void perf_hpp__column_disable(unsigned col) { - const char *sep = symbol_conf.field_sep; - struct sort_entry *se; - int ret = 0; + BUG_ON(col >= PERF_HPP__MAX_INDEX); + perf_hpp__column_unregister(&perf_hpp__format[col]); +} + +void perf_hpp__cancel_cumulate(void) +{ + if (field_order) + return; - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (se->elide) + perf_hpp__column_disable(PERF_HPP__OVERHEAD_ACC); + perf_hpp__format[PERF_HPP__OVERHEAD].header = hpp__header_overhead; +} + +void perf_hpp__setup_output_field(void) +{ + struct perf_hpp_fmt *fmt; + + /* append sort keys to output field */ + perf_hpp__for_each_sort_list(fmt) { + if (!list_empty(&fmt->list)) continue; - 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)); + /* + * sort entry fields are dynamically created, + * so they can share a same sort key even though + * the list is empty. + */ + if (perf_hpp__is_sort_entry(fmt)) { + struct perf_hpp_fmt *pos; + + perf_hpp__for_each_format(pos) { + if (perf_hpp__same_sort_entry(pos, fmt)) + goto next; + } + } + + perf_hpp__column_register(fmt); +next: + continue; } +} - return ret; +void perf_hpp__append_sort_keys(void) +{ + struct perf_hpp_fmt *fmt; + + /* append output fields to sort keys */ + perf_hpp__for_each_format(fmt) { + if (!list_empty(&fmt->sort_list)) + continue; + + /* + * sort entry fields are dynamically created, + * so they can share a same sort key even though + * the list is empty. + */ + if (perf_hpp__is_sort_entry(fmt)) { + struct perf_hpp_fmt *pos; + + perf_hpp__for_each_sort_list(pos) { + if (perf_hpp__same_sort_entry(pos, fmt)) + goto next; + } + } + + perf_hpp__register_sort_field(fmt); +next: + continue; + } +} + +void perf_hpp__reset_output_field(void) +{ + struct perf_hpp_fmt *fmt, *tmp; + + /* reset output fields */ + perf_hpp__for_each_format_safe(fmt, tmp) { + list_del_init(&fmt->list); + list_del_init(&fmt->sort_list); + } + + /* reset sort keys */ + perf_hpp__for_each_sort_list_safe(fmt, tmp) { + list_del_init(&fmt->list); + list_del_init(&fmt->sort_list); + } } /* @@ -325,22 +601,23 @@ int hist_entry__sort_snprintf(struct hist_entry *he, char *s, size_t size, unsigned int hists__sort_list_width(struct hists *hists) { struct perf_hpp_fmt *fmt; - struct sort_entry *se; - int i = 0, ret = 0; + int ret = 0; + bool first = true; struct perf_hpp dummy_hpp; perf_hpp__for_each_format(fmt) { - if (i) + if (perf_hpp__should_skip(fmt)) + continue; + + if (first) + first = false; + else ret += 2; ret += fmt->width(fmt, &dummy_hpp, hists_to_evsel(hists)); } - list_for_each_entry(se, &hist_entry__sort_list, list) - if (!se->elide) - ret += 2 + hists__col_len(hists, se->se_width_idx); - - if (verbose) /* Addr + origin */ + if (verbose && sort__has_sym) /* Addr + origin */ ret += 3 + BITS_PER_LONG / 4; return ret; diff --git a/tools/perf/ui/progress.h b/tools/perf/ui/progress.h index 29ec8efffefb..f34f89eb607c 100644 --- a/tools/perf/ui/progress.h +++ b/tools/perf/ui/progress.h @@ -1,7 +1,7 @@ #ifndef _PERF_UI_PROGRESS_H_ #define _PERF_UI_PROGRESS_H_ 1 -#include <../types.h> +#include <linux/types.h> void ui_progress__finish(void); diff --git a/tools/perf/ui/setup.c b/tools/perf/ui/setup.c index 5df5140a9f29..ba51fa8a1176 100644 --- a/tools/perf/ui/setup.c +++ b/tools/perf/ui/setup.c @@ -86,8 +86,6 @@ void setup_browser(bool fallback_to_pager) use_browser = 0; if (fallback_to_pager) setup_pager(); - - perf_hpp__init(); break; } } diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index d59893edf031..40af0acb4fe9 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -183,7 +183,8 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, * the symbol. No need to print it otherwise it appears as * displayed twice. */ - if (!i++ && sort__first_dimension == SORT_SYM) + if (!i++ && field_order == NULL && + sort_order && !prefixcmp(sort_order, "sym")) continue; if (!printed) { ret += callchain__fprintf_left_margin(fp, left_margin); @@ -270,7 +271,9 @@ static size_t hist_entry_callchain__fprintf(struct hist_entry *he, { switch (callchain_param.mode) { case CHAIN_GRAPH_REL: - return callchain__fprintf_graph(fp, &he->sorted_chain, he->stat.period, + return callchain__fprintf_graph(fp, &he->sorted_chain, + symbol_conf.cumulate_callchain ? + he->stat_acc->period : he->stat.period, left_margin); break; case CHAIN_GRAPH_ABS: @@ -296,18 +299,24 @@ static size_t hist_entry__callchain_fprintf(struct hist_entry *he, int left_margin = 0; u64 total_period = hists->stats.total_period; - if (sort__first_dimension == SORT_COMM) { - struct sort_entry *se = list_first_entry(&hist_entry__sort_list, - typeof(*se), list); - left_margin = hists__col_len(hists, se->se_width_idx); - left_margin -= thread__comm_len(he->thread); - } + if (field_order == NULL && (sort_order == NULL || + !prefixcmp(sort_order, "comm"))) { + struct perf_hpp_fmt *fmt; + + perf_hpp__for_each_format(fmt) { + if (!perf_hpp__is_sort_entry(fmt)) + continue; + /* must be 'comm' sort entry */ + left_margin = fmt->width(fmt, NULL, hists_to_evsel(hists)); + left_margin -= thread__comm_len(he->thread); + break; + } + } return hist_entry_callchain__fprintf(he, total_period, left_margin, fp); } -static int hist_entry__period_snprintf(struct perf_hpp *hpp, - struct hist_entry *he) +static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp) { const char *sep = symbol_conf.field_sep; struct perf_hpp_fmt *fmt; @@ -319,6 +328,9 @@ static int hist_entry__period_snprintf(struct perf_hpp *hpp, return 0; perf_hpp__for_each_format(fmt) { + if (perf_hpp__should_skip(fmt)) + continue; + /* * If there's no field_sep, we still need * to display initial ' '. @@ -353,8 +365,7 @@ static int hist_entry__fprintf(struct hist_entry *he, size_t size, if (size == 0 || size > bfsz) size = hpp.size = bfsz; - ret = hist_entry__period_snprintf(&hpp, he); - hist_entry__sort_snprintf(he, bf + ret, size - ret, hists); + hist_entry__snprintf(he, &hpp); ret = fprintf(fp, "%s\n", bf); @@ -368,12 +379,10 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, int max_cols, float min_pcnt, FILE *fp) { struct perf_hpp_fmt *fmt; - struct sort_entry *se; struct rb_node *nd; size_t ret = 0; unsigned int width; const char *sep = symbol_conf.field_sep; - const char *col_width = symbol_conf.col_width_list_str; int nr_rows = 0; char bf[96]; struct perf_hpp dummy_hpp = { @@ -386,12 +395,19 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, init_rem_hits(); + + perf_hpp__for_each_format(fmt) + perf_hpp__reset_width(fmt, hists); + if (!show_header) goto print_entries; fprintf(fp, "# "); perf_hpp__for_each_format(fmt) { + if (perf_hpp__should_skip(fmt)) + continue; + if (!first) fprintf(fp, "%s", sep ?: " "); else @@ -401,28 +417,6 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, fprintf(fp, "%s", bf); } - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (se->elide) - continue; - if (sep) { - fprintf(fp, "%c%s", *sep, se->se_header); - continue; - } - width = strlen(se->se_header); - if (symbol_conf.col_width_list_str) { - if (col_width) { - hists__set_col_len(hists, se->se_width_idx, - atoi(col_width)); - col_width = strchr(col_width, ','); - if (col_width) - ++col_width; - } - } - if (!hists__new_col_len(hists, se->se_width_idx, width)) - width = hists__col_len(hists, se->se_width_idx); - fprintf(fp, " %*s", width, se->se_header); - } - fprintf(fp, "\n"); if (max_rows && ++nr_rows >= max_rows) goto out; @@ -437,6 +431,9 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, perf_hpp__for_each_format(fmt) { unsigned int i; + if (perf_hpp__should_skip(fmt)) + continue; + if (!first) fprintf(fp, "%s", sep ?: " "); else @@ -447,20 +444,6 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, fprintf(fp, "."); } - list_for_each_entry(se, &hist_entry__sort_list, list) { - unsigned int i; - - if (se->elide) - continue; - - fprintf(fp, " "); - width = hists__col_len(hists, se->se_width_idx); - if (width == 0) - width = strlen(se->se_header); - for (i = 0; i < width; i++) - fprintf(fp, "."); - } - fprintf(fp, "\n"); if (max_rows && ++nr_rows >= max_rows) goto out; @@ -480,12 +463,12 @@ print_entries: for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hists->stats.total_period; + float percent; if (h->filtered) continue; + percent = hist_entry__get_percent_limit(h); if (percent < min_pcnt) continue; @@ -495,8 +478,8 @@ print_entries: break; if (h->ms.map == NULL && verbose > 1) { - __map_groups__fprintf_maps(&h->thread->mg, - MAP__FUNCTION, verbose, fp); + __map_groups__fprintf_maps(h->thread->mg, + MAP__FUNCTION, fp); fprintf(fp, "%.10s end\n", graph_dotted_line); } } diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h index 56ad4f5287de..112d6e268150 100644 --- a/tools/perf/util/annotate.h +++ b/tools/perf/util/annotate.h @@ -3,7 +3,7 @@ #include <stdbool.h> #include <stdint.h> -#include "types.h" +#include <linux/types.h> #include "symbol.h" #include "hist.h" #include "sort.h" diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c index 6baabe63182b..a904a4cfe7d3 100644 --- a/tools/perf/util/build-id.c +++ b/tools/perf/util/build-id.c @@ -25,7 +25,7 @@ int build_id__mark_dso_hit(struct perf_tool *tool __maybe_unused, struct addr_location al; u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; struct thread *thread = machine__findnew_thread(machine, sample->pid, - sample->pid); + sample->tid); if (thread == NULL) { pr_err("problem processing %d event, skipping it.\n", diff --git a/tools/perf/util/build-id.h b/tools/perf/util/build-id.h index 845ef865eced..ae392561470b 100644 --- a/tools/perf/util/build-id.h +++ b/tools/perf/util/build-id.h @@ -4,7 +4,7 @@ #define BUILD_ID_SIZE 20 #include "tool.h" -#include "types.h" +#include <linux/types.h> extern struct perf_tool build_id__mark_dso_hit_ops; struct dso; diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 8d9db454f1a9..437ee09727e6 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -25,6 +25,84 @@ __thread struct callchain_cursor callchain_cursor; +int +parse_callchain_report_opt(const char *arg) +{ + char *tok, *tok2; + char *endptr; + + symbol_conf.use_callchain = true; + + if (!arg) + return 0; + + tok = strtok((char *)arg, ","); + if (!tok) + return -1; + + /* get the output mode */ + if (!strncmp(tok, "graph", strlen(arg))) { + callchain_param.mode = CHAIN_GRAPH_ABS; + + } else if (!strncmp(tok, "flat", strlen(arg))) { + callchain_param.mode = CHAIN_FLAT; + } else if (!strncmp(tok, "fractal", strlen(arg))) { + callchain_param.mode = CHAIN_GRAPH_REL; + } else if (!strncmp(tok, "none", strlen(arg))) { + callchain_param.mode = CHAIN_NONE; + symbol_conf.use_callchain = false; + return 0; + } else { + return -1; + } + + /* get the min percentage */ + tok = strtok(NULL, ","); + if (!tok) + goto setup; + + callchain_param.min_percent = strtod(tok, &endptr); + if (tok == endptr) + return -1; + + /* get the print limit */ + tok2 = strtok(NULL, ","); + if (!tok2) + goto setup; + + if (tok2[0] != 'c') { + callchain_param.print_limit = strtoul(tok2, &endptr, 0); + tok2 = strtok(NULL, ","); + if (!tok2) + goto setup; + } + + /* get the call chain order */ + if (!strncmp(tok2, "caller", strlen("caller"))) + callchain_param.order = ORDER_CALLER; + else if (!strncmp(tok2, "callee", strlen("callee"))) + callchain_param.order = ORDER_CALLEE; + else + return -1; + + /* Get the sort key */ + tok2 = strtok(NULL, ","); + if (!tok2) + goto setup; + if (!strncmp(tok2, "function", strlen("function"))) + callchain_param.key = CCKEY_FUNCTION; + else if (!strncmp(tok2, "address", strlen("address"))) + callchain_param.key = CCKEY_ADDRESS; + else + return -1; +setup: + if (callchain_register_param(&callchain_param) < 0) { + pr_err("Can't register callchain params\n"); + return -1; + } + return 0; +} + static void rb_insert_callchain(struct rb_root *root, struct callchain_node *chain, enum chain_mode mode) @@ -538,7 +616,8 @@ int sample__resolve_callchain(struct perf_sample *sample, struct symbol **parent if (sample->callchain == NULL) return 0; - if (symbol_conf.use_callchain || sort__has_parent) { + if (symbol_conf.use_callchain || symbol_conf.cumulate_callchain || + sort__has_parent) { return machine__resolve_callchain(al->machine, evsel, al->thread, sample, parent, al, max_stack); } @@ -547,7 +626,49 @@ int sample__resolve_callchain(struct perf_sample *sample, struct symbol **parent int hist_entry__append_callchain(struct hist_entry *he, struct perf_sample *sample) { - if (!symbol_conf.use_callchain) + if (!symbol_conf.use_callchain || sample->callchain == NULL) return 0; return callchain_append(he->callchain, &callchain_cursor, sample->period); } + +int fill_callchain_info(struct addr_location *al, struct callchain_cursor_node *node, + bool hide_unresolved) +{ + al->map = node->map; + al->sym = node->sym; + if (node->map) + al->addr = node->map->map_ip(node->map, node->ip); + else + al->addr = node->ip; + + if (al->sym == NULL) { + if (hide_unresolved) + return 0; + if (al->map == NULL) + goto out; + } + + if (al->map->groups == &al->machine->kmaps) { + if (machine__is_host(al->machine)) { + al->cpumode = PERF_RECORD_MISC_KERNEL; + al->level = 'k'; + } else { + al->cpumode = PERF_RECORD_MISC_GUEST_KERNEL; + al->level = 'g'; + } + } else { + if (machine__is_host(al->machine)) { + al->cpumode = PERF_RECORD_MISC_USER; + al->level = '.'; + } else if (perf_guest) { + al->cpumode = PERF_RECORD_MISC_GUEST_USER; + al->level = 'u'; + } else { + al->cpumode = PERF_RECORD_MISC_HYPERVISOR; + al->level = 'H'; + } + } + +out: + return 1; +} diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index 8ad97e9b119f..da43619d6173 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -7,6 +7,13 @@ #include "event.h" #include "symbol.h" +enum perf_call_graph_mode { + CALLCHAIN_NONE, + CALLCHAIN_FP, + CALLCHAIN_DWARF, + CALLCHAIN_MAX +}; + enum chain_mode { CHAIN_NONE, CHAIN_FLAT, @@ -155,6 +162,31 @@ int sample__resolve_callchain(struct perf_sample *sample, struct symbol **parent struct perf_evsel *evsel, struct addr_location *al, int max_stack); int hist_entry__append_callchain(struct hist_entry *he, struct perf_sample *sample); +int fill_callchain_info(struct addr_location *al, struct callchain_cursor_node *node, + bool hide_unresolved); extern const char record_callchain_help[]; +int parse_callchain_report_opt(const char *arg); + +static inline void callchain_cursor_snapshot(struct callchain_cursor *dest, + struct callchain_cursor *src) +{ + *dest = *src; + + dest->first = src->curr; + dest->nr -= src->pos; +} + +#ifdef HAVE_SKIP_CALLCHAIN_IDX +extern int arch_skip_callchain_idx(struct machine *machine, + struct thread *thread, struct ip_callchain *chain); +#else +static inline int arch_skip_callchain_idx(struct machine *machine __maybe_unused, + struct thread *thread __maybe_unused, + struct ip_callchain *chain __maybe_unused) +{ + return -1; +} +#endif + #endif /* __PERF_CALLCHAIN_H */ diff --git a/tools/perf/util/cloexec.c b/tools/perf/util/cloexec.c new file mode 100644 index 000000000000..c5d05ec17220 --- /dev/null +++ b/tools/perf/util/cloexec.c @@ -0,0 +1,57 @@ +#include "util.h" +#include "../perf.h" +#include "cloexec.h" +#include "asm/bug.h" + +static unsigned long flag = PERF_FLAG_FD_CLOEXEC; + +static int perf_flag_probe(void) +{ + /* use 'safest' configuration as used in perf_evsel__fallback() */ + struct perf_event_attr attr = { + .type = PERF_COUNT_SW_CPU_CLOCK, + .config = PERF_COUNT_SW_CPU_CLOCK, + }; + int fd; + int err; + + /* check cloexec flag */ + fd = sys_perf_event_open(&attr, 0, -1, -1, + PERF_FLAG_FD_CLOEXEC); + err = errno; + + if (fd >= 0) { + close(fd); + return 1; + } + + WARN_ONCE(err != EINVAL, + "perf_event_open(..., PERF_FLAG_FD_CLOEXEC) failed with unexpected error %d (%s)\n", + err, strerror(err)); + + /* not supported, confirm error related to PERF_FLAG_FD_CLOEXEC */ + fd = sys_perf_event_open(&attr, 0, -1, -1, 0); + err = errno; + + if (WARN_ONCE(fd < 0, + "perf_event_open(..., 0) failed unexpectedly with error %d (%s)\n", + err, strerror(err))) + return -1; + + close(fd); + + return 0; +} + +unsigned long perf_event_open_cloexec_flag(void) +{ + static bool probed; + + if (!probed) { + if (perf_flag_probe() <= 0) + flag = 0; + probed = true; + } + + return flag; +} diff --git a/tools/perf/util/cloexec.h b/tools/perf/util/cloexec.h new file mode 100644 index 000000000000..94a5a7d829d5 --- /dev/null +++ b/tools/perf/util/cloexec.h @@ -0,0 +1,6 @@ +#ifndef __PERF_CLOEXEC_H +#define __PERF_CLOEXEC_H + +unsigned long perf_event_open_cloexec_flag(void); + +#endif /* __PERF_CLOEXEC_H */ diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index 3e0fdd369ccb..1e5e2e5af6b1 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -11,6 +11,7 @@ #include "util.h" #include "cache.h" #include "exec_cmd.h" +#include "util/hist.h" /* perf_hist_config */ #define MAXNAME (256) @@ -349,12 +350,28 @@ static int perf_default_core_config(const char *var __maybe_unused, return 0; } +static int perf_ui_config(const char *var, const char *value) +{ + /* Add other config variables here. */ + if (!strcmp(var, "ui.show-headers")) { + symbol_conf.show_hist_headers = perf_config_bool(var, value); + return 0; + } + return 0; +} + int perf_default_config(const char *var, const char *value, void *dummy __maybe_unused) { if (!prefixcmp(var, "core.")) return perf_default_core_config(var, value); + if (!prefixcmp(var, "hist.")) + return perf_hist_config(var, value); + + if (!prefixcmp(var, "ui.")) + return perf_ui_config(var, value); + /* Add other config variables here. */ return 0; } diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c index 7fe4994eeb63..c4e55b71010c 100644 --- a/tools/perf/util/cpumap.c +++ b/tools/perf/util/cpumap.c @@ -317,3 +317,163 @@ int cpu_map__build_core_map(struct cpu_map *cpus, struct cpu_map **corep) { return cpu_map__build_map(cpus, corep, cpu_map__get_core); } + +/* setup simple routines to easily access node numbers given a cpu number */ +static int get_max_num(char *path, int *max) +{ + size_t num; + char *buf; + int err = 0; + + if (filename__read_str(path, &buf, &num)) + return -1; + + buf[num] = '\0'; + + /* start on the right, to find highest node num */ + while (--num) { + if ((buf[num] == ',') || (buf[num] == '-')) { + num++; + break; + } + } + if (sscanf(&buf[num], "%d", max) < 1) { + err = -1; + goto out; + } + + /* convert from 0-based to 1-based */ + (*max)++; + +out: + free(buf); + return err; +} + +/* Determine highest possible cpu in the system for sparse allocation */ +static void set_max_cpu_num(void) +{ + const char *mnt; + char path[PATH_MAX]; + int ret = -1; + + /* set up default */ + max_cpu_num = 4096; + + mnt = sysfs__mountpoint(); + if (!mnt) + goto out; + + /* get the highest possible cpu number for a sparse allocation */ + ret = snprintf(path, PATH_MAX, "%s/devices/system/cpu/possible", mnt); + if (ret == PATH_MAX) { + pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX); + goto out; + } + + ret = get_max_num(path, &max_cpu_num); + +out: + if (ret) + pr_err("Failed to read max cpus, using default of %d\n", max_cpu_num); +} + +/* Determine highest possible node in the system for sparse allocation */ +static void set_max_node_num(void) +{ + const char *mnt; + char path[PATH_MAX]; + int ret = -1; + + /* set up default */ + max_node_num = 8; + + mnt = sysfs__mountpoint(); + if (!mnt) + goto out; + + /* get the highest possible cpu number for a sparse allocation */ + ret = snprintf(path, PATH_MAX, "%s/devices/system/node/possible", mnt); + if (ret == PATH_MAX) { + pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX); + goto out; + } + + ret = get_max_num(path, &max_node_num); + +out: + if (ret) + pr_err("Failed to read max nodes, using default of %d\n", max_node_num); +} + +static int init_cpunode_map(void) +{ + int i; + + set_max_cpu_num(); + set_max_node_num(); + + cpunode_map = calloc(max_cpu_num, sizeof(int)); + if (!cpunode_map) { + pr_err("%s: calloc failed\n", __func__); + return -1; + } + + for (i = 0; i < max_cpu_num; i++) + cpunode_map[i] = -1; + + return 0; +} + +int cpu__setup_cpunode_map(void) +{ + struct dirent *dent1, *dent2; + DIR *dir1, *dir2; + unsigned int cpu, mem; + char buf[PATH_MAX]; + char path[PATH_MAX]; + const char *mnt; + int n; + + /* initialize globals */ + if (init_cpunode_map()) + return -1; + + mnt = sysfs__mountpoint(); + if (!mnt) + return 0; + + n = snprintf(path, PATH_MAX, "%s/devices/system/node", mnt); + if (n == PATH_MAX) { + pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX); + return -1; + } + + dir1 = opendir(path); + if (!dir1) + return 0; + + /* walk tree and setup map */ + while ((dent1 = readdir(dir1)) != NULL) { + if (dent1->d_type != DT_DIR || sscanf(dent1->d_name, "node%u", &mem) < 1) + continue; + + n = snprintf(buf, PATH_MAX, "%s/%s", path, dent1->d_name); + if (n == PATH_MAX) { + pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX); + continue; + } + + dir2 = opendir(buf); + if (!dir2) + continue; + while ((dent2 = readdir(dir2)) != NULL) { + if (dent2->d_type != DT_LNK || sscanf(dent2->d_name, "cpu%u", &cpu) < 1) + continue; + cpunode_map[cpu] = mem; + } + closedir(dir2); + } + closedir(dir1); + return 0; +} diff --git a/tools/perf/util/cpumap.h b/tools/perf/util/cpumap.h index b123bb9d6f55..61a654849002 100644 --- a/tools/perf/util/cpumap.h +++ b/tools/perf/util/cpumap.h @@ -4,6 +4,9 @@ #include <stdio.h> #include <stdbool.h> +#include "perf.h" +#include "util/debug.h" + struct cpu_map { int nr; int map[]; @@ -46,4 +49,36 @@ static inline bool cpu_map__empty(const struct cpu_map *map) return map ? map->map[0] == -1 : true; } +int max_cpu_num; +int max_node_num; +int *cpunode_map; + +int cpu__setup_cpunode_map(void); + +static inline int cpu__max_node(void) +{ + if (unlikely(!max_node_num)) + pr_debug("cpu_map not initialized\n"); + + return max_node_num; +} + +static inline int cpu__max_cpu(void) +{ + if (unlikely(!max_cpu_num)) + pr_debug("cpu_map not initialized\n"); + + return max_cpu_num; +} + +static inline int cpu__get_node(int cpu) +{ + if (unlikely(cpunode_map == NULL)) { + pr_debug("cpu_map not initialized\n"); + return -1; + } + + return cpunode_map[cpu]; +} + #endif /* __PERF_CPUMAP_H */ diff --git a/tools/perf/util/data.c b/tools/perf/util/data.c index 55de44ecebef..29d720cf5844 100644 --- a/tools/perf/util/data.c +++ b/tools/perf/util/data.c @@ -7,6 +7,7 @@ #include "data.h" #include "util.h" +#include "debug.h" static bool check_pipe(struct perf_data_file *file) { @@ -65,7 +66,7 @@ static int open_file_read(struct perf_data_file *file) goto out_close; if (!file->force && st.st_uid && (st.st_uid != geteuid())) { - pr_err("file %s not owned by current user or root\n", + pr_err("File %s not owned by current user or root (use -f to override)\n", file->path); goto out_close; } diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 299b55586502..71d419362634 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -16,11 +16,11 @@ int verbose; bool dump_trace = false, quiet = false; -static int _eprintf(int level, const char *fmt, va_list args) +static int _eprintf(int level, int var, const char *fmt, va_list args) { int ret = 0; - if (verbose >= level) { + if (var >= level) { if (use_browser >= 1) ui_helpline__vshow(fmt, args); else @@ -30,13 +30,13 @@ static int _eprintf(int level, const char *fmt, va_list args) return ret; } -int eprintf(int level, const char *fmt, ...) +int eprintf(int level, int var, const char *fmt, ...) { va_list args; int ret; va_start(args, fmt); - ret = _eprintf(level, fmt, args); + ret = _eprintf(level, var, fmt, args); va_end(args); return ret; @@ -51,9 +51,9 @@ void pr_stat(const char *fmt, ...) va_list args; va_start(args, fmt); - _eprintf(1, fmt, args); + _eprintf(1, verbose, fmt, args); va_end(args); - eprintf(1, "\n"); + eprintf(1, verbose, "\n"); } int dump_printf(const char *fmt, ...) @@ -105,3 +105,47 @@ void trace_event(union perf_event *event) } printf(".\n"); } + +static struct debug_variable { + const char *name; + int *ptr; +} debug_variables[] = { + { .name = "verbose", .ptr = &verbose }, + { .name = NULL, } +}; + +int perf_debug_option(const char *str) +{ + struct debug_variable *var = &debug_variables[0]; + char *vstr, *s = strdup(str); + int v = 1; + + vstr = strchr(s, '='); + if (vstr) + *vstr++ = 0; + + while (var->name) { + if (!strcmp(s, var->name)) + break; + var++; + } + + if (!var->name) { + pr_err("Unknown debug variable name '%s'\n", s); + free(s); + return -1; + } + + if (vstr) { + v = atoi(vstr); + /* + * Allow only values in range (0, 10), + * otherwise set 0. + */ + v = (v < 0) || (v > 10) ? 0 : v; + } + + *var->ptr = v; + free(s); + return 0; +} diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index 443694c36b03..89fb6b0f7ab2 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -11,6 +11,24 @@ extern int verbose; extern bool quiet, dump_trace; +#ifndef pr_fmt +#define pr_fmt(fmt) fmt +#endif + +#define pr_err(fmt, ...) \ + eprintf(0, verbose, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_warning(fmt, ...) \ + eprintf(0, verbose, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_info(fmt, ...) \ + eprintf(0, verbose, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_debug(fmt, ...) \ + eprintf(1, verbose, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_debugN(n, fmt, ...) \ + eprintf(n, verbose, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_debug2(fmt, ...) pr_debugN(2, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_debug3(fmt, ...) pr_debugN(3, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_debug4(fmt, ...) pr_debugN(4, pr_fmt(fmt), ##__VA_ARGS__) + int dump_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); void trace_event(union perf_event *event); @@ -19,4 +37,8 @@ int ui__warning(const char *format, ...) __attribute__((format(printf, 1, 2))); void pr_stat(const char *fmt, ...); +int eprintf(int level, int var, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + +int perf_debug_option(const char *str); + #endif /* __PERF_DEBUG_H */ diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index 64453d63b971..90d02c661dd4 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -1,3 +1,6 @@ +#include <asm/bug.h> +#include <sys/time.h> +#include <sys/resource.h> #include "symbol.h" #include "dso.h" #include "machine.h" @@ -136,7 +139,48 @@ int dso__read_binary_type_filename(const struct dso *dso, return ret; } -static int open_dso(struct dso *dso, struct machine *machine) +/* + * Global list of open DSOs and the counter. + */ +static LIST_HEAD(dso__data_open); +static long dso__data_open_cnt; + +static void dso__list_add(struct dso *dso) +{ + list_add_tail(&dso->data.open_entry, &dso__data_open); + dso__data_open_cnt++; +} + +static void dso__list_del(struct dso *dso) +{ + list_del(&dso->data.open_entry); + WARN_ONCE(dso__data_open_cnt <= 0, + "DSO data fd counter out of bounds."); + dso__data_open_cnt--; +} + +static void close_first_dso(void); + +static int do_open(char *name) +{ + int fd; + + do { + fd = open(name, O_RDONLY); + if (fd >= 0) + return fd; + + pr_debug("dso open failed, mmap: %s\n", strerror(errno)); + if (!dso__data_open_cnt || errno != EMFILE) + break; + + close_first_dso(); + } while (1); + + return -1; +} + +static int __open_dso(struct dso *dso, struct machine *machine) { int fd; char *root_dir = (char *)""; @@ -154,11 +198,130 @@ static int open_dso(struct dso *dso, struct machine *machine) return -EINVAL; } - fd = open(name, O_RDONLY); + fd = do_open(name); free(name); return fd; } +static void check_data_close(void); + +/** + * dso_close - Open DSO data file + * @dso: dso object + * + * Open @dso's data file descriptor and updates + * list/count of open DSO objects. + */ +static int open_dso(struct dso *dso, struct machine *machine) +{ + int fd = __open_dso(dso, machine); + + if (fd >= 0) { + dso__list_add(dso); + /* + * Check if we crossed the allowed number + * of opened DSOs and close one if needed. + */ + check_data_close(); + } + + return fd; +} + +static void close_data_fd(struct dso *dso) +{ + if (dso->data.fd >= 0) { + close(dso->data.fd); + dso->data.fd = -1; + dso->data.file_size = 0; + dso__list_del(dso); + } +} + +/** + * dso_close - Close DSO data file + * @dso: dso object + * + * Close @dso's data file descriptor and updates + * list/count of open DSO objects. + */ +static void close_dso(struct dso *dso) +{ + close_data_fd(dso); +} + +static void close_first_dso(void) +{ + struct dso *dso; + + dso = list_first_entry(&dso__data_open, struct dso, data.open_entry); + close_dso(dso); +} + +static rlim_t get_fd_limit(void) +{ + struct rlimit l; + rlim_t limit = 0; + + /* Allow half of the current open fd limit. */ + if (getrlimit(RLIMIT_NOFILE, &l) == 0) { + if (l.rlim_cur == RLIM_INFINITY) + limit = l.rlim_cur; + else + limit = l.rlim_cur / 2; + } else { + pr_err("failed to get fd limit\n"); + limit = 1; + } + + return limit; +} + +static bool may_cache_fd(void) +{ + static rlim_t limit; + + if (!limit) + limit = get_fd_limit(); + + if (limit == RLIM_INFINITY) + return true; + + return limit > (rlim_t) dso__data_open_cnt; +} + +/* + * Check and close LRU dso if we crossed allowed limit + * for opened dso file descriptors. The limit is half + * of the RLIMIT_NOFILE files opened. +*/ +static void check_data_close(void) +{ + bool cache_fd = may_cache_fd(); + + if (!cache_fd) + close_first_dso(); +} + +/** + * dso__data_close - Close DSO data file + * @dso: dso object + * + * External interface to close @dso's data file descriptor. + */ +void dso__data_close(struct dso *dso) +{ + close_dso(dso); +} + +/** + * dso__data_fd - Get dso's data file descriptor + * @dso: dso object + * @machine: machine object + * + * External interface to find dso's file, open it and + * returns file descriptor. + */ int dso__data_fd(struct dso *dso, struct machine *machine) { enum dso_binary_type binary_type_data[] = { @@ -168,21 +331,44 @@ int dso__data_fd(struct dso *dso, struct machine *machine) }; int i = 0; - if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) - return open_dso(dso, machine); + if (dso->data.status == DSO_DATA_STATUS_ERROR) + return -1; - do { - int fd; + if (dso->data.fd >= 0) + goto out; + if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) { + dso->data.fd = open_dso(dso, machine); + goto out; + } + + do { dso->binary_type = binary_type_data[i++]; - fd = open_dso(dso, machine); - if (fd >= 0) - return fd; + dso->data.fd = open_dso(dso, machine); + if (dso->data.fd >= 0) + goto out; } while (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND); +out: + if (dso->data.fd >= 0) + dso->data.status = DSO_DATA_STATUS_OK; + else + dso->data.status = DSO_DATA_STATUS_ERROR; + + return dso->data.fd; +} - return -EINVAL; +bool dso__data_status_seen(struct dso *dso, enum dso_data_status_seen by) +{ + u32 flag = 1 << by; + + if (dso->data.status_seen & flag) + return true; + + dso->data.status_seen |= flag; + + return false; } static void @@ -260,16 +446,10 @@ dso_cache__memcpy(struct dso_cache *cache, u64 offset, } static ssize_t -dso_cache__read(struct dso *dso, struct machine *machine, - u64 offset, u8 *data, ssize_t size) +dso_cache__read(struct dso *dso, u64 offset, u8 *data, ssize_t size) { struct dso_cache *cache; ssize_t ret; - int fd; - - fd = dso__data_fd(dso, machine); - if (fd < 0) - return -1; do { u64 cache_offset; @@ -283,16 +463,16 @@ dso_cache__read(struct dso *dso, struct machine *machine, cache_offset = offset & DSO__DATA_CACHE_MASK; ret = -EINVAL; - if (-1 == lseek(fd, cache_offset, SEEK_SET)) + if (-1 == lseek(dso->data.fd, cache_offset, SEEK_SET)) break; - ret = read(fd, cache->data, DSO__DATA_CACHE_SIZE); + ret = read(dso->data.fd, cache->data, DSO__DATA_CACHE_SIZE); if (ret <= 0) break; cache->offset = cache_offset; cache->size = ret; - dso_cache__insert(&dso->cache, cache); + dso_cache__insert(&dso->data.cache, cache); ret = dso_cache__memcpy(cache, offset, data, size); @@ -301,24 +481,27 @@ dso_cache__read(struct dso *dso, struct machine *machine, if (ret <= 0) free(cache); - close(fd); return ret; } -static ssize_t dso_cache_read(struct dso *dso, struct machine *machine, - u64 offset, u8 *data, ssize_t size) +static ssize_t dso_cache_read(struct dso *dso, u64 offset, + u8 *data, ssize_t size) { struct dso_cache *cache; - cache = dso_cache__find(&dso->cache, offset); + cache = dso_cache__find(&dso->data.cache, offset); if (cache) return dso_cache__memcpy(cache, offset, data, size); else - return dso_cache__read(dso, machine, offset, data, size); + return dso_cache__read(dso, offset, data, size); } -ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, - u64 offset, u8 *data, ssize_t size) +/* + * Reads and caches dso data DSO__DATA_CACHE_SIZE size chunks + * in the rb_tree. Any read to already cached data is served + * by cached data. + */ +static ssize_t cached_read(struct dso *dso, u64 offset, u8 *data, ssize_t size) { ssize_t r = 0; u8 *p = data; @@ -326,7 +509,7 @@ ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, do { ssize_t ret; - ret = dso_cache_read(dso, machine, offset, p, size); + ret = dso_cache_read(dso, offset, p, size); if (ret < 0) return ret; @@ -346,6 +529,89 @@ ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, return r; } +static int data_file_size(struct dso *dso) +{ + struct stat st; + + if (!dso->data.file_size) { + if (fstat(dso->data.fd, &st)) { + pr_err("dso mmap failed, fstat: %s\n", strerror(errno)); + return -1; + } + dso->data.file_size = st.st_size; + } + + return 0; +} + +/** + * dso__data_size - Return dso data size + * @dso: dso object + * @machine: machine object + * + * Return: dso data size + */ +off_t dso__data_size(struct dso *dso, struct machine *machine) +{ + int fd; + + fd = dso__data_fd(dso, machine); + if (fd < 0) + return fd; + + if (data_file_size(dso)) + return -1; + + /* For now just estimate dso data size is close to file size */ + return dso->data.file_size; +} + +static ssize_t data_read_offset(struct dso *dso, u64 offset, + u8 *data, ssize_t size) +{ + if (data_file_size(dso)) + return -1; + + /* Check the offset sanity. */ + if (offset > dso->data.file_size) + return -1; + + if (offset + size < offset) + return -1; + + return cached_read(dso, offset, data, size); +} + +/** + * dso__data_read_offset - Read data from dso file offset + * @dso: dso object + * @machine: machine object + * @offset: file offset + * @data: buffer to store data + * @size: size of the @data buffer + * + * External interface to read data from dso file offset. Open + * dso data file and use cached_read to get the data. + */ +ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, + u64 offset, u8 *data, ssize_t size) +{ + if (dso__data_fd(dso, machine) < 0) + return -1; + + return data_read_offset(dso, offset, data, size); +} + +/** + * dso__data_read_addr - Read data from dso address + * @dso: dso object + * @machine: machine object + * @add: virtual memory address + * @data: buffer to store data + * @size: size of the @data buffer + * + * External interface to read data from dso address. + */ ssize_t dso__data_read_addr(struct dso *dso, struct map *map, struct machine *machine, u64 addr, u8 *data, ssize_t size) @@ -473,9 +739,12 @@ struct dso *dso__new(const char *name) dso__set_short_name(dso, dso->name, false); for (i = 0; i < MAP__NR_TYPES; ++i) dso->symbols[i] = dso->symbol_names[i] = RB_ROOT; - dso->cache = RB_ROOT; + dso->data.cache = RB_ROOT; + dso->data.fd = -1; + dso->data.status = DSO_DATA_STATUS_UNKNOWN; dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND; + dso->is_64_bit = (sizeof(void *) == 8); dso->loaded = 0; dso->rel = 0; dso->sorted_by_name = 0; @@ -485,6 +754,7 @@ struct dso *dso__new(const char *name) dso->kernel = DSO_TYPE_USER; dso->needs_swap = DSO_SWAP__UNSET; INIT_LIST_HEAD(&dso->node); + INIT_LIST_HEAD(&dso->data.open_entry); } return dso; @@ -506,7 +776,8 @@ void dso__delete(struct dso *dso) dso->long_name_allocated = false; } - dso_cache__free(&dso->cache); + dso__data_close(dso); + dso_cache__free(&dso->data.cache); dso__free_a2l(dso); zfree(&dso->symsrc_filename); free(dso); @@ -669,3 +940,14 @@ size_t dso__fprintf(struct dso *dso, enum map_type type, FILE *fp) return ret; } + +enum dso_type dso__type(struct dso *dso, struct machine *machine) +{ + int fd; + + fd = dso__data_fd(dso, machine); + if (fd < 0) + return DSO__TYPE_UNKNOWN; + + return dso__type_fd(fd); +} diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index ab06f1c03655..5e463c0964d4 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -4,7 +4,8 @@ #include <linux/types.h> #include <linux/rbtree.h> #include <stdbool.h> -#include "types.h" +#include <linux/types.h> +#include <linux/bitops.h> #include "map.h" #include "build-id.h" @@ -40,6 +41,23 @@ enum dso_swap_type { DSO_SWAP__YES, }; +enum dso_data_status { + DSO_DATA_STATUS_ERROR = -1, + DSO_DATA_STATUS_UNKNOWN = 0, + DSO_DATA_STATUS_OK = 1, +}; + +enum dso_data_status_seen { + DSO_DATA_STATUS_SEEN_ITRACE, +}; + +enum dso_type { + DSO__TYPE_UNKNOWN, + DSO__TYPE_64BIT, + DSO__TYPE_32BIT, + DSO__TYPE_X32BIT, +}; + #define DSO__SWAP(dso, type, val) \ ({ \ type ____r = val; \ @@ -76,7 +94,6 @@ struct dso { struct list_head node; struct rb_root symbols[MAP__NR_TYPES]; struct rb_root symbol_names[MAP__NR_TYPES]; - struct rb_root cache; void *a2l; char *symsrc_filename; unsigned int a2l_fails; @@ -91,6 +108,7 @@ struct dso { u8 annotate_warned:1; u8 short_name_allocated:1; u8 long_name_allocated:1; + u8 is_64_bit:1; u8 sorted_by_name; u8 loaded; u8 rel; @@ -99,6 +117,17 @@ struct dso { const char *long_name; u16 long_name_len; u16 short_name_len; + + /* dso data file */ + struct { + struct rb_root cache; + int fd; + int status; + u32 status_seen; + size_t file_size; + struct list_head open_entry; + } data; + char name[0]; }; @@ -141,12 +170,55 @@ char dso__symtab_origin(const struct dso *dso); int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type type, char *root_dir, char *filename, size_t size); +/* + * The dso__data_* external interface provides following functions: + * dso__data_fd + * dso__data_close + * dso__data_size + * dso__data_read_offset + * dso__data_read_addr + * + * Please refer to the dso.c object code for each function and + * arguments documentation. Following text tries to explain the + * dso file descriptor caching. + * + * The dso__data* interface allows caching of opened file descriptors + * to speed up the dso data accesses. The idea is to leave the file + * descriptor opened ideally for the whole life of the dso object. + * + * The current usage of the dso__data_* interface is as follows: + * + * Get DSO's fd: + * int fd = dso__data_fd(dso, machine); + * USE 'fd' SOMEHOW + * + * Read DSO's data: + * n = dso__data_read_offset(dso_0, &machine, 0, buf, BUFSIZE); + * n = dso__data_read_addr(dso_0, &machine, 0, buf, BUFSIZE); + * + * Eventually close DSO's fd: + * dso__data_close(dso); + * + * It is not necessary to close the DSO object data file. Each time new + * DSO data file is opened, the limit (RLIMIT_NOFILE/2) is checked. Once + * it is crossed, the oldest opened DSO object is closed. + * + * The dso__delete function calls close_dso function to ensure the + * data file descriptor gets closed/unmapped before the dso object + * is freed. + * + * TODO +*/ int dso__data_fd(struct dso *dso, struct machine *machine); +void dso__data_close(struct dso *dso); + +off_t dso__data_size(struct dso *dso, struct machine *machine); ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, u64 offset, u8 *data, ssize_t size); ssize_t dso__data_read_addr(struct dso *dso, struct map *map, struct machine *machine, u64 addr, u8 *data, ssize_t size); +bool dso__data_status_seen(struct dso *dso, enum dso_data_status_seen by); struct map *dso__new_map(const char *name); struct dso *dso__kernel_findnew(struct machine *machine, const char *name, @@ -181,4 +253,6 @@ static inline bool dso__is_kcore(struct dso *dso) void dso__free_a2l(struct dso *dso); +enum dso_type dso__type(struct dso *dso, struct machine *machine); + #endif /* __PERF_DSO */ diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index 9d12aa6dd485..1398c83d896d 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -1,4 +1,5 @@ #include <linux/types.h> +#include <sys/mman.h> #include "event.h" #include "debug.h" #include "hist.h" @@ -178,13 +179,14 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, return -1; } - event->header.type = PERF_RECORD_MMAP; + event->header.type = PERF_RECORD_MMAP2; while (1) { char bf[BUFSIZ]; char prot[5]; char execname[PATH_MAX]; char anonstr[] = "//anon"; + unsigned int ino; size_t size; ssize_t n; @@ -195,15 +197,20 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, strcpy(execname, ""); /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */ - n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %*x:%*x %*u %s\n", - &event->mmap.start, &event->mmap.len, prot, - &event->mmap.pgoff, - execname); + n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %x:%x %u %s\n", + &event->mmap2.start, &event->mmap2.len, prot, + &event->mmap2.pgoff, &event->mmap2.maj, + &event->mmap2.min, + &ino, execname); + /* * Anon maps don't have the execname. */ - if (n < 4) + if (n < 7) continue; + + event->mmap2.ino = (u64)ino; + /* * Just like the kernel, see __perf_event_mmap in kernel/perf_event.c */ @@ -212,6 +219,21 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, else event->header.misc = PERF_RECORD_MISC_GUEST_USER; + /* map protection and flags bits */ + event->mmap2.prot = 0; + event->mmap2.flags = 0; + if (prot[0] == 'r') + event->mmap2.prot |= PROT_READ; + if (prot[1] == 'w') + event->mmap2.prot |= PROT_WRITE; + if (prot[2] == 'x') + event->mmap2.prot |= PROT_EXEC; + + if (prot[3] == 's') + event->mmap2.flags |= MAP_SHARED; + else + event->mmap2.flags |= MAP_PRIVATE; + if (prot[2] != 'x') { if (!mmap_data || prot[0] != 'r') continue; @@ -223,15 +245,15 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, strcpy(execname, anonstr); size = strlen(execname) + 1; - memcpy(event->mmap.filename, execname, size); + memcpy(event->mmap2.filename, execname, size); size = PERF_ALIGN(size, sizeof(u64)); - event->mmap.len -= event->mmap.start; - event->mmap.header.size = (sizeof(event->mmap) - - (sizeof(event->mmap.filename) - size)); - memset(event->mmap.filename + size, 0, machine->id_hdr_size); - event->mmap.header.size += machine->id_hdr_size; - event->mmap.pid = tgid; - event->mmap.tid = pid; + event->mmap2.len -= event->mmap.start; + event->mmap2.header.size = (sizeof(event->mmap2) - + (sizeof(event->mmap2.filename) - size)); + memset(event->mmap2.filename + size, 0, machine->id_hdr_size); + event->mmap2.header.size += machine->id_hdr_size; + event->mmap2.pid = tgid; + event->mmap2.tid = pid; if (process(tool, event, &synth_sample, machine) != 0) { rc = -1; @@ -581,7 +603,14 @@ int perf_event__synthesize_kernel_mmap(struct perf_tool *tool, size_t perf_event__fprintf_comm(union perf_event *event, FILE *fp) { - return fprintf(fp, ": %s:%d\n", event->comm.comm, event->comm.tid); + const char *s; + + if (event->header.misc & PERF_RECORD_MISC_COMM_EXEC) + s = " exec"; + else + s = ""; + + return fprintf(fp, "%s: %s:%d\n", s, event->comm.comm, event->comm.tid); } int perf_event__process_comm(struct perf_tool *tool __maybe_unused, @@ -612,12 +641,15 @@ size_t perf_event__fprintf_mmap(union perf_event *event, FILE *fp) size_t perf_event__fprintf_mmap2(union perf_event *event, FILE *fp) { return fprintf(fp, " %d/%d: [%#" PRIx64 "(%#" PRIx64 ") @ %#" PRIx64 - " %02x:%02x %"PRIu64" %"PRIu64"]: %c %s\n", + " %02x:%02x %"PRIu64" %"PRIu64"]: %c%c%c%c %s\n", event->mmap2.pid, event->mmap2.tid, event->mmap2.start, event->mmap2.len, event->mmap2.pgoff, event->mmap2.maj, event->mmap2.min, event->mmap2.ino, event->mmap2.ino_generation, - (event->header.misc & PERF_RECORD_MISC_MMAP_DATA) ? 'r' : 'x', + (event->mmap2.prot & PROT_READ) ? 'r' : '-', + (event->mmap2.prot & PROT_WRITE) ? 'w' : '-', + (event->mmap2.prot & PROT_EXEC) ? 'x' : '-', + (event->mmap2.flags & MAP_SHARED) ? 's' : 'p', event->mmap2.filename); } @@ -699,7 +731,7 @@ void thread__find_addr_map(struct thread *thread, enum map_type type, u64 addr, struct addr_location *al) { - struct map_groups *mg = &thread->mg; + struct map_groups *mg = thread->mg; bool load_map = false; al->machine = machine; @@ -756,6 +788,7 @@ try_again: cpumode == PERF_RECORD_MISC_USER && machine && mg != &machine->kmaps) { mg = &machine->kmaps; + load_map = true; goto try_again; } } else { @@ -788,7 +821,7 @@ int perf_event__preprocess_sample(const union perf_event *event, { u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; struct thread *thread = machine__findnew_thread(machine, sample->pid, - sample->pid); + sample->tid); if (thread == NULL) return -1; @@ -841,3 +874,45 @@ int perf_event__preprocess_sample(const union perf_event *event, return 0; } + +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; +} + +bool sample_addr_correlates_sym(struct perf_event_attr *attr) +{ + if (attr->type == PERF_TYPE_SOFTWARE && + (attr->config == PERF_COUNT_SW_PAGE_FAULTS || + attr->config == PERF_COUNT_SW_PAGE_FAULTS_MIN || + attr->config == PERF_COUNT_SW_PAGE_FAULTS_MAJ)) + return true; + + if (is_bts_event(attr)) + return true; + + return false; +} + +void perf_event__preprocess_sample_addr(union perf_event *event, + struct perf_sample *sample, + struct machine *machine, + struct thread *thread, + struct addr_location *al) +{ + u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + + thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION, + sample->addr, al); + if (!al->map) + thread__find_addr_map(thread, machine, cpumode, MAP__VARIABLE, + sample->addr, al); + + al->cpu = sample->cpu; + al->sym = NULL; + + if (al->map) + al->sym = map__find_symbol(al->map, al->addr, NULL); +} diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index 38457d447a13..94d6976180da 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -7,6 +7,7 @@ #include "../perf.h" #include "map.h" #include "build-id.h" +#include "perf_regs.h" struct mmap_event { struct perf_event_header header; @@ -27,6 +28,8 @@ struct mmap2_event { u32 min; u64 ino; u64 ino_generation; + u32 prot; + u32 flags; char filename[PATH_MAX]; }; @@ -87,6 +90,10 @@ struct regs_dump { u64 abi; u64 mask; u64 *regs; + + /* Cached values/mask filled by first register access. */ + u64 cache_regs[PERF_REGS_MAX]; + u64 cache_mask; }; struct stack_dump { @@ -112,6 +119,30 @@ struct sample_read { }; }; +struct ip_callchain { + u64 nr; + u64 ips[0]; +}; + +struct branch_flags { + u64 mispred:1; + u64 predicted:1; + u64 in_tx:1; + u64 abort:1; + u64 reserved:60; +}; + +struct branch_entry { + u64 from; + u64 to; + struct branch_flags flags; +}; + +struct branch_stack { + u64 nr; + struct branch_entry entries[0]; +}; + struct perf_sample { u64 ip; u32 pid, tid; @@ -257,6 +288,16 @@ int perf_event__preprocess_sample(const union perf_event *event, struct addr_location *al, struct perf_sample *sample); +struct thread; + +bool is_bts_event(struct perf_event_attr *attr); +bool sample_addr_correlates_sym(struct perf_event_attr *attr); +void perf_event__preprocess_sample_addr(union perf_event *event, + struct perf_sample *sample, + struct machine *machine, + struct thread *thread, + struct addr_location *al); + const char *perf_event__name(unsigned int id); size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type, diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c index 59ef2802fcf6..814e954c1318 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -606,12 +606,17 @@ static int perf_evlist__alloc_mmap(struct perf_evlist *evlist) return evlist->mmap != NULL ? 0 : -ENOMEM; } -static int __perf_evlist__mmap(struct perf_evlist *evlist, - int idx, int prot, int mask, int fd) +struct mmap_params { + int prot; + int mask; +}; + +static int __perf_evlist__mmap(struct perf_evlist *evlist, int idx, + struct mmap_params *mp, int fd) { evlist->mmap[idx].prev = 0; - evlist->mmap[idx].mask = mask; - evlist->mmap[idx].base = mmap(NULL, evlist->mmap_len, prot, + evlist->mmap[idx].mask = mp->mask; + evlist->mmap[idx].base = mmap(NULL, evlist->mmap_len, mp->prot, MAP_SHARED, fd, 0); if (evlist->mmap[idx].base == MAP_FAILED) { pr_debug2("failed to mmap perf event ring buffer, error %d\n", @@ -625,8 +630,8 @@ static int __perf_evlist__mmap(struct perf_evlist *evlist, } static int perf_evlist__mmap_per_evsel(struct perf_evlist *evlist, int idx, - int prot, int mask, int cpu, int thread, - int *output) + struct mmap_params *mp, int cpu, + int thread, int *output) { struct perf_evsel *evsel; @@ -635,8 +640,7 @@ static int perf_evlist__mmap_per_evsel(struct perf_evlist *evlist, int idx, if (*output == -1) { *output = fd; - if (__perf_evlist__mmap(evlist, idx, prot, mask, - *output) < 0) + if (__perf_evlist__mmap(evlist, idx, mp, *output) < 0) return -1; } else { if (ioctl(fd, PERF_EVENT_IOC_SET_OUTPUT, *output) != 0) @@ -651,8 +655,8 @@ static int perf_evlist__mmap_per_evsel(struct perf_evlist *evlist, int idx, return 0; } -static int perf_evlist__mmap_per_cpu(struct perf_evlist *evlist, int prot, - int mask) +static int perf_evlist__mmap_per_cpu(struct perf_evlist *evlist, + struct mmap_params *mp) { int cpu, thread; int nr_cpus = cpu_map__nr(evlist->cpus); @@ -663,8 +667,8 @@ static int perf_evlist__mmap_per_cpu(struct perf_evlist *evlist, int prot, int output = -1; for (thread = 0; thread < nr_threads; thread++) { - if (perf_evlist__mmap_per_evsel(evlist, cpu, prot, mask, - cpu, thread, &output)) + if (perf_evlist__mmap_per_evsel(evlist, cpu, mp, cpu, + thread, &output)) goto out_unmap; } } @@ -677,8 +681,8 @@ out_unmap: return -1; } -static int perf_evlist__mmap_per_thread(struct perf_evlist *evlist, int prot, - int mask) +static int perf_evlist__mmap_per_thread(struct perf_evlist *evlist, + struct mmap_params *mp) { int thread; int nr_threads = thread_map__nr(evlist->threads); @@ -687,8 +691,8 @@ static int perf_evlist__mmap_per_thread(struct perf_evlist *evlist, int prot, for (thread = 0; thread < nr_threads; thread++) { int output = -1; - if (perf_evlist__mmap_per_evsel(evlist, thread, prot, mask, 0, - thread, &output)) + if (perf_evlist__mmap_per_evsel(evlist, thread, mp, 0, thread, + &output)) goto out_unmap; } @@ -793,7 +797,9 @@ int perf_evlist__mmap(struct perf_evlist *evlist, unsigned int pages, struct perf_evsel *evsel; const struct cpu_map *cpus = evlist->cpus; const struct thread_map *threads = evlist->threads; - int prot = PROT_READ | (overwrite ? 0 : PROT_WRITE), mask; + struct mmap_params mp = { + .prot = PROT_READ | (overwrite ? 0 : PROT_WRITE), + }; if (evlist->mmap == NULL && perf_evlist__alloc_mmap(evlist) < 0) return -ENOMEM; @@ -804,7 +810,7 @@ int perf_evlist__mmap(struct perf_evlist *evlist, unsigned int pages, evlist->overwrite = overwrite; evlist->mmap_len = perf_evlist__mmap_size(pages); pr_debug("mmap size %zuB\n", evlist->mmap_len); - mask = evlist->mmap_len - page_size - 1; + mp.mask = evlist->mmap_len - page_size - 1; evlist__for_each(evlist, evsel) { if ((evsel->attr.read_format & PERF_FORMAT_ID) && @@ -814,9 +820,9 @@ int perf_evlist__mmap(struct perf_evlist *evlist, unsigned int pages, } if (cpu_map__empty(cpus)) - return perf_evlist__mmap_per_thread(evlist, prot, mask); + return perf_evlist__mmap_per_thread(evlist, &mp); - return perf_evlist__mmap_per_cpu(evlist, prot, mask); + return perf_evlist__mmap_per_cpu(evlist, &mp); } int perf_evlist__create_maps(struct perf_evlist *evlist, struct target *target) @@ -1214,10 +1220,11 @@ int perf_evlist__strerror_open(struct perf_evlist *evlist __maybe_unused, "For your workloads it needs to be <= 1\nHint:\t"); } printed += scnprintf(buf + printed, size - printed, - "For system wide tracing it needs to be set to -1"); + "For system wide tracing it needs to be set to -1.\n"); printed += scnprintf(buf + printed, size - printed, - ".\nHint:\tThe current value is %d.", value); + "Hint:\tTry: 'sudo sh -c \"echo -1 > /proc/sys/kernel/perf_event_paranoid\"'\n" + "Hint:\tThe current value is %d.", value); break; default: scnprintf(buf, size, "%s", emsg); diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 5c28d82b76c4..21a373ebea22 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -29,6 +29,7 @@ static struct { bool sample_id_all; bool exclude_guest; bool mmap2; + bool cloexec; } perf_missing_features; #define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y)) @@ -589,10 +590,10 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) } /* - * We default some events to a 1 default interval. But keep + * We default some events to have a default interval. But keep * it a weak assumption overridable by the user. */ - if (!attr->sample_period || (opts->user_freq != UINT_MAX && + if (!attr->sample_period || (opts->user_freq != UINT_MAX || opts->user_interval != ULLONG_MAX)) { if (opts->freq) { perf_evsel__set_sample_bit(evsel, PERIOD); @@ -623,7 +624,7 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) attr->mmap_data = track; } - if (opts->call_graph_enabled) + if (opts->call_graph_enabled && !evsel->no_aux_samples) perf_evsel__config_callgraph(evsel, opts); if (target__has_cpu(&opts->target)) @@ -637,7 +638,7 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) target__has_cpu(&opts->target) || per_cpu)) perf_evsel__set_sample_bit(evsel, TIME); - if (opts->raw_samples) { + if (opts->raw_samples && !evsel->no_aux_samples) { perf_evsel__set_sample_bit(evsel, TIME); perf_evsel__set_sample_bit(evsel, RAW); perf_evsel__set_sample_bit(evsel, CPU); @@ -650,7 +651,7 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) attr->watermark = 0; attr->wakeup_events = 1; } - if (opts->branch_stack) { + if (opts->branch_stack && !evsel->no_aux_samples) { perf_evsel__set_sample_bit(evsel, BRANCH_STACK); attr->branch_sample_type = opts->branch_stack; } @@ -659,6 +660,7 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) perf_evsel__set_sample_bit(evsel, WEIGHT); attr->mmap = track; + attr->mmap2 = track && !perf_missing_features.mmap2; attr->comm = track; if (opts->sample_transaction) @@ -680,6 +682,11 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) if (target__none(&opts->target) && perf_evsel__is_group_leader(evsel) && !opts->initial_delay) attr->enable_on_exec = 1; + + if (evsel->immediate) { + attr->disabled = 0; + attr->enable_on_exec = 0; + } } int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads) @@ -959,6 +966,7 @@ static size_t perf_event_attr__fprintf(struct perf_event_attr *attr, FILE *fp) ret += PRINT_ATTR2(exclude_user, exclude_kernel); ret += PRINT_ATTR2(exclude_hv, exclude_idle); ret += PRINT_ATTR2(mmap, comm); + ret += PRINT_ATTR2(mmap2, comm_exec); ret += PRINT_ATTR2(freq, inherit_stat); ret += PRINT_ATTR2(enable_on_exec, task); ret += PRINT_ATTR2(watermark, precise_ip); @@ -966,7 +974,6 @@ static size_t perf_event_attr__fprintf(struct perf_event_attr *attr, FILE *fp) ret += PRINT_ATTR2(exclude_host, exclude_guest); ret += PRINT_ATTR2N("excl.callchain_kern", exclude_callchain_kernel, "excl.callchain_user", exclude_callchain_user); - ret += PRINT_ATTR_U32(mmap2); ret += PRINT_ATTR_U32(wakeup_events); ret += PRINT_ATTR_U32(wakeup_watermark); @@ -988,7 +995,7 @@ static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, struct thread_map *threads) { int cpu, thread; - unsigned long flags = 0; + unsigned long flags = PERF_FLAG_FD_CLOEXEC; int pid = -1, err; enum { NO_CHANGE, SET_TO_MAX, INCREASED_MAX } set_rlimit = NO_CHANGE; @@ -997,11 +1004,13 @@ static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, return -ENOMEM; if (evsel->cgrp) { - flags = PERF_FLAG_PID_CGROUP; + flags |= PERF_FLAG_PID_CGROUP; pid = evsel->cgrp->fd; } fallback_missing_features: + if (perf_missing_features.cloexec) + flags &= ~(unsigned long)PERF_FLAG_FD_CLOEXEC; if (perf_missing_features.mmap2) evsel->attr.mmap2 = 0; if (perf_missing_features.exclude_guest) @@ -1070,7 +1079,10 @@ try_fallback: if (err != -EINVAL || cpu > 0 || thread > 0) goto out_close; - if (!perf_missing_features.mmap2 && evsel->attr.mmap2) { + if (!perf_missing_features.cloexec && (flags & PERF_FLAG_FD_CLOEXEC)) { + perf_missing_features.cloexec = true; + goto fallback_missing_features; + } else if (!perf_missing_features.mmap2 && evsel->attr.mmap2) { perf_missing_features.mmap2 = true; goto fallback_missing_features; } else if (!perf_missing_features.exclude_guest && @@ -1939,6 +1951,7 @@ int perf_evsel__fprintf(struct perf_evsel *evsel, if_print(mmap); if_print(mmap2); if_print(comm); + if_print(comm_exec); if_print(freq); if_print(inherit_stat); if_print(enable_on_exec); diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 0c9926cfb292..d7f93ce0ebc1 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -5,12 +5,12 @@ #include <stdbool.h> #include <stddef.h> #include <linux/perf_event.h> -#include "types.h" +#include <linux/types.h> #include "xyarray.h" #include "cgroup.h" #include "hist.h" #include "symbol.h" - + struct perf_counts_values { union { struct { @@ -83,6 +83,8 @@ struct perf_evsel { int is_pos; bool supported; bool needs_swap; + bool no_aux_samples; + bool immediate; /* parse modifier helper */ int exclude_GH; int nr_members; @@ -91,6 +93,11 @@ struct perf_evsel { char *group_name; }; +union u64_swap { + u64 val64; + u32 val32[2]; +}; + #define hists_to_evsel(h) container_of(h, struct perf_evsel, hists) struct cpu_map; diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 893f8e2df928..158c787ce0c4 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -200,6 +200,47 @@ static int write_buildid(const char *name, size_t name_len, u8 *build_id, return write_padded(fd, name, name_len + 1, len); } +static int __dsos__hit_all(struct list_head *head) +{ + struct dso *pos; + + list_for_each_entry(pos, head, node) + pos->hit = true; + + return 0; +} + +static int machine__hit_all_dsos(struct machine *machine) +{ + int err; + + err = __dsos__hit_all(&machine->kernel_dsos); + if (err) + return err; + + return __dsos__hit_all(&machine->user_dsos); +} + +int dsos__hit_all(struct perf_session *session) +{ + struct rb_node *nd; + int err; + + err = machine__hit_all_dsos(&session->machines.host); + if (err) + return err; + + for (nd = rb_first(&session->machines.guests); nd; nd = rb_next(nd)) { + struct machine *pos = rb_entry(nd, struct machine, rb_node); + + err = machine__hit_all_dsos(pos); + if (err) + return err; + } + + return 0; +} + static int __dsos__write_buildid_table(struct list_head *head, struct machine *machine, pid_t pid, u16 misc, int fd) @@ -215,9 +256,9 @@ static int __dsos__write_buildid_table(struct list_head *head, if (!pos->hit) continue; - if (is_vdso_map(pos->short_name)) { - name = (char *) VDSO__MAP_NAME; - name_len = sizeof(VDSO__MAP_NAME) + 1; + if (dso__is_vdso(pos)) { + name = pos->short_name; + name_len = pos->short_name_len + 1; } else if (dso__is_kcore(pos)) { machine__mmap_name(machine, nm, sizeof(nm)); name = nm; @@ -298,7 +339,7 @@ int build_id_cache__add_s(const char *sbuild_id, const char *debugdir, len = scnprintf(filename, size, "%s%s%s", debugdir, slash ? "/" : "", - is_vdso ? VDSO__MAP_NAME : realname); + is_vdso ? DSO__NAME_VDSO : realname); if (mkdir_p(filename, 0755)) goto out_free; @@ -386,7 +427,7 @@ static int dso__cache_build_id(struct dso *dso, struct machine *machine, const char *debugdir) { bool is_kallsyms = dso->kernel && dso->long_name[0] != '/'; - bool is_vdso = is_vdso_map(dso->short_name); + bool is_vdso = dso__is_vdso(dso); const char *name = dso->long_name; char nm[PATH_MAX]; diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index a2d047bdf4ef..8f5cbaea64a5 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -4,10 +4,10 @@ #include <linux/perf_event.h> #include <sys/types.h> #include <stdbool.h> -#include "types.h" +#include <linux/bitmap.h> +#include <linux/types.h> #include "event.h" -#include <linux/bitmap.h> enum { HEADER_RESERVED = 0, /* always cleared */ @@ -151,6 +151,8 @@ int perf_event__process_build_id(struct perf_tool *tool, struct perf_session *session); bool is_perf_magic(u64 magic); +int dsos__hit_all(struct perf_session *session); + /* * arch specific callback */ diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index f38590d7561b..30df6187ee02 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -4,6 +4,7 @@ #include "session.h" #include "sort.h" #include "evsel.h" +#include "annotate.h" #include <math.h> static bool hists__filter_entry_by_dso(struct hists *hists, @@ -127,6 +128,8 @@ void hists__calc_col_len(struct hists *hists, struct hist_entry *h) + unresolved_col_width + 2; hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, symlen); + hists__new_col_len(hists, HISTC_MEM_DCACHELINE, + symlen + 1); } else { symlen = unresolved_col_width + 4 + 2; hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, @@ -225,14 +228,20 @@ static void he_stat__decay(struct he_stat *he_stat) static bool hists__decay_entry(struct hists *hists, struct hist_entry *he) { u64 prev_period = he->stat.period; + u64 diff; if (prev_period == 0) return true; he_stat__decay(&he->stat); + if (symbol_conf.cumulate_callchain) + he_stat__decay(he->stat_acc); + diff = prev_period - he->stat.period; + + hists->stats.total_period -= diff; if (!he->filtered) - hists->stats.total_period -= prev_period - he->stat.period; + hists->stats.total_non_filtered_period -= diff; return he->stat.period == 0; } @@ -259,8 +268,11 @@ void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel) if (sort__need_collapse) rb_erase(&n->rb_node_in, &hists->entries_collapsed); - hist_entry__free(n); --hists->nr_entries; + if (!n->filtered) + --hists->nr_non_filtered_entries; + + hist_entry__free(n); } } } @@ -269,14 +281,31 @@ void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel) * histogram, sorted on item, collects periods */ -static struct hist_entry *hist_entry__new(struct hist_entry *template) +static struct hist_entry *hist_entry__new(struct hist_entry *template, + bool sample_self) { - size_t callchain_size = symbol_conf.use_callchain ? sizeof(struct callchain_root) : 0; - struct hist_entry *he = zalloc(sizeof(*he) + callchain_size); + size_t callchain_size = 0; + struct hist_entry *he; + + if (symbol_conf.use_callchain || symbol_conf.cumulate_callchain) + callchain_size = sizeof(struct callchain_root); + + he = zalloc(sizeof(*he) + callchain_size); if (he != NULL) { *he = *template; + if (symbol_conf.cumulate_callchain) { + he->stat_acc = malloc(sizeof(he->stat)); + if (he->stat_acc == NULL) { + free(he); + return NULL; + } + memcpy(he->stat_acc, &he->stat, sizeof(he->stat)); + if (!sample_self) + memset(&he->stat, 0, sizeof(he->stat)); + } + if (he->ms.map) he->ms.map->referenced = true; @@ -288,6 +317,7 @@ static struct hist_entry *hist_entry__new(struct hist_entry *template) */ he->branch_info = malloc(sizeof(*he->branch_info)); if (he->branch_info == NULL) { + free(he->stat_acc); free(he); return NULL; } @@ -317,15 +347,6 @@ static struct hist_entry *hist_entry__new(struct hist_entry *template) return he; } -void hists__inc_nr_entries(struct hists *hists, struct hist_entry *h) -{ - if (!h->filtered) { - hists__calc_col_len(hists, h); - ++hists->nr_entries; - hists->stats.total_period += h->stat.period; - } -} - static u8 symbol__parent_filter(const struct symbol *parent) { if (symbol_conf.exclude_other && parent == NULL) @@ -335,7 +356,8 @@ static u8 symbol__parent_filter(const struct symbol *parent) static struct hist_entry *add_hist_entry(struct hists *hists, struct hist_entry *entry, - struct addr_location *al) + struct addr_location *al, + bool sample_self) { struct rb_node **p; struct rb_node *parent = NULL; @@ -359,7 +381,10 @@ static struct hist_entry *add_hist_entry(struct hists *hists, cmp = hist_entry__cmp(he, entry); if (!cmp) { - he_stat__add_period(&he->stat, period, weight); + if (sample_self) + he_stat__add_period(&he->stat, period, weight); + if (symbol_conf.cumulate_callchain) + he_stat__add_period(he->stat_acc, period, weight); /* * This mem info was allocated from sample__resolve_mem @@ -387,15 +412,17 @@ static struct hist_entry *add_hist_entry(struct hists *hists, p = &(*p)->rb_right; } - he = hist_entry__new(entry); + he = hist_entry__new(entry, sample_self); if (!he) return NULL; - hists->nr_entries++; rb_link_node(&he->rb_node_in, parent, p); rb_insert_color(&he->rb_node_in, hists->entries_in); out: - he_stat__add_cpumode_period(&he->stat, al->cpumode, period); + if (sample_self) + he_stat__add_cpumode_period(&he->stat, al->cpumode, period); + if (symbol_conf.cumulate_callchain) + he_stat__add_cpumode_period(he->stat_acc, al->cpumode, period); return he; } @@ -404,7 +431,8 @@ struct hist_entry *__hists__add_entry(struct hists *hists, struct symbol *sym_parent, struct branch_info *bi, struct mem_info *mi, - u64 period, u64 weight, u64 transaction) + u64 period, u64 weight, u64 transaction, + bool sample_self) { struct hist_entry entry = { .thread = al->thread, @@ -413,9 +441,10 @@ struct hist_entry *__hists__add_entry(struct hists *hists, .map = al->map, .sym = al->sym, }, - .cpu = al->cpu, - .ip = al->addr, - .level = al->level, + .cpu = al->cpu, + .cpumode = al->cpumode, + .ip = al->addr, + .level = al->level, .stat = { .nr_events = 1, .period = period, @@ -429,17 +458,442 @@ struct hist_entry *__hists__add_entry(struct hists *hists, .transaction = transaction, }; - return add_hist_entry(hists, &entry, al); + return add_hist_entry(hists, &entry, al, sample_self); +} + +static int +iter_next_nop_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + return 0; +} + +static int +iter_add_next_nop_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + return 0; +} + +static int +iter_prepare_mem_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct perf_sample *sample = iter->sample; + struct mem_info *mi; + + mi = sample__resolve_mem(sample, al); + if (mi == NULL) + return -ENOMEM; + + iter->priv = mi; + return 0; +} + +static int +iter_add_single_mem_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + u64 cost; + struct mem_info *mi = iter->priv; + struct hist_entry *he; + + if (mi == NULL) + return -EINVAL; + + cost = iter->sample->weight; + if (!cost) + cost = 1; + + /* + * must pass period=weight in order to get the correct + * sorting from hists__collapse_resort() which is solely + * based on periods. We want sorting be done on nr_events * weight + * and this is indirectly achieved by passing period=weight here + * and the he_stat__add_period() function. + */ + he = __hists__add_entry(&iter->evsel->hists, al, iter->parent, NULL, mi, + cost, cost, 0, true); + if (!he) + return -ENOMEM; + + iter->he = he; + return 0; +} + +static int +iter_finish_mem_entry(struct hist_entry_iter *iter, + struct addr_location *al __maybe_unused) +{ + struct perf_evsel *evsel = iter->evsel; + struct hist_entry *he = iter->he; + int err = -EINVAL; + + if (he == NULL) + goto out; + + hists__inc_nr_samples(&evsel->hists, he->filtered); + + err = hist_entry__append_callchain(he, iter->sample); + +out: + /* + * We don't need to free iter->priv (mem_info) here since + * the mem info was either already freed in add_hist_entry() or + * passed to a new hist entry by hist_entry__new(). + */ + iter->priv = NULL; + + iter->he = NULL; + return err; +} + +static int +iter_prepare_branch_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct branch_info *bi; + struct perf_sample *sample = iter->sample; + + bi = sample__resolve_bstack(sample, al); + if (!bi) + return -ENOMEM; + + iter->curr = 0; + iter->total = sample->branch_stack->nr; + + iter->priv = bi; + return 0; +} + +static int +iter_add_single_branch_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + /* to avoid calling callback function */ + iter->he = NULL; + + return 0; +} + +static int +iter_next_branch_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct branch_info *bi = iter->priv; + int i = iter->curr; + + if (bi == NULL) + return 0; + + if (iter->curr >= iter->total) + return 0; + + al->map = bi[i].to.map; + al->sym = bi[i].to.sym; + al->addr = bi[i].to.addr; + return 1; +} + +static int +iter_add_next_branch_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct branch_info *bi; + struct perf_evsel *evsel = iter->evsel; + struct hist_entry *he = NULL; + int i = iter->curr; + int err = 0; + + bi = iter->priv; + + if (iter->hide_unresolved && !(bi[i].from.sym && bi[i].to.sym)) + goto out; + + /* + * The report shows the percentage of total branches captured + * and not events sampled. Thus we use a pseudo period of 1. + */ + he = __hists__add_entry(&evsel->hists, al, iter->parent, &bi[i], NULL, + 1, 1, 0, true); + if (he == NULL) + return -ENOMEM; + + hists__inc_nr_samples(&evsel->hists, he->filtered); + +out: + iter->he = he; + iter->curr++; + return err; +} + +static int +iter_finish_branch_entry(struct hist_entry_iter *iter, + struct addr_location *al __maybe_unused) +{ + zfree(&iter->priv); + iter->he = NULL; + + return iter->curr >= iter->total ? 0 : -1; +} + +static int +iter_prepare_normal_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + return 0; +} + +static int +iter_add_single_normal_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct perf_evsel *evsel = iter->evsel; + struct perf_sample *sample = iter->sample; + struct hist_entry *he; + + he = __hists__add_entry(&evsel->hists, al, iter->parent, NULL, NULL, + sample->period, sample->weight, + sample->transaction, true); + if (he == NULL) + return -ENOMEM; + + iter->he = he; + return 0; +} + +static int +iter_finish_normal_entry(struct hist_entry_iter *iter, + struct addr_location *al __maybe_unused) +{ + struct hist_entry *he = iter->he; + struct perf_evsel *evsel = iter->evsel; + struct perf_sample *sample = iter->sample; + + if (he == NULL) + return 0; + + iter->he = NULL; + + hists__inc_nr_samples(&evsel->hists, he->filtered); + + return hist_entry__append_callchain(he, sample); +} + +static int +iter_prepare_cumulative_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + struct hist_entry **he_cache; + + callchain_cursor_commit(&callchain_cursor); + + /* + * This is for detecting cycles or recursions so that they're + * cumulated only one time to prevent entries more than 100% + * overhead. + */ + he_cache = malloc(sizeof(*he_cache) * (PERF_MAX_STACK_DEPTH + 1)); + if (he_cache == NULL) + return -ENOMEM; + + iter->priv = he_cache; + iter->curr = 0; + + return 0; +} + +static int +iter_add_single_cumulative_entry(struct hist_entry_iter *iter, + struct addr_location *al) +{ + struct perf_evsel *evsel = iter->evsel; + struct perf_sample *sample = iter->sample; + struct hist_entry **he_cache = iter->priv; + struct hist_entry *he; + int err = 0; + + he = __hists__add_entry(&evsel->hists, al, iter->parent, NULL, NULL, + sample->period, sample->weight, + sample->transaction, true); + if (he == NULL) + return -ENOMEM; + + iter->he = he; + he_cache[iter->curr++] = he; + + callchain_append(he->callchain, &callchain_cursor, sample->period); + + /* + * We need to re-initialize the cursor since callchain_append() + * advanced the cursor to the end. + */ + callchain_cursor_commit(&callchain_cursor); + + hists__inc_nr_samples(&evsel->hists, he->filtered); + + return err; +} + +static int +iter_next_cumulative_entry(struct hist_entry_iter *iter, + struct addr_location *al) +{ + struct callchain_cursor_node *node; + + node = callchain_cursor_current(&callchain_cursor); + if (node == NULL) + return 0; + + return fill_callchain_info(al, node, iter->hide_unresolved); +} + +static int +iter_add_next_cumulative_entry(struct hist_entry_iter *iter, + struct addr_location *al) +{ + struct perf_evsel *evsel = iter->evsel; + struct perf_sample *sample = iter->sample; + struct hist_entry **he_cache = iter->priv; + struct hist_entry *he; + struct hist_entry he_tmp = { + .cpu = al->cpu, + .thread = al->thread, + .comm = thread__comm(al->thread), + .ip = al->addr, + .ms = { + .map = al->map, + .sym = al->sym, + }, + .parent = iter->parent, + }; + int i; + struct callchain_cursor cursor; + + callchain_cursor_snapshot(&cursor, &callchain_cursor); + + callchain_cursor_advance(&callchain_cursor); + + /* + * Check if there's duplicate entries in the callchain. + * It's possible that it has cycles or recursive calls. + */ + for (i = 0; i < iter->curr; i++) { + if (hist_entry__cmp(he_cache[i], &he_tmp) == 0) { + /* to avoid calling callback function */ + iter->he = NULL; + return 0; + } + } + + he = __hists__add_entry(&evsel->hists, al, iter->parent, NULL, NULL, + sample->period, sample->weight, + sample->transaction, false); + if (he == NULL) + return -ENOMEM; + + iter->he = he; + he_cache[iter->curr++] = he; + + callchain_append(he->callchain, &cursor, sample->period); + return 0; +} + +static int +iter_finish_cumulative_entry(struct hist_entry_iter *iter, + struct addr_location *al __maybe_unused) +{ + zfree(&iter->priv); + iter->he = NULL; + + return 0; +} + +const struct hist_iter_ops hist_iter_mem = { + .prepare_entry = iter_prepare_mem_entry, + .add_single_entry = iter_add_single_mem_entry, + .next_entry = iter_next_nop_entry, + .add_next_entry = iter_add_next_nop_entry, + .finish_entry = iter_finish_mem_entry, +}; + +const struct hist_iter_ops hist_iter_branch = { + .prepare_entry = iter_prepare_branch_entry, + .add_single_entry = iter_add_single_branch_entry, + .next_entry = iter_next_branch_entry, + .add_next_entry = iter_add_next_branch_entry, + .finish_entry = iter_finish_branch_entry, +}; + +const struct hist_iter_ops hist_iter_normal = { + .prepare_entry = iter_prepare_normal_entry, + .add_single_entry = iter_add_single_normal_entry, + .next_entry = iter_next_nop_entry, + .add_next_entry = iter_add_next_nop_entry, + .finish_entry = iter_finish_normal_entry, +}; + +const struct hist_iter_ops hist_iter_cumulative = { + .prepare_entry = iter_prepare_cumulative_entry, + .add_single_entry = iter_add_single_cumulative_entry, + .next_entry = iter_next_cumulative_entry, + .add_next_entry = iter_add_next_cumulative_entry, + .finish_entry = iter_finish_cumulative_entry, +}; + +int hist_entry_iter__add(struct hist_entry_iter *iter, struct addr_location *al, + struct perf_evsel *evsel, struct perf_sample *sample, + int max_stack_depth, void *arg) +{ + int err, err2; + + err = sample__resolve_callchain(sample, &iter->parent, evsel, al, + max_stack_depth); + if (err) + return err; + + iter->evsel = evsel; + iter->sample = sample; + + err = iter->ops->prepare_entry(iter, al); + if (err) + goto out; + + err = iter->ops->add_single_entry(iter, al); + if (err) + goto out; + + if (iter->he && iter->add_entry_cb) { + err = iter->add_entry_cb(iter, al, true, arg); + if (err) + goto out; + } + + while (iter->ops->next_entry(iter, al)) { + err = iter->ops->add_next_entry(iter, al); + if (err) + break; + + if (iter->he && iter->add_entry_cb) { + err = iter->add_entry_cb(iter, al, false, arg); + if (err) + goto out; + } + } + +out: + err2 = iter->ops->finish_entry(iter, al); + if (!err) + err = err2; + + return err; } int64_t hist_entry__cmp(struct hist_entry *left, struct hist_entry *right) { - struct sort_entry *se; + struct perf_hpp_fmt *fmt; int64_t cmp = 0; - list_for_each_entry(se, &hist_entry__sort_list, list) { - cmp = se->se_cmp(left, right); + perf_hpp__for_each_sort_list(fmt) { + if (perf_hpp__should_skip(fmt)) + continue; + + cmp = fmt->cmp(left, right); if (cmp) break; } @@ -450,15 +904,14 @@ hist_entry__cmp(struct hist_entry *left, struct hist_entry *right) int64_t hist_entry__collapse(struct hist_entry *left, struct hist_entry *right) { - struct sort_entry *se; + struct perf_hpp_fmt *fmt; int64_t cmp = 0; - list_for_each_entry(se, &hist_entry__sort_list, list) { - int64_t (*f)(struct hist_entry *, struct hist_entry *); - - f = se->se_collapse ?: se->se_cmp; + perf_hpp__for_each_sort_list(fmt) { + if (perf_hpp__should_skip(fmt)) + continue; - cmp = f(left, right); + cmp = fmt->collapse(left, right); if (cmp) break; } @@ -470,6 +923,7 @@ void hist_entry__free(struct hist_entry *he) { zfree(&he->branch_info); zfree(&he->mem_info); + zfree(&he->stat_acc); free_srcline(he->srcline); free(he); } @@ -495,6 +949,8 @@ static bool hists__collapse_insert_entry(struct hists *hists __maybe_unused, if (!cmp) { he_stat__add_stat(&iter->stat, &he->stat); + if (symbol_conf.cumulate_callchain) + he_stat__add_stat(iter->stat_acc, he->stat_acc); if (symbol_conf.use_callchain) { callchain_cursor_reset(&callchain_cursor); @@ -571,64 +1027,50 @@ void hists__collapse_resort(struct hists *hists, struct ui_progress *prog) } } -/* - * reverse the map, sort on period. - */ - -static int period_cmp(u64 period_a, u64 period_b) -{ - if (period_a > period_b) - return 1; - if (period_a < period_b) - return -1; - return 0; -} - -static int hist_entry__sort_on_period(struct hist_entry *a, - struct hist_entry *b) +static int hist_entry__sort(struct hist_entry *a, struct hist_entry *b) { - int ret; - int i, nr_members; - struct perf_evsel *evsel; - struct hist_entry *pair; - u64 *periods_a, *periods_b; + struct perf_hpp_fmt *fmt; + int64_t cmp = 0; - ret = period_cmp(a->stat.period, b->stat.period); - if (ret || !symbol_conf.event_group) - return ret; + perf_hpp__for_each_sort_list(fmt) { + if (perf_hpp__should_skip(fmt)) + continue; - evsel = hists_to_evsel(a->hists); - nr_members = evsel->nr_members; - if (nr_members <= 1) - return ret; + cmp = fmt->sort(a, b); + if (cmp) + break; + } - periods_a = zalloc(sizeof(periods_a) * nr_members); - periods_b = zalloc(sizeof(periods_b) * nr_members); + return cmp; +} - if (!periods_a || !periods_b) - goto out; +static void hists__reset_filter_stats(struct hists *hists) +{ + hists->nr_non_filtered_entries = 0; + hists->stats.total_non_filtered_period = 0; +} - list_for_each_entry(pair, &a->pairs.head, pairs.node) { - evsel = hists_to_evsel(pair->hists); - periods_a[perf_evsel__group_idx(evsel)] = pair->stat.period; - } +void hists__reset_stats(struct hists *hists) +{ + hists->nr_entries = 0; + hists->stats.total_period = 0; - list_for_each_entry(pair, &b->pairs.head, pairs.node) { - evsel = hists_to_evsel(pair->hists); - periods_b[perf_evsel__group_idx(evsel)] = pair->stat.period; - } + hists__reset_filter_stats(hists); +} - for (i = 1; i < nr_members; i++) { - ret = period_cmp(periods_a[i], periods_b[i]); - if (ret) - break; - } +static void hists__inc_filter_stats(struct hists *hists, struct hist_entry *h) +{ + hists->nr_non_filtered_entries++; + hists->stats.total_non_filtered_period += h->stat.period; +} -out: - free(periods_a); - free(periods_b); +void hists__inc_stats(struct hists *hists, struct hist_entry *h) +{ + if (!h->filtered) + hists__inc_filter_stats(hists, h); - return ret; + hists->nr_entries++; + hists->stats.total_period += h->stat.period; } static void __hists__insert_output_entry(struct rb_root *entries, @@ -647,7 +1089,7 @@ static void __hists__insert_output_entry(struct rb_root *entries, parent = *p; iter = rb_entry(parent, struct hist_entry, rb_node); - if (hist_entry__sort_on_period(he, iter) > 0) + if (hist_entry__sort(he, iter) > 0) p = &(*p)->rb_left; else p = &(*p)->rb_right; @@ -674,8 +1116,7 @@ void hists__output_resort(struct hists *hists) next = rb_first(root); hists->entries = RB_ROOT; - hists->nr_entries = 0; - hists->stats.total_period = 0; + hists__reset_stats(hists); hists__reset_col_len(hists); while (next) { @@ -683,7 +1124,10 @@ void hists__output_resort(struct hists *hists) next = rb_next(&n->rb_node_in); __hists__insert_output_entry(&hists->entries, n, min_callchain_hits); - hists__inc_nr_entries(hists, n); + hists__inc_stats(hists, n); + + if (!n->filtered) + hists__calc_col_len(hists, n); } } @@ -694,13 +1138,13 @@ static void hists__remove_entry_filter(struct hists *hists, struct hist_entry *h if (h->filtered) return; - ++hists->nr_entries; - if (h->ms.unfolded) - hists->nr_entries += h->nr_rows; + /* force fold unfiltered entry for simplicity */ + h->ms.unfolded = false; h->row_offset = 0; - hists->stats.total_period += h->stat.period; - hists->stats.nr_events[PERF_RECORD_SAMPLE] += h->stat.nr_events; + hists->stats.nr_non_filtered_samples += h->stat.nr_events; + + hists__inc_filter_stats(hists, h); hists__calc_col_len(hists, h); } @@ -721,8 +1165,9 @@ void hists__filter_by_dso(struct hists *hists) { struct rb_node *nd; - hists->nr_entries = hists->stats.total_period = 0; - hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0; + hists->stats.nr_non_filtered_samples = 0; + + hists__reset_filter_stats(hists); hists__reset_col_len(hists); for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { @@ -754,8 +1199,9 @@ void hists__filter_by_thread(struct hists *hists) { struct rb_node *nd; - hists->nr_entries = hists->stats.total_period = 0; - hists->stats.nr_events[PERF_RECORD_SAMPLE] = 0; + hists->stats.nr_non_filtered_samples = 0; + + hists__reset_filter_stats(hists); hists__reset_col_len(hists); for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { @@ -785,8 +1231,9 @@ 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->stats.nr_non_filtered_samples = 0; + + hists__reset_filter_stats(hists); hists__reset_col_len(hists); for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { @@ -810,6 +1257,13 @@ void hists__inc_nr_events(struct hists *hists, u32 type) events_stats__inc(&hists->stats, type); } +void hists__inc_nr_samples(struct hists *hists, bool filtered) +{ + events_stats__inc(&hists->stats, PERF_RECORD_SAMPLE); + if (!filtered) + hists->stats.nr_non_filtered_samples++; +} + static struct hist_entry *hists__add_dummy_entry(struct hists *hists, struct hist_entry *pair) { @@ -841,13 +1295,13 @@ static struct hist_entry *hists__add_dummy_entry(struct hists *hists, p = &(*p)->rb_right; } - he = hist_entry__new(pair); + he = hist_entry__new(pair, true); if (he) { memset(&he->stat, 0, sizeof(he->stat)); he->hists = hists; rb_link_node(&he->rb_node_in, parent, p); rb_insert_color(&he->rb_node_in, root); - hists__inc_nr_entries(hists, he); + hists__inc_stats(hists, he); he->dummy = true; } out: @@ -931,3 +1385,30 @@ int hists__link(struct hists *leader, struct hists *other) return 0; } + +u64 hists__total_period(struct hists *hists) +{ + return symbol_conf.filter_relative ? hists->stats.total_non_filtered_period : + hists->stats.total_period; +} + +int parse_filter_percentage(const struct option *opt __maybe_unused, + const char *arg, int unset __maybe_unused) +{ + if (!strcmp(arg, "relative")) + symbol_conf.filter_relative = true; + else if (!strcmp(arg, "absolute")) + symbol_conf.filter_relative = false; + else + return -1; + + return 0; +} + +int perf_hist_config(const char *var, const char *value) +{ + if (!strcmp(var, "hist.percentage")) + return parse_filter_percentage(NULL, value, 0); + + return 0; +} diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 1f1f513dfe7f..742f49a85725 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -37,9 +37,11 @@ enum hist_filter { */ struct events_stats { u64 total_period; + u64 total_non_filtered_period; u64 total_lost; u64 total_invalid_chains; u32 nr_events[PERF_RECORD_HEADER_MAX]; + u32 nr_non_filtered_samples; u32 nr_lost_warned; u32 nr_unknown_events; u32 nr_invalid_chains; @@ -70,6 +72,7 @@ enum hist_column { HISTC_MEM_TLB, HISTC_MEM_LVL, HISTC_MEM_SNOOP, + HISTC_MEM_DCACHELINE, HISTC_TRANSACTION, HISTC_NR_COLS, /* Last entry */ }; @@ -83,6 +86,7 @@ struct hists { struct rb_root entries; struct rb_root entries_collapsed; u64 nr_entries; + u64 nr_non_filtered_entries; const struct thread *thread_filter; const struct dso *dso_filter; const char *uid_filter_str; @@ -93,12 +97,50 @@ struct hists { u16 col_len[HISTC_NR_COLS]; }; +struct hist_entry_iter; + +struct hist_iter_ops { + int (*prepare_entry)(struct hist_entry_iter *, struct addr_location *); + int (*add_single_entry)(struct hist_entry_iter *, struct addr_location *); + int (*next_entry)(struct hist_entry_iter *, struct addr_location *); + int (*add_next_entry)(struct hist_entry_iter *, struct addr_location *); + int (*finish_entry)(struct hist_entry_iter *, struct addr_location *); +}; + +struct hist_entry_iter { + int total; + int curr; + + bool hide_unresolved; + + struct perf_evsel *evsel; + struct perf_sample *sample; + struct hist_entry *he; + struct symbol *parent; + void *priv; + + const struct hist_iter_ops *ops; + /* user-defined callback function (optional) */ + int (*add_entry_cb)(struct hist_entry_iter *iter, + struct addr_location *al, bool single, void *arg); +}; + +extern const struct hist_iter_ops hist_iter_normal; +extern const struct hist_iter_ops hist_iter_branch; +extern const struct hist_iter_ops hist_iter_mem; +extern const struct hist_iter_ops hist_iter_cumulative; + struct hist_entry *__hists__add_entry(struct hists *hists, struct addr_location *al, struct symbol *parent, struct branch_info *bi, struct mem_info *mi, u64 period, - u64 weight, u64 transaction); + u64 weight, u64 transaction, + bool sample_self); +int hist_entry_iter__add(struct hist_entry_iter *iter, struct addr_location *al, + struct perf_evsel *evsel, struct perf_sample *sample, + int max_stack_depth, void *arg); + int64_t hist_entry__cmp(struct hist_entry *left, struct hist_entry *right); int64_t hist_entry__collapse(struct hist_entry *left, struct hist_entry *right); int hist_entry__transaction_len(void); @@ -112,8 +154,11 @@ void hists__collapse_resort(struct hists *hists, struct ui_progress *prog); void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel); void hists__output_recalc_col_len(struct hists *hists, int max_rows); -void hists__inc_nr_entries(struct hists *hists, struct hist_entry *h); +u64 hists__total_period(struct hists *hists); +void hists__reset_stats(struct hists *hists); +void hists__inc_stats(struct hists *hists, struct hist_entry *h); void hists__inc_nr_events(struct hists *hists, u32 type); +void hists__inc_nr_samples(struct hists *hists, bool filtered); void events_stats__inc(struct events_stats *stats, u32 type); size_t events_stats__fprintf(struct events_stats *stats, FILE *fp); @@ -124,6 +169,12 @@ void hists__filter_by_dso(struct hists *hists); void hists__filter_by_thread(struct hists *hists); void hists__filter_by_symbol(struct hists *hists); +static inline bool hists__has_filter(struct hists *hists) +{ + return hists->thread_filter || hists->dso_filter || + hists->symbol_filter_str; +} + u16 hists__col_len(struct hists *hists, enum hist_column col); void hists__set_col_len(struct hists *hists, enum hist_column col, u16 len); bool hists__new_col_len(struct hists *hists, enum hist_column col, u16 len); @@ -149,15 +200,30 @@ struct perf_hpp_fmt { struct hist_entry *he); int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hist_entry *he); + int64_t (*cmp)(struct hist_entry *a, struct hist_entry *b); + int64_t (*collapse)(struct hist_entry *a, struct hist_entry *b); + int64_t (*sort)(struct hist_entry *a, struct hist_entry *b); struct list_head list; + struct list_head sort_list; + bool elide; }; extern struct list_head perf_hpp__list; +extern struct list_head perf_hpp__sort_list; #define perf_hpp__for_each_format(format) \ list_for_each_entry(format, &perf_hpp__list, list) +#define perf_hpp__for_each_format_safe(format, tmp) \ + list_for_each_entry_safe(format, tmp, &perf_hpp__list, list) + +#define perf_hpp__for_each_sort_list(format) \ + list_for_each_entry(format, &perf_hpp__sort_list, sort_list) + +#define perf_hpp__for_each_sort_list_safe(format, tmp) \ + list_for_each_entry_safe(format, tmp, &perf_hpp__sort_list, sort_list) + extern struct perf_hpp_fmt perf_hpp__format[]; enum { @@ -167,6 +233,7 @@ enum { PERF_HPP__OVERHEAD_US, PERF_HPP__OVERHEAD_GUEST_SYS, PERF_HPP__OVERHEAD_GUEST_US, + PERF_HPP__OVERHEAD_ACC, PERF_HPP__SAMPLES, PERF_HPP__PERIOD, @@ -175,15 +242,36 @@ enum { void perf_hpp__init(void); void perf_hpp__column_register(struct perf_hpp_fmt *format); +void perf_hpp__column_unregister(struct perf_hpp_fmt *format); void perf_hpp__column_enable(unsigned col); +void perf_hpp__column_disable(unsigned col); +void perf_hpp__cancel_cumulate(void); + +void perf_hpp__register_sort_field(struct perf_hpp_fmt *format); +void perf_hpp__setup_output_field(void); +void perf_hpp__reset_output_field(void); +void perf_hpp__append_sort_keys(void); + +bool perf_hpp__is_sort_entry(struct perf_hpp_fmt *format); +bool perf_hpp__same_sort_entry(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b); + +static inline bool perf_hpp__should_skip(struct perf_hpp_fmt *format) +{ + return format->elide; +} + +void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists); typedef u64 (*hpp_field_fn)(struct hist_entry *he); typedef int (*hpp_callback_fn)(struct perf_hpp *hpp, bool front); typedef int (*hpp_snprint_fn)(struct perf_hpp *hpp, const char *fmt, ...); int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, - hpp_field_fn get_field, hpp_callback_fn callback, - const char *fmt, hpp_snprint_fn print_fn, bool fmt_percent); + hpp_field_fn get_field, const char *fmt, + hpp_snprint_fn print_fn, bool fmt_percent); +int __hpp__fmt_acc(struct perf_hpp *hpp, struct hist_entry *he, + hpp_field_fn get_field, const char *fmt, + hpp_snprint_fn print_fn, bool fmt_percent); static inline void advance_hpp(struct perf_hpp *hpp, int inc) { @@ -250,4 +338,10 @@ static inline int script_browse(const char *script_opt __maybe_unused) #endif unsigned int hists__sort_list_width(struct hists *hists); + +struct option; +int parse_filter_percentage(const struct option *opt __maybe_unused, + const char *arg, int unset __maybe_unused); +int perf_hist_config(const char *var, const char *value); + #endif /* __PERF_HIST_H */ diff --git a/tools/perf/util/include/linux/bitmap.h b/tools/perf/util/include/linux/bitmap.h index bb162e40c76c..01ffd12dc791 100644 --- a/tools/perf/util/include/linux/bitmap.h +++ b/tools/perf/util/include/linux/bitmap.h @@ -4,6 +4,9 @@ #include <string.h> #include <linux/bitops.h> +#define DECLARE_BITMAP(name,bits) \ + unsigned long name[BITS_TO_LONGS(bits)] + 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); diff --git a/tools/perf/util/include/linux/export.h b/tools/perf/util/include/linux/export.h deleted file mode 100644 index b43e2dc21e04..000000000000 --- a/tools/perf/util/include/linux/export.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef PERF_LINUX_MODULE_H -#define PERF_LINUX_MODULE_H - -#define EXPORT_SYMBOL(name) - -#endif diff --git a/tools/perf/util/include/linux/kernel.h b/tools/perf/util/include/linux/kernel.h index 9844c31b7c2b..09e8e7aea7c6 100644 --- a/tools/perf/util/include/linux/kernel.h +++ b/tools/perf/util/include/linux/kernel.h @@ -94,27 +94,6 @@ static inline int scnprintf(char * buf, size_t size, const char * fmt, ...) return (i >= ssize) ? (ssize - 1) : i; } -int eprintf(int level, - const char *fmt, ...) __attribute__((format(printf, 2, 3))); - -#ifndef pr_fmt -#define pr_fmt(fmt) fmt -#endif - -#define pr_err(fmt, ...) \ - eprintf(0, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_warning(fmt, ...) \ - eprintf(0, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_info(fmt, ...) \ - eprintf(0, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_debug(fmt, ...) \ - eprintf(1, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_debugN(n, fmt, ...) \ - eprintf(n, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_debug2(fmt, ...) pr_debugN(2, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_debug3(fmt, ...) pr_debugN(3, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_debug4(fmt, ...) pr_debugN(4, pr_fmt(fmt), ##__VA_ARGS__) - /* * This looks more complex than it should be. But we need to * get the type for the ~ right in round_down (it needs to be diff --git a/tools/perf/util/include/linux/list.h b/tools/perf/util/include/linux/list.h index bfe0a2afd0d2..76ddbc726343 100644 --- a/tools/perf/util/include/linux/list.h +++ b/tools/perf/util/include/linux/list.h @@ -1,4 +1,5 @@ #include <linux/kernel.h> +#include <linux/types.h> #include "../../../../include/linux/list.h" diff --git a/tools/perf/util/include/linux/types.h b/tools/perf/util/include/linux/types.h deleted file mode 100644 index eb464786c084..000000000000 --- a/tools/perf/util/include/linux/types.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef _PERF_LINUX_TYPES_H_ -#define _PERF_LINUX_TYPES_H_ - -#include <asm/types.h> - -#ifndef __bitwise -#define __bitwise -#endif - -#ifndef __le32 -typedef __u32 __bitwise __le32; -#endif - -#define DECLARE_BITMAP(name,bits) \ - unsigned long name[BITS_TO_LONGS(bits)] - -struct list_head { - struct list_head *next, *prev; -}; - -struct hlist_head { - struct hlist_node *first; -}; - -struct hlist_node { - struct hlist_node *next, **pprev; -}; - -#endif diff --git a/tools/perf/util/kvm-stat.h b/tools/perf/util/kvm-stat.h new file mode 100644 index 000000000000..0b5a8cd2ee79 --- /dev/null +++ b/tools/perf/util/kvm-stat.h @@ -0,0 +1,140 @@ +#ifndef __PERF_KVM_STAT_H +#define __PERF_KVM_STAT_H + +#include "../perf.h" +#include "evsel.h" +#include "evlist.h" +#include "session.h" +#include "tool.h" +#include "stat.h" + +struct event_key { + #define INVALID_KEY (~0ULL) + u64 key; + int info; + struct exit_reasons_table *exit_reasons; +}; + +struct kvm_event_stats { + u64 time; + struct stats stats; +}; + +struct kvm_event { + struct list_head hash_entry; + struct rb_node rb; + + struct event_key key; + + struct kvm_event_stats total; + + #define DEFAULT_VCPU_NUM 8 + int max_vcpu; + struct kvm_event_stats *vcpu; +}; + +typedef int (*key_cmp_fun)(struct kvm_event*, struct kvm_event*, int); + +struct kvm_event_key { + const char *name; + key_cmp_fun key; +}; + +struct perf_kvm_stat; + +struct child_event_ops { + void (*get_key)(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key); + const char *name; +}; + +struct kvm_events_ops { + bool (*is_begin_event)(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key); + bool (*is_end_event)(struct perf_evsel *evsel, + struct perf_sample *sample, struct event_key *key); + struct child_event_ops *child_ops; + void (*decode_key)(struct perf_kvm_stat *kvm, struct event_key *key, + char *decode); + const char *name; +}; + +struct exit_reasons_table { + unsigned long exit_code; + const char *reason; +}; + +#define EVENTS_BITS 12 +#define EVENTS_CACHE_SIZE (1UL << EVENTS_BITS) + +struct perf_kvm_stat { + struct perf_tool tool; + struct record_opts opts; + struct perf_evlist *evlist; + struct perf_session *session; + + const char *file_name; + const char *report_event; + const char *sort_key; + int trace_vcpu; + + struct exit_reasons_table *exit_reasons; + const char *exit_reasons_isa; + + struct kvm_events_ops *events_ops; + key_cmp_fun compare; + struct list_head kvm_events_cache[EVENTS_CACHE_SIZE]; + + u64 total_time; + u64 total_count; + u64 lost_events; + u64 duration; + + const char *pid_str; + struct intlist *pid_list; + + struct rb_root result; + + int timerfd; + unsigned int display_time; + bool live; +}; + +struct kvm_reg_events_ops { + const char *name; + struct kvm_events_ops *ops; +}; + +void exit_event_get_key(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key); +bool exit_event_begin(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key); +bool exit_event_end(struct perf_evsel *evsel, + struct perf_sample *sample, + struct event_key *key); +void exit_event_decode_key(struct perf_kvm_stat *kvm, + struct event_key *key, + char *decode); + +bool kvm_exit_event(struct perf_evsel *evsel); +bool kvm_entry_event(struct perf_evsel *evsel); + +#define define_exit_reasons_table(name, symbols) \ + static struct exit_reasons_table name[] = { \ + symbols, { -1, NULL } \ + } + +/* + * arch specific callbacks and data structures + */ +int cpu_isa_init(struct perf_kvm_stat *kvm, const char *cpuid); + +extern const char * const kvm_events_tp[]; +extern struct kvm_reg_events_ops kvm_reg_events_ops[]; +extern const char * const kvm_skip_events[]; + +#endif /* __PERF_KVM_STAT_H */ diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 27c2a5efe450..16bba9fff2c8 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -8,6 +8,7 @@ #include "sort.h" #include "strlist.h" #include "thread.h" +#include "vdso.h" #include <stdbool.h> #include <symbol/kallsyms.h> #include "unwind.h" @@ -23,6 +24,8 @@ int machine__init(struct machine *machine, const char *root_dir, pid_t pid) INIT_LIST_HEAD(&machine->dead_threads); machine->last_match = NULL; + machine->vdso_info = NULL; + machine->kmaps.machine = machine; machine->pid = pid; @@ -34,7 +37,7 @@ int machine__init(struct machine *machine, const char *root_dir, pid_t pid) return -ENOMEM; if (pid != HOST_KERNEL_ID) { - struct thread *thread = machine__findnew_thread(machine, 0, + struct thread *thread = machine__findnew_thread(machine, -1, pid); char comm[64]; @@ -45,6 +48,8 @@ int machine__init(struct machine *machine, const char *root_dir, pid_t pid) thread__set_comm(thread, comm, 0); } + machine->current_tid = NULL; + return 0; } @@ -103,7 +108,9 @@ void machine__exit(struct machine *machine) map_groups__exit(&machine->kmaps); dsos__delete(&machine->user_dsos); dsos__delete(&machine->kernel_dsos); + vdso__exit(machine); zfree(&machine->root_dir); + zfree(&machine->current_tid); } void machine__delete(struct machine *machine) @@ -272,6 +279,52 @@ void machines__set_id_hdr_size(struct machines *machines, u16 id_hdr_size) return; } +static void machine__update_thread_pid(struct machine *machine, + struct thread *th, pid_t pid) +{ + struct thread *leader; + + if (pid == th->pid_ || pid == -1 || th->pid_ != -1) + return; + + th->pid_ = pid; + + if (th->pid_ == th->tid) + return; + + leader = machine__findnew_thread(machine, th->pid_, th->pid_); + if (!leader) + goto out_err; + + if (!leader->mg) + leader->mg = map_groups__new(); + + if (!leader->mg) + goto out_err; + + if (th->mg == leader->mg) + return; + + if (th->mg) { + /* + * Maps are created from MMAP events which provide the pid and + * tid. Consequently there never should be any maps on a thread + * with an unknown pid. Just print an error if there are. + */ + if (!map_groups__empty(th->mg)) + pr_err("Discarding thread maps for %d:%d\n", + th->pid_, th->tid); + map_groups__delete(th->mg); + } + + th->mg = map_groups__get(leader->mg); + + return; + +out_err: + pr_err("Failed to join map groups for %d:%d\n", th->pid_, th->tid); +} + static struct thread *__machine__findnew_thread(struct machine *machine, pid_t pid, pid_t tid, bool create) @@ -285,10 +338,10 @@ static struct thread *__machine__findnew_thread(struct machine *machine, * so most of the time we dont have to look up * the full rbtree: */ - if (machine->last_match && machine->last_match->tid == tid) { - if (pid && pid != machine->last_match->pid_) - machine->last_match->pid_ = pid; - return machine->last_match; + th = machine->last_match; + if (th && th->tid == tid) { + machine__update_thread_pid(machine, th, pid); + return th; } while (*p != NULL) { @@ -297,8 +350,7 @@ static struct thread *__machine__findnew_thread(struct machine *machine, if (th->tid == tid) { machine->last_match = th; - if (pid && pid != th->pid_) - th->pid_ = pid; + machine__update_thread_pid(machine, th, pid); return th; } @@ -316,6 +368,19 @@ static struct thread *__machine__findnew_thread(struct machine *machine, rb_link_node(&th->rb_node, parent, p); rb_insert_color(&th->rb_node, &machine->threads); machine->last_match = th; + + /* + * We have to initialize map_groups separately + * after rb tree is updated. + * + * The reason is that we call machine__findnew_thread + * within thread__init_map_groups to find the thread + * leader and that would screwed the rb tree. + */ + if (thread__init_map_groups(th, machine)) { + thread__delete(th); + return NULL; + } } return th; @@ -485,18 +550,6 @@ struct process_args { u64 start; }; -static int symbol__in_kernel(void *arg, const char *name, - char type __maybe_unused, u64 start) -{ - struct process_args *args = arg; - - if (strchr(name, '[')) - return 0; - - args->start = start; - return 1; -} - static void machine__get_kallsyms_filename(struct machine *machine, char *buf, size_t bufsz) { @@ -506,27 +559,41 @@ static void machine__get_kallsyms_filename(struct machine *machine, char *buf, scnprintf(buf, bufsz, "%s/proc/kallsyms", machine->root_dir); } -/* Figure out the start address of kernel map from /proc/kallsyms */ -static u64 machine__get_kernel_start_addr(struct machine *machine) +const char *ref_reloc_sym_names[] = {"_text", "_stext", NULL}; + +/* Figure out the start address of kernel map from /proc/kallsyms. + * Returns the name of the start symbol in *symbol_name. Pass in NULL as + * symbol_name if it's not that important. + */ +static u64 machine__get_kernel_start_addr(struct machine *machine, + const char **symbol_name) { char filename[PATH_MAX]; - struct process_args args; + int i; + const char *name; + u64 addr = 0; machine__get_kallsyms_filename(machine, filename, PATH_MAX); if (symbol__restricted_filename(filename, "/proc/kallsyms")) return 0; - if (kallsyms__parse(filename, &args, symbol__in_kernel) <= 0) - return 0; + for (i = 0; (name = ref_reloc_sym_names[i]) != NULL; i++) { + addr = kallsyms__get_function_start(filename, name); + if (addr) + break; + } - return args.start; + if (symbol_name) + *symbol_name = name; + + return addr; } int __machine__create_kernel_maps(struct machine *machine, struct dso *kernel) { enum map_type type; - u64 start = machine__get_kernel_start_addr(machine); + u64 start = machine__get_kernel_start_addr(machine, NULL); for (type = 0; type < MAP__NR_TYPES; ++type) { struct kmap *kmap; @@ -841,23 +908,11 @@ static int machine__create_modules(struct machine *machine) return 0; } -const char *ref_reloc_sym_names[] = {"_text", "_stext", NULL}; - int machine__create_kernel_maps(struct machine *machine) { struct dso *kernel = machine__get_kernel(machine); - char filename[PATH_MAX]; const char *name; - u64 addr = 0; - int i; - - machine__get_kallsyms_filename(machine, filename, PATH_MAX); - - for (i = 0; (name = ref_reloc_sym_names[i]) != NULL; i++) { - addr = kallsyms__get_function_start(filename, name); - if (addr) - break; - } + u64 addr = machine__get_kernel_start_addr(machine, &name); if (!addr) return -1; @@ -1044,12 +1099,14 @@ int machine__process_mmap2_event(struct machine *machine, else type = MAP__FUNCTION; - map = map__new(&machine->user_dsos, event->mmap2.start, + map = map__new(machine, event->mmap2.start, event->mmap2.len, event->mmap2.pgoff, event->mmap2.pid, event->mmap2.maj, event->mmap2.min, event->mmap2.ino, event->mmap2.ino_generation, - event->mmap2.filename, type); + event->mmap2.prot, + event->mmap2.flags, + event->mmap2.filename, type, thread); if (map == NULL) goto out_problem; @@ -1092,11 +1149,11 @@ int machine__process_mmap_event(struct machine *machine, union perf_event *event else type = MAP__FUNCTION; - map = map__new(&machine->user_dsos, event->mmap.start, + map = map__new(machine, event->mmap.start, event->mmap.len, event->mmap.pgoff, - event->mmap.pid, 0, 0, 0, 0, + event->mmap.pid, 0, 0, 0, 0, 0, 0, event->mmap.filename, - type); + type, thread); if (map == NULL) goto out_problem; @@ -1278,7 +1335,9 @@ static int machine__resolve_callchain_sample(struct machine *machine, u8 cpumode = PERF_RECORD_MISC_USER; int chain_nr = min(max_stack, (int)chain->nr); int i; + int j; int err; + int skip_idx __maybe_unused; callchain_cursor_reset(&callchain_cursor); @@ -1287,14 +1346,26 @@ static int machine__resolve_callchain_sample(struct machine *machine, return 0; } + /* + * Based on DWARF debug information, some architectures skip + * a callchain entry saved by the kernel. + */ + skip_idx = arch_skip_callchain_idx(machine, thread, chain); + for (i = 0; i < chain_nr; i++) { u64 ip; struct addr_location al; if (callchain_param.order == ORDER_CALLEE) - ip = chain->ips[i]; + j = i; else - ip = chain->ips[chain->nr - i - 1]; + j = chain->nr - i - 1; + +#ifdef HAVE_SKIP_CALLCHAIN_IDX + if (j == skip_idx) + continue; +#endif + ip = chain->ips[j]; if (ip >= PERF_CONTEXT_MAX) { switch (ip) { @@ -1417,3 +1488,46 @@ int __machine__synthesize_threads(struct machine *machine, struct perf_tool *too /* command specified */ return 0; } + +pid_t machine__get_current_tid(struct machine *machine, int cpu) +{ + if (cpu < 0 || cpu >= MAX_NR_CPUS || !machine->current_tid) + return -1; + + return machine->current_tid[cpu]; +} + +int machine__set_current_tid(struct machine *machine, int cpu, pid_t pid, + pid_t tid) +{ + struct thread *thread; + + if (cpu < 0) + return -EINVAL; + + if (!machine->current_tid) { + int i; + + machine->current_tid = calloc(MAX_NR_CPUS, sizeof(pid_t)); + if (!machine->current_tid) + return -ENOMEM; + for (i = 0; i < MAX_NR_CPUS; i++) + machine->current_tid[i] = -1; + } + + if (cpu >= MAX_NR_CPUS) { + pr_err("Requested CPU %d too large. ", cpu); + pr_err("Consider raising MAX_NR_CPUS\n"); + return -EINVAL; + } + + machine->current_tid[cpu] = tid; + + thread = machine__findnew_thread(machine, pid, tid); + if (!thread) + return -ENOMEM; + + thread->cpu = cpu; + + return 0; +} diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h index c8c74a119398..b972824e6294 100644 --- a/tools/perf/util/machine.h +++ b/tools/perf/util/machine.h @@ -20,6 +20,8 @@ union perf_event; extern const char *ref_reloc_sym_names[]; +struct vdso_info; + struct machine { struct rb_node rb_node; pid_t pid; @@ -28,11 +30,13 @@ struct machine { struct rb_root threads; struct list_head dead_threads; struct thread *last_match; + struct vdso_info *vdso_info; struct list_head user_dsos; struct list_head kernel_dsos; struct map_groups kmaps; struct map *vmlinux_maps[MAP__NR_TYPES]; symbol_filter_t symbol_filter; + pid_t *current_tid; }; static inline @@ -191,4 +195,8 @@ int machine__synthesize_threads(struct machine *machine, struct target *target, perf_event__process, data_mmap); } +pid_t machine__get_current_tid(struct machine *machine, int cpu); +int machine__set_current_tid(struct machine *machine, int cpu, pid_t pid, + pid_t tid); + #endif /* __PERF_MACHINE_H */ diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index 39cd2d0faff6..31b8905dd863 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c @@ -12,6 +12,8 @@ #include "vdso.h" #include "build-id.h" #include "util.h" +#include "debug.h" +#include "machine.h" #include <linux/string.h> const char *map_type__name[MAP__NR_TYPES] = { @@ -32,6 +34,93 @@ static inline int is_no_dso_memory(const char *filename) !strcmp(filename, "[heap]"); } +static inline int is_android_lib(const char *filename) +{ + return !strncmp(filename, "/data/app-lib", 13) || + !strncmp(filename, "/system/lib", 11); +} + +static inline bool replace_android_lib(const char *filename, char *newfilename) +{ + const char *libname; + char *app_abi; + size_t app_abi_length, new_length; + size_t lib_length = 0; + + libname = strrchr(filename, '/'); + if (libname) + lib_length = strlen(libname); + + app_abi = getenv("APP_ABI"); + if (!app_abi) + return false; + + app_abi_length = strlen(app_abi); + + if (!strncmp(filename, "/data/app-lib", 13)) { + char *apk_path; + + if (!app_abi_length) + return false; + + new_length = 7 + app_abi_length + lib_length; + + apk_path = getenv("APK_PATH"); + if (apk_path) { + new_length += strlen(apk_path) + 1; + if (new_length > PATH_MAX) + return false; + snprintf(newfilename, new_length, + "%s/libs/%s/%s", apk_path, app_abi, libname); + } else { + if (new_length > PATH_MAX) + return false; + snprintf(newfilename, new_length, + "libs/%s/%s", app_abi, libname); + } + return true; + } + + if (!strncmp(filename, "/system/lib/", 11)) { + char *ndk, *app; + const char *arch; + size_t ndk_length; + size_t app_length; + + ndk = getenv("NDK_ROOT"); + app = getenv("APP_PLATFORM"); + + if (!(ndk && app)) + return false; + + ndk_length = strlen(ndk); + app_length = strlen(app); + + if (!(ndk_length && app_length && app_abi_length)) + return false; + + arch = !strncmp(app_abi, "arm", 3) ? "arm" : + !strncmp(app_abi, "mips", 4) ? "mips" : + !strncmp(app_abi, "x86", 3) ? "x86" : NULL; + + if (!arch) + return false; + + new_length = 27 + ndk_length + + app_length + lib_length + + strlen(arch); + + if (new_length > PATH_MAX) + return false; + snprintf(newfilename, new_length, + "%s/platforms/%s/arch-%s/usr/lib/%s", + ndk, app, arch, libname); + + return true; + } + return false; +} + void map__init(struct map *map, enum map_type type, u64 start, u64 end, u64 pgoff, struct dso *dso) { @@ -49,18 +138,19 @@ void map__init(struct map *map, enum map_type type, map->erange_warned = false; } -struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, +struct map *map__new(struct machine *machine, u64 start, u64 len, u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino, - u64 ino_gen, char *filename, - enum map_type type) + u64 ino_gen, u32 prot, u32 flags, char *filename, + enum map_type type, struct thread *thread) { struct map *map = malloc(sizeof(*map)); if (map != NULL) { char newfilename[PATH_MAX]; struct dso *dso; - int anon, no_dso, vdso; + int anon, no_dso, vdso, android; + android = is_android_lib(filename); anon = is_anon_memory(filename); vdso = is_vdso_map(filename); no_dso = is_no_dso_memory(filename); @@ -69,17 +159,24 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, map->min = d_min; map->ino = ino; map->ino_generation = ino_gen; + map->prot = prot; + map->flags = flags; if ((anon || no_dso) && type == MAP__FUNCTION) { snprintf(newfilename, sizeof(newfilename), "/tmp/perf-%d.map", pid); filename = newfilename; } + if (android) { + if (replace_android_lib(filename, newfilename)) + filename = newfilename; + } + if (vdso) { pgoff = 0; - dso = vdso__dso_findnew(dsos__list); + dso = vdso__dso_findnew(machine, thread); } else - dso = __dsos__findnew(dsos__list, filename); + dso = __dsos__findnew(&machine->user_dsos, filename); if (dso == NULL) goto out_delete; @@ -323,6 +420,7 @@ void map_groups__init(struct map_groups *mg) INIT_LIST_HEAD(&mg->removed_maps[i]); } mg->machine = NULL; + mg->refcnt = 1; } static void maps__delete(struct rb_root *maps) @@ -358,6 +456,42 @@ void map_groups__exit(struct map_groups *mg) } } +bool map_groups__empty(struct map_groups *mg) +{ + int i; + + for (i = 0; i < MAP__NR_TYPES; ++i) { + if (maps__first(&mg->maps[i])) + return false; + if (!list_empty(&mg->removed_maps[i])) + return false; + } + + return true; +} + +struct map_groups *map_groups__new(void) +{ + struct map_groups *mg = malloc(sizeof(*mg)); + + if (mg != NULL) + map_groups__init(mg); + + return mg; +} + +void map_groups__delete(struct map_groups *mg) +{ + map_groups__exit(mg); + free(mg); +} + +void map_groups__put(struct map_groups *mg) +{ + if (--mg->refcnt == 0) + map_groups__delete(mg); +} + void map_groups__flush(struct map_groups *mg) { int type; @@ -436,8 +570,8 @@ int map_groups__find_ams(struct addr_map_symbol *ams, symbol_filter_t filter) return ams->sym ? 0 : -1; } -size_t __map_groups__fprintf_maps(struct map_groups *mg, - enum map_type type, int verbose, FILE *fp) +size_t __map_groups__fprintf_maps(struct map_groups *mg, enum map_type type, + FILE *fp) { size_t printed = fprintf(fp, "%s:\n", map_type__name[type]); struct rb_node *nd; @@ -455,17 +589,16 @@ size_t __map_groups__fprintf_maps(struct map_groups *mg, return printed; } -size_t map_groups__fprintf_maps(struct map_groups *mg, int verbose, FILE *fp) +static size_t map_groups__fprintf_maps(struct map_groups *mg, FILE *fp) { size_t printed = 0, i; for (i = 0; i < MAP__NR_TYPES; ++i) - printed += __map_groups__fprintf_maps(mg, i, verbose, fp); + printed += __map_groups__fprintf_maps(mg, i, fp); return printed; } static size_t __map_groups__fprintf_removed_maps(struct map_groups *mg, - enum map_type type, - int verbose, FILE *fp) + enum map_type type, FILE *fp) { struct map *pos; size_t printed = 0; @@ -482,23 +615,23 @@ static size_t __map_groups__fprintf_removed_maps(struct map_groups *mg, } static size_t map_groups__fprintf_removed_maps(struct map_groups *mg, - int verbose, FILE *fp) + FILE *fp) { size_t printed = 0, i; for (i = 0; i < MAP__NR_TYPES; ++i) - printed += __map_groups__fprintf_removed_maps(mg, i, verbose, fp); + printed += __map_groups__fprintf_removed_maps(mg, i, fp); return printed; } -size_t map_groups__fprintf(struct map_groups *mg, int verbose, FILE *fp) +size_t map_groups__fprintf(struct map_groups *mg, FILE *fp) { - size_t printed = map_groups__fprintf_maps(mg, verbose, fp); + size_t printed = map_groups__fprintf_maps(mg, fp); printed += fprintf(fp, "Removed maps:\n"); - return printed + map_groups__fprintf_removed_maps(mg, verbose, fp); + return printed + map_groups__fprintf_removed_maps(mg, fp); } int map_groups__fixup_overlappings(struct map_groups *mg, struct map *map, - int verbose, FILE *fp) + FILE *fp) { struct rb_root *root = &mg->maps[map->type]; struct rb_node *next = rb_first(root); diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index f00f058afb3b..2f83954af050 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -6,7 +6,7 @@ #include <linux/rbtree.h> #include <stdio.h> #include <stdbool.h> -#include "types.h" +#include <linux/types.h> enum map_type { MAP__FUNCTION = 0, @@ -35,6 +35,8 @@ struct map { bool referenced; bool erange_warned; u32 priv; + u32 prot; + u32 flags; u64 pgoff; u64 reloc; u32 maj, min; /* only valid for MMAP2 record */ @@ -59,8 +61,21 @@ struct map_groups { struct rb_root maps[MAP__NR_TYPES]; struct list_head removed_maps[MAP__NR_TYPES]; struct machine *machine; + int refcnt; }; +struct map_groups *map_groups__new(void); +void map_groups__delete(struct map_groups *mg); +bool map_groups__empty(struct map_groups *mg); + +static inline struct map_groups *map_groups__get(struct map_groups *mg) +{ + ++mg->refcnt; + return mg; +} + +void map_groups__put(struct map_groups *mg); + static inline struct kmap *map__kmap(struct map *map) { return (struct kmap *)(map + 1); @@ -89,6 +104,7 @@ u64 map__rip_2objdump(struct map *map, u64 rip); u64 map__objdump_2mem(struct map *map, u64 ip); struct symbol; +struct thread; /* map__for_each_symbol - iterate over the symbols in the given map * @@ -104,10 +120,10 @@ typedef int (*symbol_filter_t)(struct map *map, struct symbol *sym); void map__init(struct map *map, enum map_type type, u64 start, u64 end, u64 pgoff, struct dso *dso); -struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, +struct map *map__new(struct machine *machine, u64 start, u64 len, u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino, - u64 ino_gen, - char *filename, enum map_type type); + u64 ino_gen, u32 prot, u32 flags, + char *filename, enum map_type type, struct thread *thread); struct map *map__new2(u64 start, struct dso *dso, enum map_type type); void map__delete(struct map *map); struct map *map__clone(struct map *map); @@ -127,8 +143,8 @@ void map__fixup_end(struct map *map); void map__reloc_vmlinux(struct map *map); -size_t __map_groups__fprintf_maps(struct map_groups *mg, - enum map_type type, int verbose, FILE *fp); +size_t __map_groups__fprintf_maps(struct map_groups *mg, enum map_type type, + FILE *fp); void maps__insert(struct rb_root *maps, struct map *map); void maps__remove(struct rb_root *maps, struct map *map); struct map *maps__find(struct rb_root *maps, u64 addr); @@ -138,8 +154,7 @@ void map_groups__init(struct map_groups *mg); void map_groups__exit(struct map_groups *mg); int map_groups__clone(struct map_groups *mg, struct map_groups *parent, enum map_type type); -size_t map_groups__fprintf(struct map_groups *mg, int verbose, FILE *fp); -size_t map_groups__fprintf_maps(struct map_groups *mg, int verbose, FILE *fp); +size_t map_groups__fprintf(struct map_groups *mg, FILE *fp); int maps__set_kallsyms_ref_reloc_sym(struct map **maps, const char *symbol_name, u64 addr); @@ -196,7 +211,7 @@ struct symbol *map_groups__find_function_by_name(struct map_groups *mg, } int map_groups__fixup_overlappings(struct map_groups *mg, struct map *map, - int verbose, FILE *fp); + FILE *fp); struct map *map_groups__find_by_name(struct map_groups *mg, enum map_type type, const char *name); diff --git a/tools/perf/util/pager.c b/tools/perf/util/pager.c index 3322b8446e89..31ee02d4e988 100644 --- a/tools/perf/util/pager.c +++ b/tools/perf/util/pager.c @@ -57,13 +57,13 @@ void setup_pager(void) } if (!pager) pager = getenv("PAGER"); - if (!pager) { - if (!access("/usr/bin/pager", X_OK)) - pager = "/usr/bin/pager"; - } + if (!(pager || access("/usr/bin/pager", X_OK))) + pager = "/usr/bin/pager"; + if (!(pager || access("/usr/bin/less", X_OK))) + pager = "/usr/bin/less"; if (!pager) - pager = "less"; - else if (!*pager || !strcmp(pager, "cat")) + pager = "cat"; + if (!*pager || !strcmp(pager, "cat")) return; spawned_pager = 1; /* means we are emitting to terminal */ diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h index f1cb4c4b3c70..df094b4ed5ed 100644 --- a/tools/perf/util/parse-events.h +++ b/tools/perf/util/parse-events.h @@ -6,9 +6,8 @@ #include <linux/list.h> #include <stdbool.h> -#include "types.h" +#include <linux/types.h> #include <linux/perf_event.h> -#include "types.h" struct list_head; struct perf_evsel; diff --git a/tools/perf/util/parse-events.y b/tools/perf/util/parse-events.y index 4eb67ec333f1..0bc87ba46bf3 100644 --- a/tools/perf/util/parse-events.y +++ b/tools/perf/util/parse-events.y @@ -9,7 +9,7 @@ #include <linux/compiler.h> #include <linux/list.h> -#include "types.h" +#include <linux/types.h> #include "util.h" #include "parse-events.h" #include "parse-events-bison.h" @@ -299,6 +299,18 @@ PE_PREFIX_MEM PE_VALUE sep_dc } event_legacy_tracepoint: +PE_NAME '-' PE_NAME ':' PE_NAME +{ + struct parse_events_evlist *data = _data; + struct list_head *list; + char sys_name[128]; + snprintf(&sys_name, 128, "%s-%s", $1, $3); + + ALLOC_LIST(list); + ABORT_ON(parse_events_add_tracepoint(list, &data->idx, &sys_name, $5)); + $$ = list; +} +| PE_NAME ':' PE_NAME { struct parse_events_evlist *data = _data; diff --git a/tools/perf/util/parse-options.h b/tools/perf/util/parse-options.h index d8dac8ac5f37..b59ba858e73d 100644 --- a/tools/perf/util/parse-options.h +++ b/tools/perf/util/parse-options.h @@ -98,6 +98,7 @@ struct option { parse_opt_cb *callback; intptr_t defval; bool *set; + void *data; }; #define check_vtype(v, type) ( BUILD_BUG_ON_ZERO(!__builtin_types_compatible_p(typeof(v), type)) + v ) @@ -131,6 +132,10 @@ struct option { { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l),\ .value = (v), (a), .help = (h), .callback = (f), .defval = (intptr_t)d,\ .flags = PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NOARG} +#define OPT_CALLBACK_OPTARG(s, l, v, d, a, h, f) \ + { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), \ + .value = (v), (a), .help = (h), .callback = (f), \ + .flags = PARSE_OPT_OPTARG, .data = (d) } /* parse_options() will filter out the processed options and leave the * non-option argments in argv[]. diff --git a/tools/perf/util/perf_regs.c b/tools/perf/util/perf_regs.c index a3539ef30b15..43168fb0d9a2 100644 --- a/tools/perf/util/perf_regs.c +++ b/tools/perf/util/perf_regs.c @@ -1,11 +1,15 @@ #include <errno.h> #include "perf_regs.h" +#include "event.h" int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) { int i, idx = 0; u64 mask = regs->mask; + if (regs->cache_mask & (1 << id)) + goto out; + if (!(mask & (1 << id))) return -EINVAL; @@ -14,6 +18,10 @@ int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) idx++; } - *valp = regs->regs[idx]; + regs->cache_mask |= (1 << id); + regs->cache_regs[id] = regs->regs[idx]; + +out: + *valp = regs->cache_regs[id]; return 0; } diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h index d6e8b6a8d7f3..980dbf76bc98 100644 --- a/tools/perf/util/perf_regs.h +++ b/tools/perf/util/perf_regs.h @@ -1,8 +1,9 @@ #ifndef __PERF_REGS_H #define __PERF_REGS_H -#include "types.h" -#include "event.h" +#include <linux/types.h> + +struct regs_dump; #ifdef HAVE_PERF_REGS_SUPPORT #include <perf_regs.h> @@ -11,6 +12,7 @@ int perf_reg_value(u64 *valp, struct regs_dump *regs, int id); #else #define PERF_REGS_MASK 0 +#define PERF_REGS_MAX 0 static inline const char *perf_reg_name(int id __maybe_unused) { diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 00a7dcb2f55c..7a811eb61f75 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -284,17 +284,17 @@ static int pmu_aliases(const char *name, struct list_head *head) static int pmu_alias_terms(struct perf_pmu_alias *alias, struct list_head *terms) { - struct parse_events_term *term, *clone; + struct parse_events_term *term, *cloned; LIST_HEAD(list); int ret; list_for_each_entry(term, &alias->terms, list) { - ret = parse_events_term__clone(&clone, term); + ret = parse_events_term__clone(&cloned, term); if (ret) { parse_events__free_terms(&list); return ret; } - list_add_tail(&clone->list, &list); + list_add_tail(&cloned->list, &list); } list_splice(&list, terms); return 0; diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index 8b64125a9281..c14a543ce1f3 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -1,7 +1,7 @@ #ifndef __PMU_H #define __PMU_H -#include <linux/bitops.h> +#include <linux/bitmap.h> #include <linux/perf_event.h> #include <stdbool.h> diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 0d1542f33d87..9a0a1839a377 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -628,11 +628,11 @@ static int __show_line_range(struct line_range *lr, const char *module) ret = debuginfo__find_line_range(dinfo, lr); debuginfo__delete(dinfo); - if (ret == 0) { + if (ret == 0 || ret == -ENOENT) { pr_warning("Specified source line is not found.\n"); return -ENOENT; } else if (ret < 0) { - pr_warning("Debuginfo analysis failed. (%d)\n", ret); + pr_warning("Debuginfo analysis failed.\n"); return ret; } @@ -641,7 +641,7 @@ static int __show_line_range(struct line_range *lr, const char *module) ret = get_real_path(tmp, lr->comp_dir, &lr->path); free(tmp); /* Free old path */ if (ret < 0) { - pr_warning("Failed to find source file. (%d)\n", ret); + pr_warning("Failed to find source file path.\n"); return ret; } @@ -721,9 +721,14 @@ static int show_available_vars_at(struct debuginfo *dinfo, ret = debuginfo__find_available_vars_at(dinfo, pev, &vls, max_vls, externs); if (ret <= 0) { - pr_err("Failed to find variables at %s (%d)\n", buf, ret); + if (ret == 0 || ret == -ENOENT) { + pr_err("Failed to find the address of %s\n", buf); + ret = -ENOENT; + } else + pr_warning("Debuginfo analysis failed.\n"); goto end; } + /* Some variables are found */ fprintf(stdout, "Available variables at %s\n", buf); for (i = 0; i < ret; i++) { diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 9d8eb26f0533..dca9145d704c 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -26,7 +26,6 @@ #include <errno.h> #include <stdio.h> #include <unistd.h> -#include <getopt.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> @@ -573,14 +572,13 @@ static int find_variable(Dwarf_Die *sc_die, struct probe_finder *pf) if (!die_find_variable_at(sc_die, pf->pvar->var, pf->addr, &vr_die)) { /* Search again in global variables */ if (!die_find_variable_at(&pf->cu_die, pf->pvar->var, 0, &vr_die)) + pr_warning("Failed to find '%s' in this function.\n", + pf->pvar->var); ret = -ENOENT; } if (ret >= 0) ret = convert_variable(&vr_die, pf); - if (ret < 0) - pr_warning("Failed to find '%s' in this function.\n", - pf->pvar->var); return ret; } @@ -1281,7 +1279,11 @@ out: return ret; } -/* Find available variables at given probe point */ +/* + * Find available variables at given probe point + * Return the number of found probe points. Return 0 if there is no + * matched probe point. Return <0 if an error occurs. + */ int debuginfo__find_available_vars_at(struct debuginfo *dbg, struct perf_probe_event *pev, struct variable_list **vls, diff --git a/tools/perf/util/pstack.c b/tools/perf/util/pstack.c index daa17aeb6c63..a126e6cc6e73 100644 --- a/tools/perf/util/pstack.c +++ b/tools/perf/util/pstack.c @@ -6,6 +6,7 @@ #include "util.h" #include "pstack.h" +#include "debug.h" #include <linux/kernel.h> #include <stdlib.h> diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index 122669c18ff4..12aa9b0d0ba1 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -14,12 +14,12 @@ */ int verbose; -int eprintf(int level, const char *fmt, ...) +int eprintf(int level, int var, const char *fmt, ...) { va_list args; int ret = 0; - if (verbose >= level) { + if (var >= level) { va_start(args, fmt); ret = vfprintf(stderr, fmt, args); va_end(args); diff --git a/tools/perf/util/record.c b/tools/perf/util/record.c index 049e0a09ccd3..fe8079edbdc1 100644 --- a/tools/perf/util/record.c +++ b/tools/perf/util/record.c @@ -4,6 +4,7 @@ #include "parse-events.h" #include <api/fs/fs.h> #include "util.h" +#include "cloexec.h" typedef void (*setup_probe_fn_t)(struct perf_evsel *evsel); @@ -11,6 +12,7 @@ static int perf_do_probe_api(setup_probe_fn_t fn, int cpu, const char *str) { struct perf_evlist *evlist; struct perf_evsel *evsel; + unsigned long flags = perf_event_open_cloexec_flag(); int err = -EAGAIN, fd; evlist = perf_evlist__new(); @@ -22,14 +24,14 @@ static int perf_do_probe_api(setup_probe_fn_t fn, int cpu, const char *str) evsel = perf_evlist__first(evlist); - fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, 0); + fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, flags); if (fd < 0) goto out_delete; close(fd); fn(evsel); - fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, 0); + fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, flags); if (fd < 0) { if (errno == EINVAL) err = -EINVAL; @@ -69,15 +71,26 @@ static void perf_probe_sample_identifier(struct perf_evsel *evsel) evsel->attr.sample_type |= PERF_SAMPLE_IDENTIFIER; } +static void perf_probe_comm_exec(struct perf_evsel *evsel) +{ + evsel->attr.comm_exec = 1; +} + bool perf_can_sample_identifier(void) { return perf_probe_api(perf_probe_sample_identifier); } +static bool perf_can_comm_exec(void) +{ + return perf_probe_api(perf_probe_comm_exec); +} + void perf_evlist__config(struct perf_evlist *evlist, struct record_opts *opts) { struct perf_evsel *evsel; bool use_sample_identifier = false; + bool use_comm_exec; /* * Set the evsel leader links before we configure attributes, @@ -89,8 +102,13 @@ void perf_evlist__config(struct perf_evlist *evlist, struct record_opts *opts) if (evlist->cpus->map[0] < 0) opts->no_inherit = true; - evlist__for_each(evlist, evsel) + use_comm_exec = perf_can_comm_exec(); + + evlist__for_each(evlist, evsel) { perf_evsel__config(evsel, opts); + if (!evsel->idx && use_comm_exec) + evsel->attr.comm_exec = 1; + } if (evlist->nr_entries > 1) { struct perf_evsel *first = perf_evlist__first(evlist); @@ -203,7 +221,8 @@ bool perf_evlist__can_select_event(struct perf_evlist *evlist, const char *str) cpu = evlist->cpus->map[0]; } - fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, 0); + fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, + perf_event_open_cloexec_flag()); if (fd >= 0) { close(fd); ret = true; diff --git a/tools/perf/util/scripting-engines/trace-event-perl.c b/tools/perf/util/scripting-engines/trace-event-perl.c index e108207c5de0..b2dba9c0a3a1 100644 --- a/tools/perf/util/scripting-engines/trace-event-perl.c +++ b/tools/perf/util/scripting-engines/trace-event-perl.c @@ -34,6 +34,7 @@ #include "../event.h" #include "../trace-event.h" #include "../evsel.h" +#include "../debug.h" void boot_Perf__Trace__Context(pTHX_ CV *cv); void boot_DynaLoader(pTHX_ CV *cv); @@ -215,6 +216,7 @@ static void define_event_symbols(struct event_format *event, case PRINT_BSTRING: case PRINT_DYNAMIC_ARRAY: case PRINT_STRING: + case PRINT_BITMASK: break; case PRINT_TYPE: define_event_symbols(event, ev_name, args->typecast.item); diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c index cd9774df3750..cbce2545da45 100644 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ b/tools/perf/util/scripting-engines/trace-event-python.c @@ -27,11 +27,13 @@ #include <errno.h> #include "../../perf.h" +#include "../debug.h" #include "../evsel.h" #include "../util.h" #include "../event.h" #include "../thread.h" #include "../trace-event.h" +#include "../machine.h" PyMODINIT_FUNC initperf_trace_context(void); @@ -50,10 +52,14 @@ static int zero_flag_atom; static PyObject *main_module, *main_dict; +static void handler_call_die(const char *handler_name) NORETURN; static void handler_call_die(const char *handler_name) { PyErr_Print(); Py_FatalError("problem in Python trace event handler"); + // Py_FatalError does not return + // but we have to make the compiler happy + abort(); } /* @@ -97,6 +103,7 @@ static void define_value(enum print_arg_type field_type, retval = PyObject_CallObject(handler, t); if (retval == NULL) handler_call_die(handler_name); + Py_DECREF(retval); } Py_DECREF(t); @@ -143,6 +150,7 @@ static void define_field(enum print_arg_type field_type, retval = PyObject_CallObject(handler, t); if (retval == NULL) handler_call_die(handler_name); + Py_DECREF(retval); } Py_DECREF(t); @@ -197,6 +205,7 @@ static void define_event_symbols(struct event_format *event, case PRINT_BSTRING: case PRINT_DYNAMIC_ARRAY: case PRINT_FUNC: + case PRINT_BITMASK: /* we should warn... */ return; } @@ -230,15 +239,133 @@ static inline struct event_format *find_cache_event(struct perf_evsel *evsel) return event; } +static PyObject *get_field_numeric_entry(struct event_format *event, + struct format_field *field, void *data) +{ + bool is_array = field->flags & FIELD_IS_ARRAY; + PyObject *obj, *list = NULL; + unsigned long long val; + unsigned int item_size, n_items, i; + + if (is_array) { + list = PyList_New(field->arraylen); + item_size = field->size / field->arraylen; + n_items = field->arraylen; + } else { + item_size = field->size; + n_items = 1; + } + + for (i = 0; i < n_items; i++) { + + val = read_size(event, data + field->offset + i * item_size, + item_size); + if (field->flags & FIELD_IS_SIGNED) { + if ((long long)val >= LONG_MIN && + (long long)val <= LONG_MAX) + obj = PyInt_FromLong(val); + else + obj = PyLong_FromLongLong(val); + } else { + if (val <= LONG_MAX) + obj = PyInt_FromLong(val); + else + obj = PyLong_FromUnsignedLongLong(val); + } + if (is_array) + PyList_SET_ITEM(list, i, obj); + } + if (is_array) + obj = list; + return obj; +} + + +static PyObject *python_process_callchain(struct perf_sample *sample, + struct perf_evsel *evsel, + struct addr_location *al) +{ + PyObject *pylist; + + pylist = PyList_New(0); + if (!pylist) + Py_FatalError("couldn't create Python list"); + + if (!symbol_conf.use_callchain || !sample->callchain) + goto exit; + + if (machine__resolve_callchain(al->machine, evsel, al->thread, + sample, NULL, NULL, + PERF_MAX_STACK_DEPTH) != 0) { + pr_err("Failed to resolve callchain. Skipping\n"); + goto exit; + } + callchain_cursor_commit(&callchain_cursor); + + + while (1) { + PyObject *pyelem; + struct callchain_cursor_node *node; + node = callchain_cursor_current(&callchain_cursor); + if (!node) + break; + + pyelem = PyDict_New(); + if (!pyelem) + Py_FatalError("couldn't create Python dictionary"); + + + pydict_set_item_string_decref(pyelem, "ip", + PyLong_FromUnsignedLongLong(node->ip)); + + if (node->sym) { + PyObject *pysym = PyDict_New(); + if (!pysym) + Py_FatalError("couldn't create Python dictionary"); + pydict_set_item_string_decref(pysym, "start", + PyLong_FromUnsignedLongLong(node->sym->start)); + pydict_set_item_string_decref(pysym, "end", + PyLong_FromUnsignedLongLong(node->sym->end)); + pydict_set_item_string_decref(pysym, "binding", + PyInt_FromLong(node->sym->binding)); + pydict_set_item_string_decref(pysym, "name", + PyString_FromStringAndSize(node->sym->name, + node->sym->namelen)); + pydict_set_item_string_decref(pyelem, "sym", pysym); + } + + if (node->map) { + struct map *map = node->map; + const char *dsoname = "[unknown]"; + 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; + } + pydict_set_item_string_decref(pyelem, "dso", + PyString_FromString(dsoname)); + } + + callchain_cursor_advance(&callchain_cursor); + PyList_Append(pylist, pyelem); + Py_DECREF(pyelem); + } + +exit: + return pylist; +} + + static void python_process_tracepoint(struct perf_sample *sample, struct perf_evsel *evsel, struct thread *thread, struct addr_location *al) { - PyObject *handler, *retval, *context, *t, *obj, *dict = NULL; + PyObject *handler, *retval, *context, *t, *obj, *callchain; + PyObject *dict = NULL; static char handler_name[256]; struct format_field *field; - unsigned long long val; unsigned long s, ns; struct event_format *event; unsigned n = 0; @@ -279,18 +406,23 @@ static void python_process_tracepoint(struct perf_sample *sample, PyTuple_SetItem(t, n++, PyString_FromString(handler_name)); PyTuple_SetItem(t, n++, context); + /* ip unwinding */ + callchain = python_process_callchain(sample, evsel, al); + if (handler) { PyTuple_SetItem(t, n++, PyInt_FromLong(cpu)); PyTuple_SetItem(t, n++, PyInt_FromLong(s)); PyTuple_SetItem(t, n++, PyInt_FromLong(ns)); PyTuple_SetItem(t, n++, PyInt_FromLong(pid)); PyTuple_SetItem(t, n++, PyString_FromString(comm)); + PyTuple_SetItem(t, n++, callchain); } else { pydict_set_item_string_decref(dict, "common_cpu", PyInt_FromLong(cpu)); pydict_set_item_string_decref(dict, "common_s", PyInt_FromLong(s)); pydict_set_item_string_decref(dict, "common_ns", PyInt_FromLong(ns)); pydict_set_item_string_decref(dict, "common_pid", PyInt_FromLong(pid)); pydict_set_item_string_decref(dict, "common_comm", PyString_FromString(comm)); + pydict_set_item_string_decref(dict, "common_callchain", callchain); } for (field = event->format.fields; field; field = field->next) { if (field->flags & FIELD_IS_STRING) { @@ -302,20 +434,7 @@ static void python_process_tracepoint(struct perf_sample *sample, offset = field->offset; obj = PyString_FromString((char *)data + offset); } else { /* FIELD_IS_NUMERIC */ - val = read_size(event, data + field->offset, - field->size); - if (field->flags & FIELD_IS_SIGNED) { - if ((long long)val >= LONG_MIN && - (long long)val <= LONG_MAX) - obj = PyInt_FromLong(val); - else - obj = PyLong_FromLongLong(val); - } else { - if (val <= LONG_MAX) - obj = PyInt_FromLong(val); - else - obj = PyLong_FromUnsignedLongLong(val); - } + obj = get_field_numeric_entry(event, field, data); } if (handler) PyTuple_SetItem(t, n++, obj); @@ -323,6 +442,7 @@ static void python_process_tracepoint(struct perf_sample *sample, pydict_set_item_string_decref(dict, field->name, obj); } + if (!handler) PyTuple_SetItem(t, n++, dict); @@ -333,6 +453,7 @@ static void python_process_tracepoint(struct perf_sample *sample, retval = PyObject_CallObject(handler, t); if (retval == NULL) handler_call_die(handler_name); + Py_DECREF(retval); } else { handler = PyDict_GetItemString(main_dict, "trace_unhandled"); if (handler && PyCallable_Check(handler)) { @@ -340,6 +461,7 @@ static void python_process_tracepoint(struct perf_sample *sample, retval = PyObject_CallObject(handler, t); if (retval == NULL) handler_call_die("trace_unhandled"); + Py_DECREF(retval); } Py_DECREF(dict); } @@ -352,7 +474,7 @@ static void python_process_general_event(struct perf_sample *sample, struct thread *thread, struct addr_location *al) { - PyObject *handler, *retval, *t, *dict; + PyObject *handler, *retval, *t, *dict, *callchain, *dict_sample; static char handler_name[64]; unsigned n = 0; @@ -368,6 +490,10 @@ static void python_process_general_event(struct perf_sample *sample, if (!dict) Py_FatalError("couldn't create Python dictionary"); + dict_sample = PyDict_New(); + if (!dict_sample) + Py_FatalError("couldn't create Python dictionary"); + snprintf(handler_name, sizeof(handler_name), "%s", "process_event"); handler = PyDict_GetItemString(main_dict, handler_name); @@ -377,8 +503,21 @@ static void python_process_general_event(struct perf_sample *sample, pydict_set_item_string_decref(dict, "ev_name", PyString_FromString(perf_evsel__name(evsel))); pydict_set_item_string_decref(dict, "attr", PyString_FromStringAndSize( (const char *)&evsel->attr, sizeof(evsel->attr))); - pydict_set_item_string_decref(dict, "sample", PyString_FromStringAndSize( - (const char *)sample, sizeof(*sample))); + + pydict_set_item_string_decref(dict_sample, "pid", + PyInt_FromLong(sample->pid)); + pydict_set_item_string_decref(dict_sample, "tid", + PyInt_FromLong(sample->tid)); + pydict_set_item_string_decref(dict_sample, "cpu", + PyInt_FromLong(sample->cpu)); + pydict_set_item_string_decref(dict_sample, "ip", + PyLong_FromUnsignedLongLong(sample->ip)); + pydict_set_item_string_decref(dict_sample, "time", + PyLong_FromUnsignedLongLong(sample->time)); + pydict_set_item_string_decref(dict_sample, "period", + PyLong_FromUnsignedLongLong(sample->period)); + pydict_set_item_string_decref(dict, "sample", dict_sample); + pydict_set_item_string_decref(dict, "raw_buf", PyString_FromStringAndSize( (const char *)sample->raw_data, sample->raw_size)); pydict_set_item_string_decref(dict, "comm", @@ -392,6 +531,10 @@ static void python_process_general_event(struct perf_sample *sample, PyString_FromString(al->sym->name)); } + /* ip unwinding */ + callchain = python_process_callchain(sample, evsel, al); + pydict_set_item_string_decref(dict, "callchain", callchain); + PyTuple_SetItem(t, n++, dict); if (_PyTuple_Resize(&t, n) == -1) Py_FatalError("error resizing Python tuple"); @@ -399,6 +542,7 @@ static void python_process_general_event(struct perf_sample *sample, retval = PyObject_CallObject(handler, t); if (retval == NULL) handler_call_die(handler_name); + Py_DECREF(retval); exit: Py_DECREF(dict); Py_DECREF(t); @@ -520,8 +664,7 @@ static int python_stop_script(void) retval = PyObject_CallObject(handler, NULL); if (retval == NULL) handler_call_die("trace_end"); - else - Py_DECREF(retval); + Py_DECREF(retval); out: Py_XDECREF(main_dict); Py_XDECREF(main_module); @@ -588,6 +731,7 @@ static int python_generate_script(struct pevent *pevent, const char *outfile) fprintf(ofp, "common_nsecs, "); fprintf(ofp, "common_pid, "); fprintf(ofp, "common_comm,\n\t"); + fprintf(ofp, "common_callchain, "); not_first = 0; count = 0; @@ -622,6 +766,7 @@ static int python_generate_script(struct pevent *pevent, const char *outfile) fprintf(ofp, "%s=", f->name); if (f->flags & FIELD_IS_STRING || f->flags & FIELD_IS_FLAG || + f->flags & FIELD_IS_ARRAY || f->flags & FIELD_IS_SYMBOLIC) fprintf(ofp, "%%s"); else if (f->flags & FIELD_IS_SIGNED) @@ -630,7 +775,7 @@ static int python_generate_script(struct pevent *pevent, const char *outfile) fprintf(ofp, "%%u"); } - fprintf(ofp, "\\n\" %% \\\n\t\t("); + fprintf(ofp, "\" %% \\\n\t\t("); not_first = 0; count = 0; @@ -666,7 +811,15 @@ static int python_generate_script(struct pevent *pevent, const char *outfile) fprintf(ofp, "%s", f->name); } - fprintf(ofp, "),\n\n"); + fprintf(ofp, ")\n\n"); + + fprintf(ofp, "\t\tfor node in common_callchain:"); + fprintf(ofp, "\n\t\t\tif 'sym' in node:"); + fprintf(ofp, "\n\t\t\t\tprint \"\\t[%%x] %%s\" %% (node['ip'], node['sym']['name'])"); + fprintf(ofp, "\n\t\t\telse:"); + fprintf(ofp, "\n\t\t\t\tprint \"\t[%%x]\" %% (node['ip'])\n\n"); + fprintf(ofp, "\t\tprint \"\\n\"\n\n"); + } fprintf(ofp, "def trace_unhandled(event_name, context, " diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 55960f22233c..88dfef70c13d 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -14,7 +14,6 @@ #include "util.h" #include "cpumap.h" #include "perf_regs.h" -#include "vdso.h" static int perf_session__open(struct perf_session *session) { @@ -156,7 +155,6 @@ void perf_session__delete(struct perf_session *session) if (session->file) perf_data_file__close(session->file); free(session); - vdso__exit(); } static int process_event_synth_tracing_data_stub(struct perf_tool *tool @@ -511,6 +509,7 @@ static int flush_sample_queue(struct perf_session *s, os->last_flush = iter->timestamp; list_del(&iter->list); list_add(&iter->list, &os->sample_cache); + os->nr_samples--; if (show_progress) ui_progress__update(&prog, 1); @@ -523,8 +522,6 @@ static int flush_sample_queue(struct perf_session *s, list_entry(head->prev, struct sample_queue, list); } - os->nr_samples = 0; - return 0; } @@ -994,8 +991,10 @@ static int perf_session_deliver_event(struct perf_session *session, } } -static int perf_session__process_user_event(struct perf_session *session, union perf_event *event, - struct perf_tool *tool, u64 file_offset) +static s64 perf_session__process_user_event(struct perf_session *session, + union perf_event *event, + struct perf_tool *tool, + u64 file_offset) { int fd = perf_data_file__fd(session->file); int err; @@ -1037,7 +1036,7 @@ static void event_swap(union perf_event *event, bool sample_id_all) swap(event, sample_id_all); } -static int perf_session__process_event(struct perf_session *session, +static s64 perf_session__process_event(struct perf_session *session, union perf_event *event, struct perf_tool *tool, u64 file_offset) @@ -1083,13 +1082,14 @@ void perf_event_header__bswap(struct perf_event_header *hdr) struct thread *perf_session__findnew(struct perf_session *session, pid_t pid) { - return machine__findnew_thread(&session->machines.host, 0, pid); + return machine__findnew_thread(&session->machines.host, -1, pid); } static struct thread *perf_session__register_idle_thread(struct perf_session *session) { - struct thread *thread = perf_session__findnew(session, 0); + struct thread *thread; + thread = machine__findnew_thread(&session->machines.host, 0, 0); if (thread == NULL || thread__set_comm(thread, "swapper", 0)) { pr_err("problem inserting idle task.\n"); thread = NULL; @@ -1147,7 +1147,7 @@ static int __perf_session__process_pipe_events(struct perf_session *session, union perf_event *event; uint32_t size, cur_size = 0; void *buf = NULL; - int skip = 0; + s64 skip = 0; u64 head; ssize_t err; void *p; @@ -1276,13 +1276,13 @@ int __perf_session__process_events(struct perf_session *session, u64 file_size, struct perf_tool *tool) { int fd = perf_data_file__fd(session->file); - u64 head, page_offset, file_offset, file_pos; + u64 head, page_offset, file_offset, file_pos, size; int err, mmap_prot, mmap_flags, map_idx = 0; size_t mmap_size; char *buf, *mmaps[NUM_MMAPS]; union perf_event *event; - uint32_t size; struct ui_progress prog; + s64 skip; perf_tool__fill_defaults(tool); @@ -1296,8 +1296,10 @@ int __perf_session__process_events(struct perf_session *session, ui_progress__init(&prog, file_size, "Processing events..."); mmap_size = MMAP_SIZE; - if (mmap_size > file_size) + if (mmap_size > file_size) { mmap_size = file_size; + session->one_mmap = true; + } memset(mmaps, 0, sizeof(mmaps)); @@ -1319,6 +1321,10 @@ remap: mmaps[map_idx] = buf; map_idx = (map_idx + 1) & (ARRAY_SIZE(mmaps) - 1); file_pos = file_offset + head; + if (session->one_mmap) { + session->one_mmap_addr = buf; + session->one_mmap_offset = file_offset; + } more: event = fetch_mmaped_event(session, head, mmap_size, buf); @@ -1337,7 +1343,8 @@ more: size = event->header.size; if (size < sizeof(struct perf_event_header) || - perf_session__process_event(session, event, tool, file_pos) < 0) { + (skip = perf_session__process_event(session, event, tool, file_pos)) + < 0) { pr_err("%#" PRIx64 " [%#x]: failed to process type: %d\n", file_offset + head, event->header.size, event->header.type); @@ -1345,6 +1352,9 @@ more: goto out_err; } + if (skip) + size += skip; + head += size; file_pos += size; @@ -1364,6 +1374,7 @@ out_err: ui_progress__finish(); perf_session__warn_about_errors(session, tool); perf_session_free_sample_buffers(session); + session->one_mmap = false; return err; } @@ -1625,13 +1636,14 @@ out_delete_map: void perf_session__fprintf_info(struct perf_session *session, FILE *fp, bool full) { - int fd = perf_data_file__fd(session->file); struct stat st; - int ret; + int fd, ret; if (session == NULL || fp == NULL) return; + fd = perf_data_file__fd(session->file); + ret = fstat(fd, &st); if (ret == -1) return; diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index 3140f8ae6148..0321013bd9fd 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h @@ -36,6 +36,9 @@ struct perf_session { struct trace_event tevent; struct events_stats stats; bool repipe; + bool one_mmap; + void *one_mmap_addr; + u64 one_mmap_offset; struct ordered_samples ordered_samples; struct perf_data_file *file; }; diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 635cd8f8b22e..14e5a039bc45 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -1,13 +1,20 @@ +#include <sys/mman.h> #include "sort.h" #include "hist.h" #include "comm.h" #include "symbol.h" +#include "evsel.h" regex_t parent_regex; const char default_parent_pattern[] = "^sys_|^do_page_fault"; const char *parent_pattern = default_parent_pattern; const char default_sort_order[] = "comm,dso,symbol"; -const char *sort_order = default_sort_order; +const char default_branch_sort_order[] = "comm,dso_from,symbol_from,dso_to,symbol_to"; +const char default_mem_sort_order[] = "local_weight,mem,sym,dso,symbol_daddr,dso_daddr,snoop,tlb,locked"; +const char default_top_sort_order[] = "dso,symbol"; +const char default_diff_sort_order[] = "dso,symbol"; +const char *sort_order; +const char *field_order; regex_t ignore_callees_regex; int have_ignore_callees = 0; int sort__need_collapse = 0; @@ -16,9 +23,6 @@ int sort__has_sym = 0; int sort__has_dso = 0; enum sort_mode sort__mode = SORT_MODE__NORMAL; -enum sort_type sort__first_dimension; - -LIST_HEAD(hist_entry__sort_list); static int repsep_snprintf(char *bf, size_t size, const char *fmt, ...) { @@ -93,6 +97,12 @@ sort__comm_collapse(struct hist_entry *left, struct hist_entry *right) return comm__str(right->comm) - comm__str(left->comm); } +static int64_t +sort__comm_sort(struct hist_entry *left, struct hist_entry *right) +{ + return strcmp(comm__str(right->comm), comm__str(left->comm)); +} + static int hist_entry__comm_snprintf(struct hist_entry *he, char *bf, size_t size, unsigned int width) { @@ -103,6 +113,7 @@ struct sort_entry sort_comm = { .se_header = "Command", .se_cmp = sort__comm_cmp, .se_collapse = sort__comm_collapse, + .se_sort = sort__comm_sort, .se_snprintf = hist_entry__comm_snprintf, .se_width_idx = HISTC_COMM, }; @@ -116,7 +127,7 @@ static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r) const char *dso_name_l, *dso_name_r; if (!dso_l || !dso_r) - return cmp_null(dso_l, dso_r); + return cmp_null(dso_r, dso_l); if (verbose) { dso_name_l = dso_l->long_name; @@ -132,7 +143,7 @@ static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r) static int64_t sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) { - return _sort__dso_cmp(left->ms.map, right->ms.map); + return _sort__dso_cmp(right->ms.map, left->ms.map); } static int _hist_entry__dso_snprintf(struct map *map, char *bf, @@ -204,6 +215,15 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) return _sort__sym_cmp(left->ms.sym, right->ms.sym); } +static int64_t +sort__sym_sort(struct hist_entry *left, struct hist_entry *right) +{ + if (!left->ms.sym || !right->ms.sym) + return cmp_null(left->ms.sym, right->ms.sym); + + return strcmp(right->ms.sym->name, left->ms.sym->name); +} + static int _hist_entry__sym_snprintf(struct map *map, struct symbol *sym, u64 ip, char level, char *bf, size_t size, unsigned int width) @@ -250,6 +270,7 @@ static int hist_entry__sym_snprintf(struct hist_entry *he, char *bf, struct sort_entry sort_sym = { .se_header = "Symbol", .se_cmp = sort__sym_cmp, + .se_sort = sort__sym_sort, .se_snprintf = hist_entry__sym_snprintf, .se_width_idx = HISTC_SYMBOL, }; @@ -277,7 +298,7 @@ sort__srcline_cmp(struct hist_entry *left, struct hist_entry *right) map__rip_2objdump(map, right->ip)); } } - return strcmp(left->srcline, right->srcline); + return strcmp(right->srcline, left->srcline); } static int hist_entry__srcline_snprintf(struct hist_entry *he, char *bf, @@ -305,7 +326,7 @@ sort__parent_cmp(struct hist_entry *left, struct hist_entry *right) if (!sym_l || !sym_r) return cmp_null(sym_l, sym_r); - return strcmp(sym_l->name, sym_r->name); + return strcmp(sym_r->name, sym_l->name); } static int hist_entry__parent_snprintf(struct hist_entry *he, char *bf, @@ -764,6 +785,104 @@ static int hist_entry__snoop_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*s", width, out); } +static inline u64 cl_address(u64 address) +{ + /* return the cacheline of the address */ + return (address & ~(cacheline_size - 1)); +} + +static int64_t +sort__dcacheline_cmp(struct hist_entry *left, struct hist_entry *right) +{ + u64 l, r; + struct map *l_map, *r_map; + + if (!left->mem_info) return -1; + if (!right->mem_info) return 1; + + /* group event types together */ + if (left->cpumode > right->cpumode) return -1; + if (left->cpumode < right->cpumode) return 1; + + l_map = left->mem_info->daddr.map; + r_map = right->mem_info->daddr.map; + + /* if both are NULL, jump to sort on al_addr instead */ + if (!l_map && !r_map) + goto addr; + + if (!l_map) return -1; + if (!r_map) return 1; + + if (l_map->maj > r_map->maj) return -1; + if (l_map->maj < r_map->maj) return 1; + + if (l_map->min > r_map->min) return -1; + if (l_map->min < r_map->min) return 1; + + if (l_map->ino > r_map->ino) return -1; + if (l_map->ino < r_map->ino) return 1; + + if (l_map->ino_generation > r_map->ino_generation) return -1; + if (l_map->ino_generation < r_map->ino_generation) return 1; + + /* + * Addresses with no major/minor numbers are assumed to be + * anonymous in userspace. Sort those on pid then address. + * + * The kernel and non-zero major/minor mapped areas are + * assumed to be unity mapped. Sort those on address. + */ + + if ((left->cpumode != PERF_RECORD_MISC_KERNEL) && + (!(l_map->flags & MAP_SHARED)) && + !l_map->maj && !l_map->min && !l_map->ino && + !l_map->ino_generation) { + /* userspace anonymous */ + + if (left->thread->pid_ > right->thread->pid_) return -1; + if (left->thread->pid_ < right->thread->pid_) return 1; + } + +addr: + /* al_addr does all the right addr - start + offset calculations */ + l = cl_address(left->mem_info->daddr.al_addr); + r = cl_address(right->mem_info->daddr.al_addr); + + if (l > r) return -1; + if (l < r) return 1; + + return 0; +} + +static int hist_entry__dcacheline_snprintf(struct hist_entry *he, char *bf, + size_t size, unsigned int width) +{ + + uint64_t addr = 0; + struct map *map = NULL; + struct symbol *sym = NULL; + char level = he->level; + + if (he->mem_info) { + addr = cl_address(he->mem_info->daddr.al_addr); + map = he->mem_info->daddr.map; + sym = he->mem_info->daddr.sym; + + /* print [s] for shared data mmaps */ + if ((he->cpumode != PERF_RECORD_MISC_KERNEL) && + map && (map->type == MAP__VARIABLE) && + (map->flags & MAP_SHARED) && + (map->maj || map->min || map->ino || + map->ino_generation)) + level = 's'; + else if (!map) + level = 'X'; + } + return _hist_entry__sym_snprintf(map, sym, addr, level, bf, size, + width); +} + struct sort_entry sort_mispredict = { .se_header = "Branch Mispredicted", .se_cmp = sort__mispredict_cmp, @@ -856,6 +975,13 @@ struct sort_entry sort_mem_snoop = { .se_width_idx = HISTC_MEM_SNOOP, }; +struct sort_entry sort_mem_dcacheline = { + .se_header = "Data Cacheline", + .se_cmp = sort__dcacheline_cmp, + .se_snprintf = hist_entry__dcacheline_snprintf, + .se_width_idx = HISTC_MEM_DCACHELINE, +}; + static int64_t sort__abort_cmp(struct hist_entry *left, struct hist_entry *right) { @@ -1023,23 +1149,199 @@ static struct sort_dimension memory_sort_dimensions[] = { DIM(SORT_MEM_TLB, "tlb", sort_mem_tlb), DIM(SORT_MEM_LVL, "mem", sort_mem_lvl), DIM(SORT_MEM_SNOOP, "snoop", sort_mem_snoop), + DIM(SORT_MEM_DCACHELINE, "dcacheline", sort_mem_dcacheline), +}; + +#undef DIM + +struct hpp_dimension { + const char *name; + struct perf_hpp_fmt *fmt; + int taken; +}; + +#define DIM(d, n) { .name = n, .fmt = &perf_hpp__format[d], } + +static struct hpp_dimension hpp_sort_dimensions[] = { + DIM(PERF_HPP__OVERHEAD, "overhead"), + DIM(PERF_HPP__OVERHEAD_SYS, "overhead_sys"), + DIM(PERF_HPP__OVERHEAD_US, "overhead_us"), + DIM(PERF_HPP__OVERHEAD_GUEST_SYS, "overhead_guest_sys"), + DIM(PERF_HPP__OVERHEAD_GUEST_US, "overhead_guest_us"), + DIM(PERF_HPP__OVERHEAD_ACC, "overhead_children"), + DIM(PERF_HPP__SAMPLES, "sample"), + DIM(PERF_HPP__PERIOD, "period"), }; #undef DIM -static void __sort_dimension__add(struct sort_dimension *sd, enum sort_type idx) +struct hpp_sort_entry { + struct perf_hpp_fmt hpp; + struct sort_entry *se; +}; + +bool perf_hpp__same_sort_entry(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) { - if (sd->taken) + struct hpp_sort_entry *hse_a; + struct hpp_sort_entry *hse_b; + + if (!perf_hpp__is_sort_entry(a) || !perf_hpp__is_sort_entry(b)) + return false; + + hse_a = container_of(a, struct hpp_sort_entry, hpp); + hse_b = container_of(b, struct hpp_sort_entry, hpp); + + return hse_a->se == hse_b->se; +} + +void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists) +{ + struct hpp_sort_entry *hse; + + if (!perf_hpp__is_sort_entry(fmt)) return; + hse = container_of(fmt, struct hpp_sort_entry, hpp); + hists__new_col_len(hists, hse->se->se_width_idx, + strlen(hse->se->se_header)); +} + +static int __sort__hpp_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct perf_evsel *evsel) +{ + struct hpp_sort_entry *hse; + size_t len; + + hse = container_of(fmt, struct hpp_sort_entry, hpp); + len = hists__col_len(&evsel->hists, hse->se->se_width_idx); + + return scnprintf(hpp->buf, hpp->size, "%-*s", len, hse->se->se_header); +} + +static int __sort__hpp_width(struct perf_hpp_fmt *fmt, + struct perf_hpp *hpp __maybe_unused, + struct perf_evsel *evsel) +{ + struct hpp_sort_entry *hse; + + hse = container_of(fmt, struct hpp_sort_entry, hpp); + + return hists__col_len(&evsel->hists, hse->se->se_width_idx); +} + +static int __sort__hpp_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hist_entry *he) +{ + struct hpp_sort_entry *hse; + size_t len; + + hse = container_of(fmt, struct hpp_sort_entry, hpp); + len = hists__col_len(he->hists, hse->se->se_width_idx); + + return hse->se->se_snprintf(he, hpp->buf, hpp->size, len); +} + +static struct hpp_sort_entry * +__sort_dimension__alloc_hpp(struct sort_dimension *sd) +{ + struct hpp_sort_entry *hse; + + hse = malloc(sizeof(*hse)); + if (hse == NULL) { + pr_err("Memory allocation failed\n"); + return NULL; + } + + hse->se = sd->entry; + hse->hpp.header = __sort__hpp_header; + hse->hpp.width = __sort__hpp_width; + hse->hpp.entry = __sort__hpp_entry; + hse->hpp.color = NULL; + + hse->hpp.cmp = sd->entry->se_cmp; + hse->hpp.collapse = sd->entry->se_collapse ? : sd->entry->se_cmp; + hse->hpp.sort = sd->entry->se_sort ? : hse->hpp.collapse; + + INIT_LIST_HEAD(&hse->hpp.list); + INIT_LIST_HEAD(&hse->hpp.sort_list); + hse->hpp.elide = false; + + return hse; +} + +bool perf_hpp__is_sort_entry(struct perf_hpp_fmt *format) +{ + return format->header == __sort__hpp_header; +} + +static int __sort_dimension__add_hpp_sort(struct sort_dimension *sd) +{ + struct hpp_sort_entry *hse = __sort_dimension__alloc_hpp(sd); + + if (hse == NULL) + return -1; + + perf_hpp__register_sort_field(&hse->hpp); + return 0; +} + +static int __sort_dimension__add_hpp_output(struct sort_dimension *sd) +{ + struct hpp_sort_entry *hse = __sort_dimension__alloc_hpp(sd); + + if (hse == NULL) + return -1; + + perf_hpp__column_register(&hse->hpp); + return 0; +} + +static int __sort_dimension__add(struct sort_dimension *sd) +{ + if (sd->taken) + return 0; + + if (__sort_dimension__add_hpp_sort(sd) < 0) + return -1; + if (sd->entry->se_collapse) sort__need_collapse = 1; - if (list_empty(&hist_entry__sort_list)) - sort__first_dimension = idx; + sd->taken = 1; + + return 0; +} + +static int __hpp_dimension__add(struct hpp_dimension *hd) +{ + if (!hd->taken) { + hd->taken = 1; + + perf_hpp__register_sort_field(hd->fmt); + } + return 0; +} + +static int __sort_dimension__add_output(struct sort_dimension *sd) +{ + if (sd->taken) + return 0; + + if (__sort_dimension__add_hpp_output(sd) < 0) + return -1; - list_add_tail(&sd->entry->list, &hist_entry__sort_list); sd->taken = 1; + return 0; +} + +static int __hpp_dimension__add_output(struct hpp_dimension *hd) +{ + if (!hd->taken) { + hd->taken = 1; + + perf_hpp__column_register(hd->fmt); + } + return 0; } int sort_dimension__add(const char *tok) @@ -1068,8 +1370,16 @@ int sort_dimension__add(const char *tok) sort__has_dso = 1; } - __sort_dimension__add(sd, i); - return 0; + return __sort_dimension__add(sd); + } + + for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) { + struct hpp_dimension *hd = &hpp_sort_dimensions[i]; + + if (strncasecmp(tok, hd->name, strlen(tok))) + continue; + + return __hpp_dimension__add(hd); } for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++) { @@ -1084,7 +1394,7 @@ int sort_dimension__add(const char *tok) if (sd->entry == &sort_sym_from || sd->entry == &sort_sym_to) sort__has_sym = 1; - __sort_dimension__add(sd, i + __SORT_BRANCH_STACK); + __sort_dimension__add(sd); return 0; } @@ -1100,18 +1410,47 @@ int sort_dimension__add(const char *tok) if (sd->entry == &sort_mem_daddr_sym) sort__has_sym = 1; - __sort_dimension__add(sd, i + __SORT_MEMORY_MODE); + __sort_dimension__add(sd); return 0; } return -ESRCH; } -int setup_sorting(void) +static const char *get_default_sort_order(void) +{ + const char *default_sort_orders[] = { + default_sort_order, + default_branch_sort_order, + default_mem_sort_order, + default_top_sort_order, + default_diff_sort_order, + }; + + BUG_ON(sort__mode >= ARRAY_SIZE(default_sort_orders)); + + return default_sort_orders[sort__mode]; +} + +static int __setup_sorting(void) { - char *tmp, *tok, *str = strdup(sort_order); + char *tmp, *tok, *str; + const char *sort_keys = sort_order; int ret = 0; + if (sort_keys == NULL) { + if (field_order) { + /* + * If user specified field order but no sort order, + * we'll honor it and not add default sort orders. + */ + return 0; + } + + sort_keys = get_default_sort_order(); + } + + str = strdup(sort_keys); if (str == NULL) { error("Not enough memory to setup sort keys"); return -ENOMEM; @@ -1133,66 +1472,235 @@ int setup_sorting(void) return ret; } -static void sort_entry__setup_elide(struct sort_entry *se, - struct strlist *list, - const char *list_name, FILE *fp) +void perf_hpp__set_elide(int idx, bool elide) +{ + struct perf_hpp_fmt *fmt; + struct hpp_sort_entry *hse; + + perf_hpp__for_each_format(fmt) { + if (!perf_hpp__is_sort_entry(fmt)) + continue; + + hse = container_of(fmt, struct hpp_sort_entry, hpp); + if (hse->se->se_width_idx == idx) { + fmt->elide = elide; + break; + } + } +} + +static bool __get_elide(struct strlist *list, const char *list_name, FILE *fp) { if (list && strlist__nr_entries(list) == 1) { if (fp != NULL) fprintf(fp, "# %s: %s\n", list_name, strlist__entry(list, 0)->s); - se->elide = true; + return true; } + return false; +} + +static bool get_elide(int idx, FILE *output) +{ + switch (idx) { + case HISTC_SYMBOL: + return __get_elide(symbol_conf.sym_list, "symbol", output); + case HISTC_DSO: + return __get_elide(symbol_conf.dso_list, "dso", output); + case HISTC_COMM: + return __get_elide(symbol_conf.comm_list, "comm", output); + default: + break; + } + + if (sort__mode != SORT_MODE__BRANCH) + return false; + + switch (idx) { + case HISTC_SYMBOL_FROM: + return __get_elide(symbol_conf.sym_from_list, "sym_from", output); + case HISTC_SYMBOL_TO: + return __get_elide(symbol_conf.sym_to_list, "sym_to", output); + case HISTC_DSO_FROM: + return __get_elide(symbol_conf.dso_from_list, "dso_from", output); + case HISTC_DSO_TO: + return __get_elide(symbol_conf.dso_to_list, "dso_to", output); + default: + break; + } + + return false; } void sort__setup_elide(FILE *output) { - struct sort_entry *se; + struct perf_hpp_fmt *fmt; + struct hpp_sort_entry *hse; + + perf_hpp__for_each_format(fmt) { + if (!perf_hpp__is_sort_entry(fmt)) + continue; - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "dso", output); - sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, - "comm", output); - sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, - "symbol", output); - - if (sort__mode == SORT_MODE__BRANCH) { - sort_entry__setup_elide(&sort_dso_from, - symbol_conf.dso_from_list, - "dso_from", output); - sort_entry__setup_elide(&sort_dso_to, - symbol_conf.dso_to_list, - "dso_to", output); - sort_entry__setup_elide(&sort_sym_from, - symbol_conf.sym_from_list, - "sym_from", output); - sort_entry__setup_elide(&sort_sym_to, - symbol_conf.sym_to_list, - "sym_to", output); - } else if (sort__mode == SORT_MODE__MEMORY) { - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "symbol_daddr", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "dso_daddr", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "mem", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "local_weight", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "tlb", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "snoop", output); + hse = container_of(fmt, struct hpp_sort_entry, hpp); + fmt->elide = get_elide(hse->se->se_width_idx, output); } /* * It makes no sense to elide all of sort entries. * Just revert them to show up again. */ - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (!se->elide) + perf_hpp__for_each_format(fmt) { + if (!perf_hpp__is_sort_entry(fmt)) + continue; + + if (!fmt->elide) return; } - list_for_each_entry(se, &hist_entry__sort_list, list) - se->elide = false; + perf_hpp__for_each_format(fmt) { + if (!perf_hpp__is_sort_entry(fmt)) + continue; + + fmt->elide = false; + } +} + +static int output_field_add(char *tok) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(common_sort_dimensions); i++) { + struct sort_dimension *sd = &common_sort_dimensions[i]; + + if (strncasecmp(tok, sd->name, strlen(tok))) + continue; + + return __sort_dimension__add_output(sd); + } + + for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) { + struct hpp_dimension *hd = &hpp_sort_dimensions[i]; + + if (strncasecmp(tok, hd->name, strlen(tok))) + continue; + + return __hpp_dimension__add_output(hd); + } + + for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++) { + struct sort_dimension *sd = &bstack_sort_dimensions[i]; + + if (strncasecmp(tok, sd->name, strlen(tok))) + continue; + + return __sort_dimension__add_output(sd); + } + + for (i = 0; i < ARRAY_SIZE(memory_sort_dimensions); i++) { + struct sort_dimension *sd = &memory_sort_dimensions[i]; + + if (strncasecmp(tok, sd->name, strlen(tok))) + continue; + + return __sort_dimension__add_output(sd); + } + + return -ESRCH; +} + +static void reset_dimensions(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(common_sort_dimensions); i++) + common_sort_dimensions[i].taken = 0; + + for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) + hpp_sort_dimensions[i].taken = 0; + + for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++) + bstack_sort_dimensions[i].taken = 0; + + for (i = 0; i < ARRAY_SIZE(memory_sort_dimensions); i++) + memory_sort_dimensions[i].taken = 0; +} + +static int __setup_output_field(void) +{ + char *tmp, *tok, *str; + int ret = 0; + + if (field_order == NULL) + return 0; + + reset_dimensions(); + + str = strdup(field_order); + if (str == NULL) { + error("Not enough memory to setup output fields"); + return -ENOMEM; + } + + for (tok = strtok_r(str, ", ", &tmp); + tok; tok = strtok_r(NULL, ", ", &tmp)) { + ret = output_field_add(tok); + if (ret == -EINVAL) { + error("Invalid --fields key: `%s'", tok); + break; + } else if (ret == -ESRCH) { + error("Unknown --fields key: `%s'", tok); + break; + } + } + + free(str); + return ret; +} + +int setup_sorting(void) +{ + int err; + + err = __setup_sorting(); + if (err < 0) + return err; + + if (parent_pattern != default_parent_pattern) { + err = sort_dimension__add("parent"); + if (err < 0) + return err; + } + + reset_dimensions(); + + /* + * perf diff doesn't use default hpp output fields. + */ + if (sort__mode != SORT_MODE__DIFF) + perf_hpp__init(); + + err = __setup_output_field(); + if (err < 0) + return err; + + /* copy sort keys to output fields */ + perf_hpp__setup_output_field(); + /* and then copy output fields to sort keys */ + perf_hpp__append_sort_keys(); + + return 0; +} + +void reset_output_field(void) +{ + sort__need_collapse = 0; + sort__has_parent = 0; + sort__has_sym = 0; + sort__has_dso = 0; + + field_order = NULL; + sort_order = NULL; + + reset_dimensions(); + perf_hpp__reset_output_field(); } diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 43e5ff42a609..041f0c9cea2b 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -20,11 +20,12 @@ #include "parse-options.h" #include "parse-events.h" - +#include "hist.h" #include "thread.h" extern regex_t parent_regex; extern const char *sort_order; +extern const char *field_order; extern const char default_parent_pattern[]; extern const char *parent_pattern; extern const char default_sort_order[]; @@ -81,12 +82,14 @@ struct hist_entry { struct list_head head; } pairs; struct he_stat stat; + struct he_stat *stat_acc; struct map_symbol ms; struct thread *thread; struct comm *comm; u64 ip; u64 transaction; s32 cpu; + u8 cpumode; struct hist_entry_diff diff; @@ -129,10 +132,27 @@ static inline void hist_entry__add_pair(struct hist_entry *pair, list_add_tail(&pair->pairs.node, &he->pairs.head); } +static inline float hist_entry__get_percent_limit(struct hist_entry *he) +{ + u64 period = he->stat.period; + u64 total_period = hists__total_period(he->hists); + + if (unlikely(total_period == 0)) + return 0; + + if (symbol_conf.cumulate_callchain) + period = he->stat_acc->period; + + return period * 100.0 / total_period; +} + + enum sort_mode { SORT_MODE__NORMAL, SORT_MODE__BRANCH, SORT_MODE__MEMORY, + SORT_MODE__TOP, + SORT_MODE__DIFF, }; enum sort_type { @@ -166,6 +186,7 @@ enum sort_type { SORT_MEM_TLB, SORT_MEM_LVL, SORT_MEM_SNOOP, + SORT_MEM_DCACHELINE, }; /* @@ -179,18 +200,21 @@ struct sort_entry { int64_t (*se_cmp)(struct hist_entry *, struct hist_entry *); int64_t (*se_collapse)(struct hist_entry *, struct hist_entry *); + int64_t (*se_sort)(struct hist_entry *, struct hist_entry *); int (*se_snprintf)(struct hist_entry *he, char *bf, size_t size, unsigned int width); u8 se_width_idx; - bool elide; }; extern struct sort_entry sort_thread; extern struct list_head hist_entry__sort_list; int setup_sorting(void); +int setup_output_field(void); +void reset_output_field(void); extern int sort_dimension__add(const char *); void sort__setup_elide(FILE *fp); +void perf_hpp__set_elide(int idx, bool elide); int report_parse_ignore_callees_opt(const struct option *opt, const char *arg, int unset); diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h index ae8ccd7227cf..5667fc3e39cf 100644 --- a/tools/perf/util/stat.h +++ b/tools/perf/util/stat.h @@ -1,7 +1,7 @@ #ifndef __PERF_STATS_H #define __PERF_STATS_H -#include "types.h" +#include <linux/types.h> struct stats { diff --git a/tools/perf/util/svghelper.c b/tools/perf/util/svghelper.c index 43262b83c541..283d3e73e2f2 100644 --- a/tools/perf/util/svghelper.c +++ b/tools/perf/util/svghelper.c @@ -17,7 +17,7 @@ #include <stdlib.h> #include <unistd.h> #include <string.h> -#include <linux/bitops.h> +#include <linux/bitmap.h> #include "perf.h" #include "svghelper.h" @@ -30,6 +30,7 @@ static u64 turbo_frequency, max_freq; #define SLOT_MULT 30.0 #define SLOT_HEIGHT 25.0 +#define SLOT_HALF (SLOT_HEIGHT / 2) int svg_page_width = 1000; u64 svg_highlight; @@ -114,8 +115,14 @@ void open_svg(const char *filename, int cpus, int rows, u64 start, u64 end) fprintf(svgfile, " rect { stroke-width: 1; }\n"); fprintf(svgfile, " rect.process { fill:rgb(180,180,180); fill-opacity:0.9; stroke-width:1; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.process2 { fill:rgb(180,180,180); fill-opacity:0.9; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); + fprintf(svgfile, " rect.process3 { fill:rgb(180,180,180); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.sample { fill:rgb( 0, 0,255); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.sample_hi{ fill:rgb(255,128, 0); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); + fprintf(svgfile, " rect.error { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); + fprintf(svgfile, " rect.net { fill:rgb( 0,128, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); + fprintf(svgfile, " rect.disk { fill:rgb( 0, 0,255); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); + fprintf(svgfile, " rect.sync { fill:rgb(128,128, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); + fprintf(svgfile, " rect.poll { fill:rgb( 0,128,128); fill-opacity:0.2; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.blocked { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.waiting { fill:rgb(224,214, 0); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.WAITING { fill:rgb(255,214, 48); fill-opacity:0.6; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); @@ -132,12 +139,81 @@ void open_svg(const char *filename, int cpus, int rows, u64 start, u64 end) fprintf(svgfile, " ]]>\n </style>\n</defs>\n"); } +static double normalize_height(double height) +{ + if (height < 0.25) + return 0.25; + else if (height < 0.50) + return 0.50; + else if (height < 0.75) + return 0.75; + else + return 0.100; +} + +void svg_ubox(int Yslot, u64 start, u64 end, double height, const char *type, int fd, int err, int merges) +{ + double w = time2pixels(end) - time2pixels(start); + height = normalize_height(height); + + if (!svgfile) + return; + + fprintf(svgfile, "<g>\n"); + fprintf(svgfile, "<title>fd=%d error=%d merges=%d</title>\n", fd, err, merges); + fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n", + time2pixels(start), + w, + Yslot * SLOT_MULT, + SLOT_HALF * height, + type); + fprintf(svgfile, "</g>\n"); +} + +void svg_lbox(int Yslot, u64 start, u64 end, double height, const char *type, int fd, int err, int merges) +{ + double w = time2pixels(end) - time2pixels(start); + height = normalize_height(height); + + if (!svgfile) + return; + + fprintf(svgfile, "<g>\n"); + fprintf(svgfile, "<title>fd=%d error=%d merges=%d</title>\n", fd, err, merges); + fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n", + time2pixels(start), + w, + Yslot * SLOT_MULT + SLOT_HEIGHT - SLOT_HALF * height, + SLOT_HALF * height, + type); + fprintf(svgfile, "</g>\n"); +} + +void svg_fbox(int Yslot, u64 start, u64 end, double height, const char *type, int fd, int err, int merges) +{ + double w = time2pixels(end) - time2pixels(start); + height = normalize_height(height); + + if (!svgfile) + return; + + fprintf(svgfile, "<g>\n"); + fprintf(svgfile, "<title>fd=%d error=%d merges=%d</title>\n", fd, err, merges); + fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n", + time2pixels(start), + w, + Yslot * SLOT_MULT + SLOT_HEIGHT - SLOT_HEIGHT * height, + SLOT_HEIGHT * height, + type); + fprintf(svgfile, "</g>\n"); +} + void svg_box(int Yslot, u64 start, u64 end, const char *type) { if (!svgfile) return; - fprintf(svgfile, "<rect x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\" class=\"%s\"/>\n", + fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n", time2pixels(start), time2pixels(end)-time2pixels(start), Yslot * SLOT_MULT, SLOT_HEIGHT, type); } @@ -174,7 +250,7 @@ void svg_running(int Yslot, int cpu, u64 start, u64 end, const char *backtrace) cpu, time_to_string(end - start)); if (backtrace) fprintf(svgfile, "<desc>Switched because:\n%s</desc>\n", backtrace); - fprintf(svgfile, "<rect x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\" class=\"%s\"/>\n", + fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"%s\"/>\n", time2pixels(start), time2pixels(end)-time2pixels(start), Yslot * SLOT_MULT, SLOT_HEIGHT, type); @@ -186,7 +262,7 @@ void svg_running(int Yslot, int cpu, u64 start, u64 end, const char *backtrace) text_size = round_text_size(text_size); if (text_size > MIN_TEXT_SIZE) - fprintf(svgfile, "<text x=\"%1.8f\" y=\"%1.8f\" font-size=\"%1.8fpt\">%i</text>\n", + fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\" font-size=\"%.8fpt\">%i</text>\n", time2pixels(start), Yslot * SLOT_MULT + SLOT_HEIGHT - 1, text_size, cpu + 1); fprintf(svgfile, "</g>\n"); @@ -202,10 +278,10 @@ static char *time_to_string(u64 duration) return text; if (duration < 1000 * 1000) { /* less than 1 msec */ - sprintf(text, "%4.1f us", duration / 1000.0); + sprintf(text, "%.1f us", duration / 1000.0); return text; } - sprintf(text, "%4.1f ms", duration / 1000.0 / 1000); + sprintf(text, "%.1f ms", duration / 1000.0 / 1000); return text; } @@ -233,14 +309,14 @@ void svg_waiting(int Yslot, int cpu, u64 start, u64 end, const char *backtrace) font_size = round_text_size(font_size); - fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\">\n", time2pixels(start), Yslot * SLOT_MULT); + fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\">\n", time2pixels(start), Yslot * SLOT_MULT); fprintf(svgfile, "<title>#%d waiting %s</title>\n", cpu, time_to_string(end - start)); if (backtrace) fprintf(svgfile, "<desc>Waiting on:\n%s</desc>\n", backtrace); - fprintf(svgfile, "<rect x=\"0\" width=\"%4.8f\" y=\"0\" height=\"%4.1f\" class=\"%s\"/>\n", + fprintf(svgfile, "<rect x=\"0\" width=\"%.8f\" y=\"0\" height=\"%.1f\" class=\"%s\"/>\n", time2pixels(end)-time2pixels(start), SLOT_HEIGHT, style); if (font_size > MIN_TEXT_SIZE) - fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%1.8fpt\"> %s</text>\n", + fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%.8fpt\"> %s</text>\n", font_size, text); fprintf(svgfile, "</g>\n"); } @@ -289,16 +365,16 @@ void svg_cpu_box(int cpu, u64 __max_freq, u64 __turbo_freq) fprintf(svgfile, "<g>\n"); - fprintf(svgfile, "<rect x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\" class=\"cpu\"/>\n", + fprintf(svgfile, "<rect x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\" class=\"cpu\"/>\n", time2pixels(first_time), time2pixels(last_time)-time2pixels(first_time), cpu2y(cpu), SLOT_MULT+SLOT_HEIGHT); sprintf(cpu_string, "CPU %i", (int)cpu); - fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\">%s</text>\n", + fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\">%s</text>\n", 10+time2pixels(first_time), cpu2y(cpu) + SLOT_HEIGHT/2, cpu_string); - fprintf(svgfile, "<text transform=\"translate(%4.8f,%4.8f)\" font-size=\"1.25pt\">%s</text>\n", + fprintf(svgfile, "<text transform=\"translate(%.8f,%.8f)\" font-size=\"1.25pt\">%s</text>\n", 10+time2pixels(first_time), cpu2y(cpu) + SLOT_MULT + SLOT_HEIGHT - 4, cpu_model()); fprintf(svgfile, "</g>\n"); @@ -319,11 +395,11 @@ void svg_process(int cpu, u64 start, u64 end, int pid, const char *name, const c else type = "sample"; - fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\">\n", time2pixels(start), cpu2y(cpu)); + fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\">\n", time2pixels(start), cpu2y(cpu)); fprintf(svgfile, "<title>%d %s running %s</title>\n", pid, name, time_to_string(end - start)); if (backtrace) fprintf(svgfile, "<desc>Switched because:\n%s</desc>\n", backtrace); - fprintf(svgfile, "<rect x=\"0\" width=\"%4.8f\" y=\"0\" height=\"%4.1f\" class=\"%s\"/>\n", + fprintf(svgfile, "<rect x=\"0\" width=\"%.8f\" y=\"0\" height=\"%.1f\" class=\"%s\"/>\n", time2pixels(end)-time2pixels(start), SLOT_MULT+SLOT_HEIGHT, type); width = time2pixels(end)-time2pixels(start); if (width > 6) @@ -332,7 +408,7 @@ void svg_process(int cpu, u64 start, u64 end, int pid, const char *name, const c width = round_text_size(width); if (width > MIN_TEXT_SIZE) - fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%3.8fpt\">%s</text>\n", + fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%.8fpt\">%s</text>\n", width, name); fprintf(svgfile, "</g>\n"); @@ -353,7 +429,7 @@ void svg_cstate(int cpu, u64 start, u64 end, int type) type = 6; sprintf(style, "c%i", type); - fprintf(svgfile, "<rect class=\"%s\" x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\"/>\n", + fprintf(svgfile, "<rect class=\"%s\" x=\"%.8f\" width=\"%.8f\" y=\"%.1f\" height=\"%.1f\"/>\n", style, time2pixels(start), time2pixels(end)-time2pixels(start), cpu2y(cpu), SLOT_MULT+SLOT_HEIGHT); @@ -365,7 +441,7 @@ void svg_cstate(int cpu, u64 start, u64 end, int type) width = round_text_size(width); if (width > MIN_TEXT_SIZE) - fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\" font-size=\"%3.8fpt\">C%i</text>\n", + fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\" font-size=\"%.8fpt\">C%i</text>\n", time2pixels(start), cpu2y(cpu)+width, width, type); fprintf(svgfile, "</g>\n"); @@ -407,9 +483,9 @@ void svg_pstate(int cpu, u64 start, u64 end, u64 freq) if (max_freq) height = freq * 1.0 / max_freq * (SLOT_HEIGHT + SLOT_MULT); height = 1 + cpu2y(cpu) + SLOT_MULT + SLOT_HEIGHT - height; - fprintf(svgfile, "<line x1=\"%4.8f\" x2=\"%4.8f\" y1=\"%4.1f\" y2=\"%4.1f\" class=\"pstate\"/>\n", + fprintf(svgfile, "<line x1=\"%.8f\" x2=\"%.8f\" y1=\"%.1f\" y2=\"%.1f\" class=\"pstate\"/>\n", time2pixels(start), time2pixels(end), height, height); - fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\" font-size=\"0.25pt\">%s</text>\n", + fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\" font-size=\"0.25pt\">%s</text>\n", time2pixels(start), height+0.9, HzToHuman(freq)); fprintf(svgfile, "</g>\n"); @@ -435,32 +511,32 @@ void svg_partial_wakeline(u64 start, int row1, char *desc1, int row2, char *desc if (row1 < row2) { if (row1) { - fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", + fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/32); if (desc2) - fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s ></text></g>\n", + fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s ></text></g>\n", time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT + SLOT_HEIGHT/48, desc2); } if (row2) { - fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", + fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", time2pixels(start), row2 * SLOT_MULT - SLOT_MULT/32, time2pixels(start), row2 * SLOT_MULT); if (desc1) - fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s ></text></g>\n", + fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s ></text></g>\n", time2pixels(start), row2 * SLOT_MULT - SLOT_MULT/32, desc1); } } else { if (row2) { - fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", + fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/32); if (desc1) - fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s <</text></g>\n", + fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s <</text></g>\n", time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/48, desc1); } if (row1) { - fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", + fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", time2pixels(start), row1 * SLOT_MULT - SLOT_MULT/32, time2pixels(start), row1 * SLOT_MULT); if (desc2) - fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s <</text></g>\n", + fprintf(svgfile, "<g transform=\"translate(%.8f,%.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s <</text></g>\n", time2pixels(start), row1 * SLOT_MULT - SLOT_HEIGHT/32, desc2); } } @@ -468,7 +544,7 @@ void svg_partial_wakeline(u64 start, int row1, char *desc1, int row2, char *desc if (row2 > row1) height += SLOT_HEIGHT; if (row1) - fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n", + fprintf(svgfile, "<circle cx=\"%.8f\" cy=\"%.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n", time2pixels(start), height); fprintf(svgfile, "</g>\n"); @@ -488,16 +564,16 @@ void svg_wakeline(u64 start, int row1, int row2, const char *backtrace) fprintf(svgfile, "<desc>%s</desc>\n", backtrace); if (row1 < row2) - fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", + fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row2 * SLOT_MULT); else - fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", + fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n", time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row1 * SLOT_MULT); height = row1 * SLOT_MULT; if (row2 > row1) height += SLOT_HEIGHT; - fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n", + fprintf(svgfile, "<circle cx=\"%.8f\" cy=\"%.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n", time2pixels(start), height); fprintf(svgfile, "</g>\n"); @@ -515,9 +591,9 @@ void svg_interrupt(u64 start, int row, const char *backtrace) if (backtrace) fprintf(svgfile, "<desc>%s</desc>\n", backtrace); - fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n", + fprintf(svgfile, "<circle cx=\"%.8f\" cy=\"%.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n", time2pixels(start), row * SLOT_MULT); - fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n", + fprintf(svgfile, "<circle cx=\"%.8f\" cy=\"%.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n", time2pixels(start), row * SLOT_MULT + SLOT_HEIGHT); fprintf(svgfile, "</g>\n"); @@ -528,7 +604,7 @@ void svg_text(int Yslot, u64 start, const char *text) if (!svgfile) return; - fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\">%s</text>\n", + fprintf(svgfile, "<text x=\"%.8f\" y=\"%.8f\">%s</text>\n", time2pixels(start), Yslot * SLOT_MULT+SLOT_HEIGHT/2, text); } @@ -537,12 +613,26 @@ static void svg_legenda_box(int X, const char *text, const char *style) double boxsize; boxsize = SLOT_HEIGHT / 2; - fprintf(svgfile, "<rect x=\"%i\" width=\"%4.8f\" y=\"0\" height=\"%4.1f\" class=\"%s\"/>\n", + fprintf(svgfile, "<rect x=\"%i\" width=\"%.8f\" y=\"0\" height=\"%.1f\" class=\"%s\"/>\n", X, boxsize, boxsize, style); - fprintf(svgfile, "<text transform=\"translate(%4.8f, %4.8f)\" font-size=\"%4.8fpt\">%s</text>\n", + fprintf(svgfile, "<text transform=\"translate(%.8f, %.8f)\" font-size=\"%.8fpt\">%s</text>\n", X + boxsize + 5, boxsize, 0.8 * boxsize, text); } +void svg_io_legenda(void) +{ + if (!svgfile) + return; + + fprintf(svgfile, "<g>\n"); + svg_legenda_box(0, "Disk", "disk"); + svg_legenda_box(100, "Network", "net"); + svg_legenda_box(200, "Sync", "sync"); + svg_legenda_box(300, "Poll", "poll"); + svg_legenda_box(400, "Error", "error"); + fprintf(svgfile, "</g>\n"); +} + void svg_legenda(void) { if (!svgfile) @@ -559,7 +649,7 @@ void svg_legenda(void) fprintf(svgfile, "</g>\n"); } -void svg_time_grid(void) +void svg_time_grid(double min_thickness) { u64 i; @@ -579,8 +669,10 @@ void svg_time_grid(void) color = 128; } - fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%" PRIu64 "\" style=\"stroke:rgb(%i,%i,%i);stroke-width:%1.3f\"/>\n", - time2pixels(i), SLOT_MULT/2, time2pixels(i), total_height, color, color, color, thickness); + if (thickness >= min_thickness) + fprintf(svgfile, "<line x1=\"%.8f\" y1=\"%.2f\" x2=\"%.8f\" y2=\"%" PRIu64 "\" style=\"stroke:rgb(%i,%i,%i);stroke-width:%.3f\"/>\n", + time2pixels(i), SLOT_MULT/2, time2pixels(i), + total_height, color, color, color, thickness); i += 10000000; } diff --git a/tools/perf/util/svghelper.h b/tools/perf/util/svghelper.h index f7b4d6e699ea..9292a5291445 100644 --- a/tools/perf/util/svghelper.h +++ b/tools/perf/util/svghelper.h @@ -1,9 +1,12 @@ #ifndef __PERF_SVGHELPER_H #define __PERF_SVGHELPER_H -#include "types.h" +#include <linux/types.h> extern void open_svg(const char *filename, int cpus, int rows, u64 start, u64 end); +extern void svg_ubox(int Yslot, u64 start, u64 end, double height, const char *type, int fd, int err, int merges); +extern void svg_lbox(int Yslot, u64 start, u64 end, double height, const char *type, int fd, int err, int merges); +extern void svg_fbox(int Yslot, u64 start, u64 end, double height, const char *type, int fd, int err, int merges); extern void svg_box(int Yslot, u64 start, u64 end, const char *type); extern void svg_blocked(int Yslot, int cpu, u64 start, u64 end, const char *backtrace); extern void svg_running(int Yslot, int cpu, u64 start, u64 end, const char *backtrace); @@ -16,7 +19,8 @@ extern void svg_cstate(int cpu, u64 start, u64 end, int type); extern void svg_pstate(int cpu, u64 start, u64 end, u64 freq); -extern void svg_time_grid(void); +extern void svg_time_grid(double min_thickness); +extern void svg_io_legenda(void); extern void svg_legenda(void); extern void svg_wakeline(u64 start, int row1, int row2, const char *backtrace); extern void svg_partial_wakeline(u64 start, int row1, char *desc1, int row2, char *desc2, const char *backtrace); diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index 6864661a79dd..d75349979e65 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -49,7 +49,8 @@ static inline uint8_t elf_sym__type(const GElf_Sym *sym) static inline int elf_sym__is_function(const GElf_Sym *sym) { - return elf_sym__type(sym) == STT_FUNC && + return (elf_sym__type(sym) == STT_FUNC || + elf_sym__type(sym) == STT_GNU_IFUNC) && sym->st_name != 0 && sym->st_shndx != SHN_UNDEF; } @@ -598,6 +599,8 @@ int symsrc__init(struct symsrc *ss, struct dso *dso, const char *name, goto out_elf_end; } + ss->is_64_bit = (gelf_getclass(elf) == ELFCLASS64); + ss->symtab = elf_section_by_name(elf, &ehdr, &ss->symshdr, ".symtab", NULL); if (ss->symshdr.sh_type != SHT_SYMTAB) @@ -619,7 +622,7 @@ int symsrc__init(struct symsrc *ss, struct dso *dso, const char *name, GElf_Shdr shdr; ss->adjust_symbols = (ehdr.e_type == ET_EXEC || ehdr.e_type == ET_REL || - is_vdso_map(dso->short_name) || + dso__is_vdso(dso) || elf_section_by_name(elf, &ehdr, &shdr, ".gnu.prelink_undo", NULL) != NULL); @@ -698,6 +701,7 @@ int dso__load_sym(struct dso *dso, struct map *map, bool remap_kernel = false, adjust_kernel_syms = false; dso->symtab_type = syms_ss->type; + dso->is_64_bit = syms_ss->is_64_bit; dso->rel = syms_ss->ehdr.e_type == ET_REL; /* @@ -1024,6 +1028,39 @@ int file__read_maps(int fd, bool exe, mapfn_t mapfn, void *data, return err; } +enum dso_type dso__type_fd(int fd) +{ + enum dso_type dso_type = DSO__TYPE_UNKNOWN; + GElf_Ehdr ehdr; + Elf_Kind ek; + Elf *elf; + + elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); + if (elf == NULL) + goto out; + + ek = elf_kind(elf); + if (ek != ELF_K_ELF) + goto out_end; + + if (gelf_getclass(elf) == ELFCLASS64) { + dso_type = DSO__TYPE_64BIT; + goto out_end; + } + + if (gelf_getehdr(elf, &ehdr) == NULL) + goto out_end; + + if (ehdr.e_machine == EM_X86_64) + dso_type = DSO__TYPE_X32BIT; + else + dso_type = DSO__TYPE_32BIT; +out_end: + elf_end(elf); +out: + return dso_type; +} + static int copy_bytes(int from, off_t from_offs, int to, off_t to_offs, u64 len) { ssize_t r; diff --git a/tools/perf/util/symbol-minimal.c b/tools/perf/util/symbol-minimal.c index bd15f490d04f..c9541fea9514 100644 --- a/tools/perf/util/symbol-minimal.c +++ b/tools/perf/util/symbol-minimal.c @@ -288,6 +288,44 @@ int dso__synthesize_plt_symbols(struct dso *dso __maybe_unused, return 0; } +static int fd__is_64_bit(int fd) +{ + u8 e_ident[EI_NIDENT]; + + if (lseek(fd, 0, SEEK_SET)) + return -1; + + if (readn(fd, e_ident, sizeof(e_ident)) != sizeof(e_ident)) + return -1; + + if (memcmp(e_ident, ELFMAG, SELFMAG) || + e_ident[EI_VERSION] != EV_CURRENT) + return -1; + + return e_ident[EI_CLASS] == ELFCLASS64; +} + +enum dso_type dso__type_fd(int fd) +{ + Elf64_Ehdr ehdr; + int ret; + + ret = fd__is_64_bit(fd); + if (ret < 0) + return DSO__TYPE_UNKNOWN; + + if (ret) + return DSO__TYPE_64BIT; + + if (readn(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) + return DSO__TYPE_UNKNOWN; + + if (ehdr.e_machine == EM_X86_64) + return DSO__TYPE_X32BIT; + + return DSO__TYPE_32BIT; +} + int dso__load_sym(struct dso *dso, struct map *map __maybe_unused, struct symsrc *ss, struct symsrc *runtime_ss __maybe_unused, @@ -295,6 +333,11 @@ int dso__load_sym(struct dso *dso, struct map *map __maybe_unused, int kmodule __maybe_unused) { unsigned char *build_id[BUILD_ID_SIZE]; + int ret; + + ret = fd__is_64_bit(ss->fd); + if (ret >= 0) + dso->is_64_bit = ret; if (filename__read_build_id(ss->name, build_id, BUILD_ID_SIZE) > 0) { dso__set_build_id(dso, build_id); diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 95e249779931..eb06746b06b2 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -29,11 +29,13 @@ int vmlinux_path__nr_entries; char **vmlinux_path; struct symbol_conf symbol_conf = { - .use_modules = true, - .try_vmlinux_path = true, - .annotate_src = true, - .demangle = true, - .symfs = "", + .use_modules = true, + .try_vmlinux_path = true, + .annotate_src = true, + .demangle = true, + .cumulate_callchain = true, + .show_hist_headers = true, + .symfs = "", }; static enum dso_binary_type binary_type_symtab[] = { @@ -340,6 +342,16 @@ static struct symbol *symbols__first(struct rb_root *symbols) return NULL; } +static struct symbol *symbols__next(struct symbol *sym) +{ + struct rb_node *n = rb_next(&sym->rb_node); + + if (n) + return rb_entry(n, struct symbol, rb_node); + + return NULL; +} + struct symbol_name_rb_node { struct rb_node rb_node; struct symbol sym; @@ -410,11 +422,16 @@ struct symbol *dso__find_symbol(struct dso *dso, return symbols__find(&dso->symbols[type], addr); } -static struct symbol *dso__first_symbol(struct dso *dso, enum map_type type) +struct symbol *dso__first_symbol(struct dso *dso, enum map_type type) { return symbols__first(&dso->symbols[type]); } +struct symbol *dso__next_symbol(struct symbol *sym) +{ + return symbols__next(sym); +} + struct symbol *dso__find_symbol_by_name(struct dso *dso, enum map_type type, const char *name) { @@ -1063,6 +1080,7 @@ static int dso__load_kcore(struct dso *dso, struct map *map, &is_64_bit); if (err) goto out_err; + dso->is_64_bit = is_64_bit; if (list_empty(&md.maps)) { err = -EINVAL; @@ -1661,6 +1679,7 @@ do_kallsyms: free(kallsyms_allocated_filename); if (err > 0 && !dso__is_kcore(dso)) { + dso->binary_type = DSO_BINARY_TYPE__KALLSYMS; dso__set_long_name(dso, "[kernel.kallsyms]", false); map__fixup_start(map); map__fixup_end(map); @@ -1708,6 +1727,7 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map, if (err > 0) pr_debug("Using %s for symbols\n", kallsyms_filename); if (err > 0 && !dso__is_kcore(dso)) { + dso->binary_type = DSO_BINARY_TYPE__GUEST_KALLSYMS; machine__mmap_name(machine, path, sizeof(path)); dso__set_long_name(dso, strdup(path), true); map__fixup_start(map); diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 501e4e722e8e..e7295e93cff9 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -12,6 +12,7 @@ #include <byteswap.h> #include <libgen.h> #include "build-id.h" +#include "event.h" #ifdef HAVE_LIBELF_SUPPORT #include <libelf.h> @@ -108,6 +109,7 @@ struct symbol_conf { show_nr_samples, show_total_period, use_callchain, + cumulate_callchain, exclude_other, show_cpu_utilization, initialized, @@ -115,7 +117,9 @@ struct symbol_conf { annotate_asm_raw, annotate_src, event_group, - demangle; + demangle, + filter_relative, + show_hist_headers; const char *vmlinux_name, *kallsyms_name, *source_prefix, @@ -212,6 +216,7 @@ struct symsrc { GElf_Shdr dynshdr; bool adjust_symbols; + bool is_64_bit; #endif }; @@ -235,6 +240,11 @@ struct symbol *dso__find_symbol(struct dso *dso, enum map_type type, struct symbol *dso__find_symbol_by_name(struct dso *dso, enum map_type type, const char *name); +struct symbol *dso__first_symbol(struct dso *dso, enum map_type type); +struct symbol *dso__next_symbol(struct symbol *sym); + +enum dso_type dso__type_fd(int fd); + int filename__read_build_id(const char *filename, void *bf, size_t size); int sysfs__read_build_id(const char *filename, void *bf, size_t size); int modules__parse(const char *filename, void *arg, diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index 3ce0498bdae6..12c7a253a63c 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -8,6 +8,22 @@ #include "debug.h" #include "comm.h" +int thread__init_map_groups(struct thread *thread, struct machine *machine) +{ + struct thread *leader; + pid_t pid = thread->pid_; + + if (pid == thread->tid || pid == -1) { + thread->mg = map_groups__new(); + } else { + leader = machine__findnew_thread(machine, pid, pid); + if (leader) + thread->mg = map_groups__get(leader->mg); + } + + return thread->mg ? 0 : -1; +} + struct thread *thread__new(pid_t pid, pid_t tid) { char *comm_str; @@ -15,10 +31,10 @@ struct thread *thread__new(pid_t pid, pid_t tid) struct thread *thread = zalloc(sizeof(*thread)); if (thread != NULL) { - map_groups__init(&thread->mg); thread->pid_ = pid; thread->tid = tid; thread->ppid = -1; + thread->cpu = -1; INIT_LIST_HEAD(&thread->comm_list); comm_str = malloc(32); @@ -45,7 +61,10 @@ void thread__delete(struct thread *thread) { struct comm *comm, *tmp; - map_groups__exit(&thread->mg); + if (thread->mg) { + map_groups__put(thread->mg); + thread->mg = NULL; + } list_for_each_entry_safe(comm, tmp, &thread->comm_list, list) { list_del(&comm->list); comm__free(comm); @@ -111,18 +130,35 @@ int thread__comm_len(struct thread *thread) size_t thread__fprintf(struct thread *thread, FILE *fp) { return fprintf(fp, "Thread %d %s\n", thread->tid, thread__comm_str(thread)) + - map_groups__fprintf(&thread->mg, verbose, fp); + map_groups__fprintf(thread->mg, fp); } void thread__insert_map(struct thread *thread, struct map *map) { - map_groups__fixup_overlappings(&thread->mg, map, verbose, stderr); - map_groups__insert(&thread->mg, map); + map_groups__fixup_overlappings(thread->mg, map, stderr); + map_groups__insert(thread->mg, map); +} + +static int thread__clone_map_groups(struct thread *thread, + struct thread *parent) +{ + int i; + + /* This is new thread, we share map groups for process. */ + if (thread->pid_ == parent->pid_) + return 0; + + /* But this one is new process, copy maps. */ + for (i = 0; i < MAP__NR_TYPES; ++i) + if (map_groups__clone(thread->mg, parent->mg, i) < 0) + return -ENOMEM; + + return 0; } int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp) { - int i, err; + int err; if (parent->comm_set) { const char *comm = thread__comm_str(parent); @@ -134,13 +170,8 @@ int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp) thread->comm_set = true; } - for (i = 0; i < MAP__NR_TYPES; ++i) - if (map_groups__clone(&thread->mg, &parent->mg, i) < 0) - return -ENOMEM; - thread->ppid = parent->tid; - - return 0; + return thread__clone_map_groups(thread, parent); } void thread__find_cpumode_addr_location(struct thread *thread, diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h index 9b29f085aede..716b7723cce2 100644 --- a/tools/perf/util/thread.h +++ b/tools/perf/util/thread.h @@ -13,10 +13,11 @@ struct thread { struct rb_node rb_node; struct list_head node; }; - struct map_groups mg; + struct map_groups *mg; pid_t pid_; /* Not all tools update this */ pid_t tid; pid_t ppid; + int cpu; char shortname[3]; bool comm_set; bool dead; /* if set thread has exited */ @@ -30,6 +31,7 @@ struct machine; struct comm; struct thread *thread__new(pid_t pid, pid_t tid); +int thread__init_map_groups(struct thread *thread, struct machine *machine); void thread__delete(struct thread *thread); static inline void thread__exited(struct thread *thread) { diff --git a/tools/perf/util/top.h b/tools/perf/util/top.h index dab14d0ad3d0..f92c37abb0a8 100644 --- a/tools/perf/util/top.h +++ b/tools/perf/util/top.h @@ -2,7 +2,7 @@ #define __PERF_TOP_H 1 #include "tool.h" -#include "types.h" +#include <linux/types.h> #include <stddef.h> #include <stdbool.h> #include <termios.h> diff --git a/tools/perf/util/trace-event-info.c b/tools/perf/util/trace-event-info.c index 7e6fcfe8b438..eb72716017ac 100644 --- a/tools/perf/util/trace-event-info.c +++ b/tools/perf/util/trace-event-info.c @@ -40,6 +40,7 @@ #include "trace-event.h" #include <api/fs/debugfs.h> #include "evsel.h" +#include "debug.h" #define VERSION "0.5" @@ -191,12 +192,10 @@ static int copy_event_system(const char *sys, struct tracepoint_path *tps) strcmp(dent->d_name, "..") == 0 || !name_in_tp_list(dent->d_name, tps)) continue; - format = malloc(strlen(sys) + strlen(dent->d_name) + 10); - if (!format) { + if (asprintf(&format, "%s/%s/format", sys, dent->d_name) < 0) { err = -ENOMEM; goto out; } - sprintf(format, "%s/%s/format", sys, dent->d_name); ret = stat(format, &st); free(format); if (ret < 0) @@ -217,12 +216,10 @@ static int copy_event_system(const char *sys, struct tracepoint_path *tps) strcmp(dent->d_name, "..") == 0 || !name_in_tp_list(dent->d_name, tps)) continue; - format = malloc(strlen(sys) + strlen(dent->d_name) + 10); - if (!format) { + if (asprintf(&format, "%s/%s/format", sys, dent->d_name) < 0) { err = -ENOMEM; goto out; } - sprintf(format, "%s/%s/format", sys, dent->d_name); ret = stat(format, &st); if (ret >= 0) { @@ -317,12 +314,10 @@ static int record_event_files(struct tracepoint_path *tps) strcmp(dent->d_name, "ftrace") == 0 || !system_in_tp_list(dent->d_name, tps)) continue; - sys = malloc(strlen(path) + strlen(dent->d_name) + 2); - if (!sys) { + if (asprintf(&sys, "%s/%s", path, dent->d_name) < 0) { err = -ENOMEM; goto out; } - sprintf(sys, "%s/%s", path, dent->d_name); ret = stat(sys, &st); if (ret >= 0) { ssize_t size = strlen(dent->d_name) + 1; diff --git a/tools/perf/util/trace-event-read.c b/tools/perf/util/trace-event-read.c index e113e180c48f..54d9e9b548a8 100644 --- a/tools/perf/util/trace-event-read.c +++ b/tools/perf/util/trace-event-read.c @@ -22,7 +22,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <getopt.h> #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> @@ -36,6 +35,7 @@ #include "../perf.h" #include "util.h" #include "trace-event.h" +#include "debug.h" static int input_fd; diff --git a/tools/perf/util/tsc.c b/tools/perf/util/tsc.c new file mode 100644 index 000000000000..4d4210d4e13d --- /dev/null +++ b/tools/perf/util/tsc.c @@ -0,0 +1,30 @@ +#include <linux/compiler.h> +#include <linux/types.h> + +#include "tsc.h" + +u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc) +{ + u64 t, quot, rem; + + t = ns - tc->time_zero; + quot = t / tc->time_mult; + rem = t % tc->time_mult; + return (quot << tc->time_shift) + + (rem << tc->time_shift) / tc->time_mult; +} + +u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc) +{ + u64 quot, rem; + + quot = cyc >> tc->time_shift; + rem = cyc & ((1 << tc->time_shift) - 1); + return tc->time_zero + quot * tc->time_mult + + ((rem * tc->time_mult) >> tc->time_shift); +} + +u64 __weak rdtsc(void) +{ + return 0; +} diff --git a/tools/perf/util/tsc.h b/tools/perf/util/tsc.h new file mode 100644 index 000000000000..a8b78f1b3243 --- /dev/null +++ b/tools/perf/util/tsc.h @@ -0,0 +1,12 @@ +#ifndef __PERF_TSC_H +#define __PERF_TSC_H + +#include <linux/types.h> + +#include "../arch/x86/util/tsc.h" + +u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc); +u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc); +u64 rdtsc(void); + +#endif diff --git a/tools/perf/util/types.h b/tools/perf/util/types.h deleted file mode 100644 index c51fa6b70a28..000000000000 --- a/tools/perf/util/types.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef __PERF_TYPES_H -#define __PERF_TYPES_H - -#include <stdint.h> - -/* - * We define u64 as uint64_t for every architecture - * so that we can print it with "%"PRIx64 without getting warnings. - */ -typedef uint64_t u64; -typedef int64_t s64; -typedef unsigned int u32; -typedef signed int s32; -typedef unsigned short u16; -typedef signed short s16; -typedef unsigned char u8; -typedef signed char s8; - -union u64_swap { - u64 val64; - u32 val32[2]; -}; - -#endif /* __PERF_TYPES_H */ diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c index 67db73ec3dab..7419768c38b1 100644 --- a/tools/perf/util/unwind-libdw.c +++ b/tools/perf/util/unwind-libdw.c @@ -3,11 +3,12 @@ #include <elfutils/libdwfl.h> #include <inttypes.h> #include <errno.h> +#include "debug.h" #include "unwind.h" #include "unwind-libdw.h" #include "machine.h" #include "thread.h" -#include "types.h" +#include <linux/types.h> #include "event.h" #include "perf_regs.h" diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c index bd5768d74f01..92b56db52471 100644 --- a/tools/perf/util/unwind-libunwind.c +++ b/tools/perf/util/unwind-libunwind.c @@ -30,6 +30,7 @@ #include "unwind.h" #include "symbol.h" #include "util.h" +#include "debug.h" extern int UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as, @@ -250,7 +251,6 @@ static int read_unwind_spec_eh_frame(struct dso *dso, struct machine *machine, /* Check the .eh_frame section for unwinding info */ offset = elf_section_offset(fd, ".eh_frame_hdr"); - close(fd); if (offset) ret = unwind_spec_ehframe(dso, machine, offset, @@ -271,7 +271,6 @@ static int read_unwind_spec_debug_frame(struct dso *dso, /* Check the .debug_frame section for unwinding info */ *offset = elf_section_offset(fd, ".debug_frame"); - close(fd); if (*offset) return 0; diff --git a/tools/perf/util/unwind.h b/tools/perf/util/unwind.h index b031316f221a..f03061260b4e 100644 --- a/tools/perf/util/unwind.h +++ b/tools/perf/util/unwind.h @@ -1,7 +1,7 @@ #ifndef __UNWIND_H #define __UNWIND_H -#include "types.h" +#include <linux/types.h> #include "event.h" #include "symbol.h" diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 9f66549562bd..e52e7461911b 100644 --- a/tools/perf/util/util.c +++ b/tools/perf/util/util.c @@ -1,5 +1,6 @@ #include "../perf.h" #include "util.h" +#include "debug.h" #include <api/fs/fs.h> #include <sys/mman.h> #ifdef HAVE_BACKTRACE_SUPPORT @@ -17,6 +18,7 @@ * XXX We need to find a better place for these things... */ unsigned int page_size; +int cacheline_size; bool test_attr__enabled; @@ -166,6 +168,8 @@ static ssize_t ion(bool is_read, int fd, void *buf, size_t n) ssize_t ret = is_read ? read(fd, buf, left) : write(fd, buf, left); + if (ret < 0 && errno == EINTR) + continue; if (ret <= 0) return ret; @@ -330,12 +334,9 @@ const char *find_tracing_dir(void) if (!debugfs) return NULL; - tracing = malloc(strlen(debugfs) + 9); - if (!tracing) + if (asprintf(&tracing, "%s/tracing", debugfs) < 0) return NULL; - sprintf(tracing, "%s/tracing", debugfs); - tracing_found = 1; return tracing; } @@ -349,11 +350,9 @@ char *get_tracing_file(const char *name) if (!tracing) return NULL; - file = malloc(strlen(tracing) + strlen(name) + 2); - if (!file) + if (asprintf(&file, "%s/%s", tracing, name) < 0) return NULL; - sprintf(file, "%s/%s", tracing, name); return file; } diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index 6995d66f225c..66864364ccb4 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -69,7 +69,7 @@ #include <sys/ioctl.h> #include <inttypes.h> #include <linux/magic.h> -#include "types.h" +#include <linux/types.h> #include <sys/ttydefaults.h> #include <api/fs/debugfs.h> #include <termios.h> @@ -304,6 +304,7 @@ char *rtrim(char *s); void dump_stack(void); extern unsigned int page_size; +extern int cacheline_size; void get_term_dimensions(struct winsize *ws); diff --git a/tools/perf/util/values.h b/tools/perf/util/values.h index 2fa967e1a88a..b21a80c6cf8d 100644 --- a/tools/perf/util/values.h +++ b/tools/perf/util/values.h @@ -1,7 +1,7 @@ #ifndef __PERF_VALUES_H #define __PERF_VALUES_H -#include "types.h" +#include <linux/types.h> struct perf_read_values { int threads; diff --git a/tools/perf/util/vdso.c b/tools/perf/util/vdso.c index 0ddb3b8a89ec..adca69384fcc 100644 --- a/tools/perf/util/vdso.c +++ b/tools/perf/util/vdso.c @@ -11,10 +11,34 @@ #include "vdso.h" #include "util.h" #include "symbol.h" +#include "machine.h" #include "linux/string.h" +#include "debug.h" -static bool vdso_found; -static char vdso_file[] = "/tmp/perf-vdso.so-XXXXXX"; +#define VDSO__TEMP_FILE_NAME "/tmp/perf-vdso.so-XXXXXX" + +struct vdso_file { + bool found; + bool error; + char temp_file_name[sizeof(VDSO__TEMP_FILE_NAME)]; + const char *dso_name; +}; + +struct vdso_info { + struct vdso_file vdso; +}; + +static struct vdso_info *vdso_info__new(void) +{ + static const struct vdso_info vdso_info_init = { + .vdso = { + .temp_file_name = VDSO__TEMP_FILE_NAME, + .dso_name = DSO__NAME_VDSO, + }, + }; + + return memdup(&vdso_info_init, sizeof(vdso_info_init)); +} static int find_vdso_map(void **start, void **end) { @@ -47,7 +71,7 @@ static int find_vdso_map(void **start, void **end) return !found; } -static char *get_file(void) +static char *get_file(struct vdso_file *vdso_file) { char *vdso = NULL; char *buf = NULL; @@ -55,10 +79,10 @@ static char *get_file(void) size_t size; int fd; - if (vdso_found) - return vdso_file; + if (vdso_file->found) + return vdso_file->temp_file_name; - if (find_vdso_map(&start, &end)) + if (vdso_file->error || find_vdso_map(&start, &end)) return NULL; size = end - start; @@ -67,45 +91,78 @@ static char *get_file(void) if (!buf) return NULL; - fd = mkstemp(vdso_file); + fd = mkstemp(vdso_file->temp_file_name); if (fd < 0) goto out; if (size == (size_t) write(fd, buf, size)) - vdso = vdso_file; + vdso = vdso_file->temp_file_name; close(fd); out: free(buf); - vdso_found = (vdso != NULL); + vdso_file->found = (vdso != NULL); + vdso_file->error = !vdso_file->found; return vdso; } -void vdso__exit(void) +void vdso__exit(struct machine *machine) { - if (vdso_found) - unlink(vdso_file); + struct vdso_info *vdso_info = machine->vdso_info; + + if (!vdso_info) + return; + + if (vdso_info->vdso.found) + unlink(vdso_info->vdso.temp_file_name); + + zfree(&machine->vdso_info); } -struct dso *vdso__dso_findnew(struct list_head *head) +static struct dso *vdso__new(struct machine *machine, const char *short_name, + const char *long_name) { - struct dso *dso = dsos__find(head, VDSO__MAP_NAME, true); + struct dso *dso; + dso = dso__new(short_name); + if (dso != NULL) { + dsos__add(&machine->user_dsos, dso); + dso__set_long_name(dso, long_name, false); + } + + return dso; +} + +struct dso *vdso__dso_findnew(struct machine *machine, + struct thread *thread __maybe_unused) +{ + struct vdso_info *vdso_info; + struct dso *dso; + + if (!machine->vdso_info) + machine->vdso_info = vdso_info__new(); + + vdso_info = machine->vdso_info; + if (!vdso_info) + return NULL; + + dso = dsos__find(&machine->user_dsos, DSO__NAME_VDSO, true); if (!dso) { char *file; - file = get_file(); + file = get_file(&vdso_info->vdso); if (!file) return NULL; - dso = dso__new(VDSO__MAP_NAME); - if (dso != NULL) { - dsos__add(head, dso); - dso__set_long_name(dso, file, false); - } + dso = vdso__new(machine, DSO__NAME_VDSO, file); } return dso; } + +bool dso__is_vdso(struct dso *dso) +{ + return !strcmp(dso->short_name, DSO__NAME_VDSO); +} diff --git a/tools/perf/util/vdso.h b/tools/perf/util/vdso.h index 0f76e7caf6f8..af9d6929a215 100644 --- a/tools/perf/util/vdso.h +++ b/tools/perf/util/vdso.h @@ -7,12 +7,21 @@ #define VDSO__MAP_NAME "[vdso]" +#define DSO__NAME_VDSO "[vdso]" + static inline bool is_vdso_map(const char *filename) { return !strcmp(filename, VDSO__MAP_NAME); } -struct dso *vdso__dso_findnew(struct list_head *head); -void vdso__exit(void); +struct dso; + +bool dso__is_vdso(struct dso *dso); + +struct machine; +struct thread; + +struct dso *vdso__dso_findnew(struct machine *machine, struct thread *thread); +void vdso__exit(struct machine *machine); #endif /* __PERF_VDSO__ */ |